import {randomUUID} from 'node:crypto'

import type {PlaceRef} from '@crumbs/core/schemas'
import {afterAll, expect, test} from 'bun:test'
import {inArray} from 'drizzle-orm'

import {db, schema} from '../db/client'
import {
  createProvisionalPlace,
  getPlace,
  resolvePlaceRef,
} from './places'

const createdPlaceIds: string[] = []

const track = <T extends {id: string}>(place: T): T => {
  createdPlaceIds.push(place.id)
  return place
}

const makeRef = (overrides: Partial<PlaceRef> = {}): PlaceRef => {
  const osmId = String(Math.floor(Math.random() * 1e12))
  return {
    id: `liq:node/${osmId}`,
    provider: 'locationiq',
    osmType: 'node',
    osmId,
    name: 'Test cafe',
    category: 'cafe',
    address: '1 Test st',
    coordinate: {latitude: 37.77, longitude: -122.42},
    ...overrides,
  }
}

afterAll(async () => {
  if (!createdPlaceIds.length) return
  await db
    .delete(schema.placeMerges)
    .where(inArray(schema.placeMerges.loserId, createdPlaceIds))
  await db
    .delete(schema.places)
    .where(inArray(schema.places.id, createdPlaceIds))
})

test('resolvePlaceRef creates a canonical place with evidence', async () => {
  const ref = makeRef()
  const place = track(await resolvePlaceRef(ref, {queryText: 'q'}))

  expect(place.status).toBe('canonical')
  expect(place.name).toBe('Test cafe')
  expect(place.latitude).toBe(37.77)

  const sources = await db
    .select()
    .from(schema.placeSources)
    .where(inArray(schema.placeSources.placeId, [place.id]))
  expect(sources).toHaveLength(1)
  expect(sources[0]!.osmId).toBe(ref.osmId!)
  expect(sources[0]!.queryText).toBe('q')
})

test('resolvePlaceRef dedups by osm key', async () => {
  const ref = makeRef()
  const first = track(await resolvePlaceRef(ref))
  const second = await resolvePlaceRef({
    ...ref,
    name: 'Renamed upstream',
  })
  expect(second.id).toBe(first.id)
})

test('resolvePlaceRef without an osm key always inserts', async () => {
  const ref = makeRef({osmType: undefined, osmId: undefined})
  const first = track(await resolvePlaceRef(ref))
  const second = track(await resolvePlaceRef(ref))
  expect(second.id).not.toBe(first.id)
})

test('merged places redirect to their survivor', async () => {
  const loser = track(await resolvePlaceRef(makeRef()))
  const winner = track(await resolvePlaceRef(makeRef()))

  await db.insert(schema.placeMerges).values({
    loserId: loser.id,
    winnerId: winner.id,
    reason: 'test',
  })
  await db
    .update(schema.places)
    .set({status: 'merged', mergedIntoId: winner.id})
    .where(inArray(schema.places.id, [loser.id]))

  const resolved = await getPlace(loser.id)
  expect(resolved?.id).toBe(winner.id)
})

test('createProvisionalPlace stores hints without coordinates', async () => {
  const place = track(
    await createProvisionalPlace({
      name: `Mystery spot ${randomUUID()}`,
      address: 'somewhere in brooklyn',
    }),
  )
  expect(place.status).toBe('provisional')
  expect(place.latitude).toBeNull()
})

test('crumbs cannot target both a place and an event', async () => {
  const place = track(await resolvePlaceRef(makeRef()))
  const [event] = await db
    .insert(schema.events)
    .values({name: 'Test event'})
    .returning()
  const [user] = await db
    .insert(schema.users)
    .values({
      email: `places-${randomUUID()}@example.com`,
      handle: `places-${randomUUID().slice(0, 8)}`,
    })
    .returning()

  const rejected = await db
    .insert(schema.crumbs)
    .values({
      authorId: user!.id,
      name: 'impossible',
      placeId: place.id,
      eventId: event!.id,
    })
    .execute()
    .then(
      () => false,
      () => true,
    )
  expect(rejected).toBe(true)

  await db
    .delete(schema.users)
    .where(inArray(schema.users.id, [user!.id]))
  await db
    .delete(schema.events)
    .where(inArray(schema.events.id, [event!.id]))
})
