import {randomUUID} from 'node:crypto'

import {expect} from 'bun:test'
import {inArray} from 'drizzle-orm'

import app from './app'
import {createSession} from './auth/session'
import {upsertUserByEmail} from './auth/users'
import {db, schema} from './db/client'

export type TestUser = {id: string; handle: string; token: string}

const createdUserIds: string[] = []

export const createUser = async (
  prefix = 'test',
): Promise<TestUser> => {
  const user = await upsertUserByEmail(
    `${prefix}-${randomUUID()}@example.com`,
  )
  createdUserIds.push(user.id)
  const token = await createSession(user.id)
  return {id: user.id, handle: user.handle, token}
}

// each file calls this in afterAll; deleting a user cascades their
// sessions, crumbs, trails, and social rows
export const cleanupCreatedUsers = async () => {
  const ids = createdUserIds.splice(0)
  if (ids.length)
    await db.delete(schema.users).where(inArray(schema.users.id, ids))
}

export const request = async (
  as: TestUser,
  method: string,
  path: string,
  body?: unknown,
) => {
  const res = await app.request(path, {
    method,
    headers: {
      Authorization: `Bearer ${as.token}`,
      ...(body ? {'Content-Type': 'application/json'} : {}),
    },
    ...(body ? {body: JSON.stringify(body)} : {}),
  })
  return {
    status: res.status,
    json: await res.json().catch(() => null),
  }
}

export const befriend = (a: TestUser, b: TestUser) =>
  db.insert(schema.friendships).values({
    requesterId: a.id,
    addresseeId: b.id,
    status: 'accepted',
  })

export const requestFriend = (a: TestUser, b: TestUser) =>
  db.insert(schema.friendships).values({
    requesterId: a.id,
    addresseeId: b.id,
    status: 'pending',
  })

export const createCrumb = async (as: TestUser, name = 'spot') => {
  const res = await request(as, 'POST', '/crumbs', {name})
  expect(res.status).toBe(200)
  return res.json.crumb.id as string
}

export const createTrail = async (
  as: TestUser,
  {
    name = 'Test trail',
    crumbId,
  }: {name?: string; crumbId?: string} = {},
) => {
  const res = await request(as, 'POST', '/trails', {
    name,
    ...(crumbId && {crumbId}),
  })
  expect(res.status).toBe(200)
  return res.json.trail.id as string
}
