import {randomUUID} from 'node:crypto'

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

import app from '../app'
import {db, schema} from '../db/client'
import {setNetworkDown} from '../lib/dev-network'

// the routes are default-disabled; the suite opts in exactly like the
// dev/e2e scripts do
process.env.DEV_ROUTES = 'true'

const createdEmails: string[] = []
const e2eEmail = () => {
  const email = `dev-${randomUUID()}@example.com`
  createdEmails.push(email)
  return email
}

// seed sets deterministic handles, so seeding tests share one stable email
// (a singleton row) exactly like real e2e usage
const SEED_EMAIL = 'dev-seed@example.com'
createdEmails.push(SEED_EMAIL)

const login = (email: string) =>
  app.request('/dev/login', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({email}),
  })

const seed = (email: string, scenarios: string[]) =>
  app.request('/dev/seed', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({email, scenarios}),
  })

afterAll(async () => {
  const emails = [...createdEmails, 'e2e-friend@crumbs.test']
  const users = await db
    .select({id: schema.users.id})
    .from(schema.users)
    .where(inArray(schema.users.email, emails))
  const ids = users.map((u) => u.id)
  if (ids.length) {
    await db
      .delete(schema.sessions)
      .where(inArray(schema.sessions.userId, ids))
    await db.delete(schema.users).where(inArray(schema.users.id, ids))
  }
})

test('login mints a session for the email', async () => {
  const email = e2eEmail()
  const res = await login(email)
  expect(res.status).toBe(200)
  const {token, user} = (await res.json()) as {
    token: string
    user: {email: string}
  }
  expect(user.email).toBe(email)

  const me = await app.request('/auth/me', {
    headers: {Authorization: `Bearer ${token}`},
  })
  expect(me.status).toBe(200)
})

test('login is unreachable in production, even when opted in', async () => {
  const prev = process.env.NODE_ENV
  process.env.NODE_ENV = 'production'
  try {
    const res = await login(e2eEmail())
    expect(res.status).toBe(404)
  } finally {
    process.env.NODE_ENV = prev
  }
})

test('login is unreachable without the explicit opt-in', async () => {
  const prev = process.env.DEV_ROUTES
  delete process.env.DEV_ROUTES
  try {
    const res = await login(e2eEmail())
    expect(res.status).toBe(404)
  } finally {
    process.env.DEV_ROUTES = prev
  }
})

test('network toggle fails authed routes, then recovers', async () => {
  const {token} = (await (await login(e2eEmail())).json()) as {
    token: string
  }
  const auth = {Authorization: `Bearer ${token}`}
  const setNetwork = (down: boolean) =>
    app.request('/dev/network', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({down}),
    })
  try {
    expect((await setNetwork(true)).status).toBe(200)
    // authed routes now fail as if the api were unreachable, so the mobile
    // outbox keeps the write queued
    expect(
      (await app.request('/crumbs', {headers: auth})).status,
    ).toBe(503)
    // the /dev toggle itself stays reachable, so a flow can flip back
    expect((await setNetwork(false)).status).toBe(200)
    expect(
      (await app.request('/crumbs', {headers: auth})).status,
    ).toBe(200)
  } finally {
    // never leak the flag into another test, even on assertion failure
    setNetworkDown(false)
  }
})

test('seed builds fixtures and a working session', async () => {
  const res = await seed(SEED_EMAIL, [
    'crumbs',
    'trail',
    'friend-request',
    'trail-invite',
  ])
  expect(res.status).toBe(200)
  const {token, ids} = (await res.json()) as {
    token: string
    ids: {crumbIds?: string[]; trailId?: string}
  }
  expect(ids.crumbIds).toHaveLength(3)
  expect(ids.trailId).toBeString()

  const auth = {Authorization: `Bearer ${token}`}
  const crumbs = await app.request('/crumbs', {headers: auth})
  expect(
    ((await crumbs.json()) as {crumbs: unknown[]}).crumbs,
  ).toHaveLength(3)

  const requests = await app.request('/friends/requests', {
    headers: auth,
  })
  expect(
    (
      (await requests.json()) as {
        requests: {requester: {handle: string}}[]
      }
    ).requests[0]?.requester.handle,
  ).toBe('e2e-friend')

  const trails = await app.request('/trails', {headers: auth})
  expect(
    ((await trails.json()) as {trails: {name: string}[]}).trails.map(
      (t) => t.name,
    ),
  ).toContain('Weekend plans')

  const invites = await app.request('/trails/invites', {
    headers: auth,
  })
  expect(
    ((await invites.json()) as {invites: {trail: {name: string}}[]})
      .invites[0]?.trail.name,
  ).toBe('Friend trip')
})

test('seed is idempotent — wipes prior state each run', async () => {
  await seed(SEED_EMAIL, ['crumbs'])
  const res = await seed(SEED_EMAIL, ['crumbs'])
  const {token} = (await res.json()) as {token: string}
  const crumbs = await app.request('/crumbs', {
    headers: {Authorization: `Bearer ${token}`},
  })
  expect(
    ((await crumbs.json()) as {crumbs: unknown[]}).crumbs,
  ).toHaveLength(3)
})
