import {randomUUID} from 'node:crypto'

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

import {upsertUserByEmail} from '../auth/users'
import {db, schema} from '../db/client'
import {generateAutoReminders, pruneStaleReminders} from './reminders'

let userId: string
let eventId: string
let crumbId: string

const DAY_MS = 24 * 60 * 60 * 1000

beforeAll(async () => {
  const user = await upsertUserByEmail(
    `reminders-${randomUUID()}@example.com`,
  )
  userId = user.id
  const startsAt = new Date(Date.now() + 30 * DAY_MS)
  const [event] = await db
    .insert(schema.events)
    .values({name: 'Future gig', startsAt})
    .returning()
  eventId = event!.id
  const [crumb] = await db
    .insert(schema.crumbs)
    .values({authorId: userId, name: 'Gig', eventId})
    .returning()
  crumbId = crumb!.id
})

afterAll(async () => {
  await db.delete(schema.users).where(eq(schema.users.id, userId))
  await db.delete(schema.events).where(eq(schema.events.id, eventId))
})

test('generates week and day auto reminders idempotently', async () => {
  await generateAutoReminders()
  await generateAutoReminders()

  const rows = await db
    .select()
    .from(schema.reminders)
    .where(
      and(
        eq(schema.reminders.crumbId, crumbId),
        inArray(schema.reminders.kind, ['week_before', 'day_before']),
      ),
    )
  expect(rows).toHaveLength(2)
  const kinds = rows.map((r) => r.kind).sort()
  expect(kinds).toEqual(['day_before', 'week_before'])
})

test('reschedules unsent reminders when the event moves', async () => {
  const newStart = new Date(Date.now() + 60 * DAY_MS)
  await db
    .update(schema.events)
    .set({startsAt: newStart})
    .where(eq(schema.events.id, eventId))

  await generateAutoReminders()

  const [weekBefore] = await db
    .select()
    .from(schema.reminders)
    .where(
      and(
        eq(schema.reminders.crumbId, crumbId),
        eq(schema.reminders.kind, 'week_before'),
      ),
    )
  expect(weekBefore!.remindAt.getTime()).toBe(
    newStart.getTime() - 7 * DAY_MS,
  )
})

const reminderRows = (id: string) =>
  db
    .select()
    .from(schema.reminders)
    .where(
      and(
        eq(schema.reminders.crumbId, id),
        inArray(schema.reminders.kind, ['week_before', 'day_before']),
      ),
    )

test('prunes unsent reminders when a plan is cancelled', async () => {
  const [crumb] = await db
    .insert(schema.crumbs)
    .values({
      authorId: userId,
      name: 'Planned spot',
      status: 'planned',
      plannedFor: new Date(Date.now() + 10 * DAY_MS),
    })
    .returning()

  await generateAutoReminders()
  expect(await reminderRows(crumb!.id)).toHaveLength(1)

  await db
    .update(schema.crumbs)
    .set({status: 'saved', plannedFor: null})
    .where(eq(schema.crumbs.id, crumb!.id))
  await pruneStaleReminders()

  expect(await reminderRows(crumb!.id)).toHaveLength(0)
})

test('a stale plannedFor on a non-planned crumb neither generates nor survives', async () => {
  const [crumb] = await db
    .insert(schema.crumbs)
    .values({
      authorId: userId,
      name: 'Reverted plan',
      status: 'saved',
      plannedFor: new Date(Date.now() + 10 * DAY_MS),
    })
    .returning()

  // plannedFor without status=planned is not an upcoming basis
  await generateAutoReminders()
  expect(await reminderRows(crumb!.id)).toHaveLength(0)

  // a reminder generated while planned is pruned once the plan reverts
  await db
    .update(schema.crumbs)
    .set({status: 'planned'})
    .where(eq(schema.crumbs.id, crumb!.id))
  await generateAutoReminders()
  expect(await reminderRows(crumb!.id)).toHaveLength(1)

  await db
    .update(schema.crumbs)
    .set({status: 'saved'})
    .where(eq(schema.crumbs.id, crumb!.id))
  await pruneStaleReminders()
  expect(await reminderRows(crumb!.id)).toHaveLength(0)
})

test('prunes unsent reminders when the crumb is done', async () => {
  await db
    .update(schema.crumbs)
    .set({status: 'done'})
    .where(eq(schema.crumbs.id, crumbId))
  await pruneStaleReminders()

  expect(await reminderRows(crumbId)).toHaveLength(0)
})

test('keeps sent and manual reminders on prune', async () => {
  const [crumb] = await db
    .insert(schema.crumbs)
    .values({authorId: userId, name: 'Kept', status: 'saved'})
    .returning()
  await db.insert(schema.reminders).values([
    {
      userId,
      crumbId: crumb!.id,
      remindAt: new Date(),
      kind: 'manual',
    },
    {
      userId,
      crumbId: crumb!.id,
      remindAt: new Date(),
      kind: 'day_before',
      sentAt: new Date(),
    },
  ])

  await pruneStaleReminders()

  const rows = await db
    .select()
    .from(schema.reminders)
    .where(eq(schema.reminders.crumbId, crumb!.id))
  expect(rows).toHaveLength(2)
})
