import {randomUUID} from 'node:crypto'

import {afterAll, beforeAll, expect, test} from 'bun:test'
import {eq} from 'drizzle-orm'

import {db, schema} from '../db/client'
import {
  cleanupCreatedUsers,
  createUser,
  request,
  type TestUser,
} from '../test-fixtures'

let me: TestUser

beforeAll(async () => {
  me = await createUser()
  const friends = await Promise.all([
    createUser(),
    createUser(),
    createUser(),
  ])
  await db.insert(schema.friendships).values(
    friends.map((f) => ({
      requesterId: me.id,
      addresseeId: f.id,
      status: 'accepted' as const,
    })),
  )
})

afterAll(cleanupCreatedUsers)

test('friends list paginates with limit and cursor', async () => {
  const first = await request(me, 'GET', '/friends?limit=2')
  expect(first.status).toBe(200)
  expect(first.json.friends).toHaveLength(2)
  expect(first.json.nextCursor).toBeTruthy()

  const second = await request(
    me,
    'GET',
    `/friends?limit=2&cursor=${encodeURIComponent(first.json.nextCursor)}`,
  )
  expect(second.json.friends).toHaveLength(1)
  expect(second.json.nextCursor).toBeNull()

  const ids = [...first.json.friends, ...second.json.friends].map(
    (f: {id: string}) => f.id,
  )
  expect(new Set(ids).size).toBe(3)
})

test('friends list filters by handle with q', async () => {
  const pal = await createUser()
  const stranger = await createUser()
  const needle = `needle${randomUUID().slice(0, 8)}`
  await db
    .update(schema.users)
    .set({handle: needle})
    .where(eq(schema.users.id, pal.id))
  await db
    .update(schema.users)
    .set({handle: `${needle}x`})
    .where(eq(schema.users.id, stranger.id))
  await db.insert(schema.friendships).values({
    requesterId: pal.id,
    addresseeId: me.id,
    status: 'accepted' as const,
  })

  // matches by substring, friends only — the stranger shares the needle
  const hit = await request(
    me,
    'GET',
    `/friends?q=${needle.slice(2, 10)}`,
  )
  expect(hit.status).toBe(200)
  expect(hit.json.friends.map((f: {id: string}) => f.id)).toEqual([
    pal.id,
  ])

  const miss = await request(me, 'GET', '/friends?q=nosuchhandle')
  expect(miss.status).toBe(200)
  expect(miss.json.friends).toEqual([])
})

test('unfriend removes the friendship in either direction', async () => {
  const [requester, addressee] = await Promise.all([
    createUser(),
    createUser(),
  ])
  await db.insert(schema.friendships).values([
    {
      requesterId: requester.id,
      addresseeId: me.id,
      status: 'accepted' as const,
    },
    {
      requesterId: me.id,
      addresseeId: addressee.id,
      status: 'accepted' as const,
    },
  ])

  const asAddressee = await request(
    me,
    'DELETE',
    `/friends/${requester.id}`,
  )
  expect(asAddressee.status).toBe(200)

  const asRequester = await request(
    me,
    'DELETE',
    `/friends/${addressee.id}`,
  )
  expect(asRequester.status).toBe(200)

  const list = await request(me, 'GET', '/friends?limit=50')
  const ids = list.json.friends.map((f: {id: string}) => f.id)
  expect(ids).not.toContain(requester.id)
  expect(ids).not.toContain(addressee.id)
})

test('unfriend 404s for non-friends and pending requests', async () => {
  const stranger = await createUser()
  const missing = await request(
    me,
    'DELETE',
    `/friends/${stranger.id}`,
  )
  expect(missing.status).toBe(404)

  await db.insert(schema.friendships).values({
    requesterId: me.id,
    addresseeId: stranger.id,
    status: 'pending' as const,
  })
  const pending = await request(
    me,
    'DELETE',
    `/friends/${stranger.id}`,
  )
  expect(pending.status).toBe(404)
})
