import type { CompetitionCategory, PrismaClient } from '@prisma/client'
import { signFinalistToken } from '@/lib/finalist-token'
import { sendFinalistConfirmationEmail } from '@/lib/email'
import { logAudit } from '@/server/utils/audit'
import { notifyAdmins, createNotification, NotificationTypes } from './in-app-notification'

type AnyPrisma = Pick<PrismaClient, 'finalistConfirmation' | 'waitlistEntry' | 'project'>

/**
 * Create a PENDING FinalistConfirmation row with a signed token. Caller is
 * responsible for sending the notification email separately.
 */
export async function createPendingConfirmation(
  prisma: Pick<PrismaClient, 'finalistConfirmation'>,
  args: {
    projectId: string
    category: CompetitionCategory
    windowHours: number
    promotedFromWaitlistEntryId?: string
  },
): Promise<{ id: string; token: string; deadline: Date }> {
  const deadline = new Date(Date.now() + args.windowHours * 3_600_000)
  // Generate the row ID up front so we can sign it into the token before
  // writing the row (token is unique-indexed; embedding the ID gives the
  // public verify path a stable lookup key).
  const id = `cmfc_${Math.random().toString(36).slice(2, 10)}_${Date.now().toString(36)}`
  const token = signFinalistToken({
    confirmationId: id,
    exp: Math.floor(deadline.getTime() / 1000),
  })
  await prisma.finalistConfirmation.create({
    data: {
      id,
      projectId: args.projectId,
      category: args.category,
      status: 'PENDING',
      deadline,
      token,
      promotedFromWaitlistEntryId: args.promotedFromWaitlistEntryId ?? null,
    },
  })
  return { id, token, deadline }
}

/**
 * Promote the lowest-ranked WAITING waitlist entry in the given category to
 * PROMOTED, create a fresh PENDING confirmation for the project, and send
 * the notification email. No-op if no WAITING entry exists.
 */
export async function promoteNextWaitlistEntry(
  prisma: AnyPrisma,
  args: { programId: string; category: CompetitionCategory; windowHours: number },
): Promise<{ promoted: boolean; entryId?: string; confirmationId?: string }> {
  const entry = await prisma.waitlistEntry.findFirst({
    where: {
      programId: args.programId,
      category: args.category,
      status: 'WAITING',
    },
    orderBy: { rank: 'asc' },
  })
  if (!entry) return { promoted: false }

  await prisma.waitlistEntry.update({
    where: { id: entry.id },
    data: { status: 'PROMOTED' },
  })

  const { id: confirmationId, token, deadline } = await createPendingConfirmation(prisma, {
    projectId: entry.projectId,
    category: args.category,
    windowHours: args.windowHours,
    promotedFromWaitlistEntryId: entry.id,
  })

  // Send email — log and continue on failure.
  const project = await prisma.project.findUnique({
    where: { id: entry.projectId },
    select: {
      title: true,
      teamMembers: {
        where: { role: 'LEAD' },
        take: 1,
        select: { user: { select: { email: true, name: true } } },
      },
    },
  })
  const lead = project?.teamMembers[0]?.user
  if (lead?.email && project) {
    const baseUrl = (process.env.NEXTAUTH_URL ?? 'http://localhost:3000').replace(/\/$/, '')
    const confirmUrl = `${baseUrl}/finalist/confirm/${token}`
    try {
      await sendFinalistConfirmationEmail(
        lead.email,
        lead.name ?? null,
        project.title,
        deadline,
        confirmUrl,
      )
    } catch (err) {
      console.error(
        `[promoteNextWaitlistEntry] failed to send email for project ${entry.projectId}:`,
        err,
      )
    }
  }

  // Admin alert — best-effort, never throws
  try {
    const projectTitle = project?.title ?? entry.projectId
    await notifyAdmins({
      type: NotificationTypes.FINALIST_WAITLIST_PROMOTED,
      title: 'Waitlist entry promoted',
      message: `"${projectTitle}" (${args.category}) was promoted from the waitlist.`,
      linkUrl: '/admin/logistics',
      metadata: {
        projectId: entry.projectId,
        projectTitle,
        category: args.category,
      },
    })
  } catch (err) {
    console.error(
      `[promoteNextWaitlistEntry] failed to send admin notification for project ${entry.projectId}:`,
      err,
    )
  }

  return { promoted: true, entryId: entry.id, confirmationId }
}

/**
 * Cron entrypoint: find every PENDING confirmation past its deadline, mark
 * each EXPIRED, and promote the next waitlist entry per affected category.
 */
export async function expirePendingPastDeadline(
  prisma: PrismaClient,
): Promise<{ expired: number; promoted: number }> {
  const expired = await prisma.finalistConfirmation.findMany({
    where: { status: 'PENDING', deadline: { lt: new Date() } },
    include: { project: { select: { programId: true } } },
  })
  let promoted = 0
  for (const c of expired) {
    await prisma.finalistConfirmation.update({
      where: { id: c.id },
      data: { status: 'EXPIRED', expiredAt: new Date() },
    })
    await logAudit({
      prisma,
      action: 'FINALIST_EXPIRED',
      entityType: 'FinalistConfirmation',
      entityId: c.id,
      detailsJson: { projectId: c.projectId, category: c.category },
    })
    // Admin alert — best-effort, never throws
    try {
      // Resolve project title for a meaningful notification message
      const proj = await prisma.project.findUnique({
        where: { id: c.projectId },
        select: { title: true },
      })
      const projectTitle = proj?.title ?? c.projectId
      await notifyAdmins({
        type: NotificationTypes.FINALIST_EXPIRED,
        title: 'Finalist confirmation expired',
        message: `"${projectTitle}" (${c.category}) did not confirm in time — confirmation expired.`,
        linkUrl: '/admin/logistics',
        metadata: {
          projectId: c.projectId,
          projectTitle,
          category: c.category,
        },
      })
    } catch (err) {
      console.error(
        `[expirePendingPastDeadline] failed to send admin notification for project ${c.projectId}:`,
        err,
      )
    }
    // Resolve windowHours for this program's grand-finale round
    const round = await prisma.round.findFirst({
      where: {
        competition: { programId: c.project.programId },
        roundType: 'LIVE_FINAL',
      },
      orderBy: { sortOrder: 'desc' },
      select: { configJson: true },
    })
    const cfg = (round?.configJson ?? {}) as { confirmationWindowHours?: number }
    const windowHours = cfg.confirmationWindowHours ?? 24
    const result = await promoteNextWaitlistEntry(prisma, {
      programId: c.project.programId,
      category: c.category,
      windowHours,
    })
    if (result.promoted) promoted++
  }
  return { expired: expired.length, promoted }
}

/**
 * Cron entrypoint: send pre-deadline confirmation reminders.
 *
 * For each PENDING confirmation that has not yet received a reminder
 * (reminderSentAt IS NULL) and whose deadline is still in the future but
 * within the program's configured `reminderHoursBeforeDeadline` window
 * (default 12 h), send a FINALIST_REMINDER in-app notification (+ email via
 * the notification pipeline) to the project's LEAD team member, then stamp
 * `reminderSentAt` so the row is never processed again.
 *
 * Best-effort per row — a failure on one row never aborts the rest.
 */
export async function sendDueConfirmationReminders(
  prisma: PrismaClient,
): Promise<{ remindersSent: number }> {
  const now = new Date()

  // Load all candidates: PENDING, no reminder sent yet, deadline still future.
  const candidates = await prisma.finalistConfirmation.findMany({
    where: {
      status: 'PENDING',
      reminderSentAt: null,
      deadline: { gt: now },
    },
    include: {
      project: {
        select: {
          id: true,
          title: true,
          programId: true,
          teamMembers: {
            where: { role: 'LEAD' },
            take: 1,
            select: {
              userId: true,
              user: { select: { name: true } },
            },
          },
        },
      },
    },
  })

  if (candidates.length === 0) return { remindersSent: 0 }

  // Cache reminderHoursBeforeDeadline per programId to avoid repeat queries.
  const reminderHoursCache = new Map<string, number>()

  async function getReminderHours(programId: string): Promise<number> {
    if (reminderHoursCache.has(programId)) {
      return reminderHoursCache.get(programId)!
    }
    const round = await prisma.round.findFirst({
      where: {
        competition: { programId },
        roundType: 'LIVE_FINAL',
      },
      orderBy: { sortOrder: 'desc' },
      select: { configJson: true },
    })
    const cfg = (round?.configJson ?? {}) as { reminderHoursBeforeDeadline?: number }
    const hours = cfg.reminderHoursBeforeDeadline ?? 12
    reminderHoursCache.set(programId, hours)
    return hours
  }

  const baseUrl = (process.env.NEXTAUTH_URL ?? 'http://localhost:3000').replace(/\/$/, '')
  let remindersSent = 0

  for (const row of candidates) {
    try {
      const reminderHours = await getReminderHours(row.project.programId)
      const windowMs = reminderHours * 3_600_000
      const isDue = row.deadline.getTime() <= now.getTime() + windowMs

      if (!isDue) continue

      const lead = row.project.teamMembers[0]
      if (!lead) continue

      const confirmUrl = `${baseUrl}/finalist/confirm/${row.token}`
      const title = row.project.title

      await createNotification({
        userId: lead.userId,
        type: NotificationTypes.FINALIST_REMINDER,
        title: 'Reminder: confirm your grand-finale attendance',
        message: `Please confirm attendance for "${title}" before the deadline.`,
        linkUrl: confirmUrl,
        metadata: {
          projectTitle: title,
          projectId: row.project.id,
          deadline: row.deadline.toISOString(),
        },
      })

      await prisma.finalistConfirmation.update({
        where: { id: row.id },
        data: { reminderSentAt: new Date() },
      })

      remindersSent++
    } catch (err) {
      console.error(
        `[sendDueConfirmationReminders] failed for confirmation ${row.id} (project ${row.projectId}):`,
        err,
      )
    }
  }

  return { remindersSent }
}
