import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { getSessionUser } from '@/lib/auth'
import { createAdminClient } from '@/lib/supabase/admin'
import { isMissingColumn } from '@/lib/db-compat'
import { TERMINAL_STATUSES } from '@/lib/domain/moderation'
import type { TicketStatus } from '@/types/db'

/**
 * Moderator actions (admin-gated; the finer Moderator / State Expert /
 * Knowledge Manager roles are backend-later — an admin acts in all three).
 * Every change lands in ticket_events — moderators add context, they never
 * erase history (§20).
 *
 * ops:
 *   (none)           patch one ticket's fields (+ optional note)
 *   merge            §23 — fold tickets into a master issue
 *   developer_issue  §24 — raise a technical issue with full context
 *   publish          §16/§17 — mark an approved correction as published
 *   settings         §3/§22 — categories, state → reviewer assignments
 *   issue_done       close a developer issue
 */

const STATUSES = [
  'new',
  'in_review',
  'needs_more_context',
  'escalated',
  'correction_proposed',
  'approved_for_knowledge_update',
  'knowledge_updated',
  'no_change_needed',
  'closed',
  'duplicate',
] as const

const patchSchema = z
  .object({
    status: z.enum(STATUSES).optional(),
    decision: z.string().trim().max(60).optional(),
    assigned_to: z.string().trim().max(120).nullable().optional(),
    moderator_notes: z.string().trim().max(4000).optional(),
    correction: z.string().trim().max(4000).optional(),
    correction_explanation: z.string().trim().max(4000).optional(),
    correction_source: z.string().trim().max(400).optional(),
    correction_state: z.string().trim().max(40).optional(),
    correction_topic: z.string().trim().max(60).optional(),
    applies_to: z.enum(['this_permit', 'all_permits']).optional(),
    applies_states: z.enum(['one_state', 'multiple_states']).optional(),
    update_state_notes: z.boolean().optional(),
    create_qa: z.boolean().optional(),
    update_prompt_logic: z.boolean().optional(),
    escalate_developer: z.boolean().optional(),
    confidence_review: z
      .enum(['appropriate', 'too_high', 'too_low', 'should_have_escalated', 'missing'])
      .optional(),
    source_alignment: z
      .enum([
        'permit_matches_provision',
        'permit_conflicts_provision',
        'notes_add_exception',
        'internal_qa_used',
        'no_permit_available',
        'low_source_confidence',
      ])
      .optional(),
    update_type: z
      .enum(['state_note', 'qa_entry', 'prompt_logic', 'retrieval', 'ocr_parsing', 'product_ui'])
      .nullable()
      .optional(),
    qa_visibility: z
      .enum([
        'internal_only',
        'broker_facing',
        'carrier_facing',
        'driver_facing',
        'processing_team_only',
        'training_only',
        'do_not_use',
      ])
      .nullable()
      .optional(),
    close_reason: z.string().trim().max(400).optional(),
  })
  .refine((v) => Object.keys(v).length > 0, { message: 'Nothing to update.' })

const bodySchema = z.discriminatedUnion('op', [
  z.object({
    op: z.literal('patch').default('patch'),
    ticket_id: z.string().uuid(),
    patch: patchSchema,
    note: z.string().trim().max(400).optional().or(z.literal('')),
  }),
  z.object({
    op: z.literal('merge'),
    master_id: z.string().uuid(),
    ticket_ids: z.array(z.string().uuid()).min(1).max(50),
  }),
  z.object({
    op: z.literal('developer_issue'),
    ticket_id: z.string().uuid(),
    error_category: z.string().trim().min(1).max(120),
    summary: z.string().trim().max(2000).optional().or(z.literal('')),
  }),
  z.object({ op: z.literal('publish'), ticket_id: z.string().uuid() }),
  z.object({
    op: z.literal('settings'),
    key: z.enum(['categories', 'state_reviewers']),
    value: z.unknown(),
  }),
  z.object({ op: z.literal('issue_done'), issue_id: z.string().uuid() }),
])

const NEW_COLUMNS = ['update_type', 'qa_visibility', 'close_reason', 'resolved_at', 'duplicate_of']

export async function POST(req: NextRequest) {
  const user = await getSessionUser()
  if (!user || user.role !== 'admin') {
    return NextResponse.json({ error: 'Admin only.' }, { status: 403 })
  }
  const raw = await req.json().catch(() => ({}))
  const parsed = bodySchema.safeParse({ op: 'patch', ...raw })
  if (!parsed.success) return NextResponse.json({ error: 'Invalid request.' }, { status: 400 })
  const body = parsed.data
  const admin = createAdminClient()
  const actor = user.name || user.email
  const now = new Date().toISOString()

  const logEvent = (ticketId: string, action: string, detail: Record<string, unknown>) =>
    admin.from('ticket_events').insert({ ticket_id: ticketId, actor, action, detail })

  /** Update with migration tolerance: strip 0012 columns if the DB lacks them. */
  async function updateTicket(ticketId: string, fields: Record<string, unknown>) {
    let { data, error } = await admin
      .from('review_tickets')
      .update({ ...fields, updated_at: now })
      .eq('id', ticketId)
      .select()
      .single()
    if (error && isMissingColumn(error, ...NEW_COLUMNS)) {
      const legacy = Object.fromEntries(
        Object.entries(fields).filter(([k]) => !NEW_COLUMNS.includes(k)),
      )
      ;({ data, error } = await admin
        .from('review_tickets')
        .update({ ...legacy, updated_at: now })
        .eq('id', ticketId)
        .select()
        .single())
    }
    return { data, error }
  }

  // ---- patch ----------------------------------------------------------
  if (body.op === 'patch') {
    const { ticket_id, patch, note } = body
    const fields: Record<string, unknown> = { ...patch }
    if (patch.status) {
      // Terminal statuses stamp resolved_at (§21); reopening clears it.
      fields.resolved_at = TERMINAL_STATUSES.includes(patch.status as TicketStatus) ? now : null
    }
    const { data: ticket, error } = await updateTicket(ticket_id, fields)
    if (error || !ticket) {
      return NextResponse.json({ error: 'Could not update the ticket.' }, { status: 500 })
    }
    const action = patch.status
      ? 'status_changed'
      : patch.decision
        ? 'decision_recorded'
        : patch.correction !== undefined
          ? 'correction_proposed'
          : patch.assigned_to !== undefined
            ? 'assigned'
            : 'ticket_updated'
    await logEvent(ticket_id, action, { ...patch, ...(note ? { note } : {}) })
    return NextResponse.json({ ok: true, ticket })
  }

  // ---- merge (§23) -------------------------------------------------------
  if (body.op === 'merge') {
    const children = body.ticket_ids.filter((id) => id !== body.master_id)
    let merged = 0
    for (const id of children) {
      const { error } = await updateTicket(id, {
        status: 'duplicate',
        duplicate_of: body.master_id,
        resolved_at: now,
      })
      if (!error) {
        merged++
        await logEvent(id, 'merged_into_master', { master_id: body.master_id })
      }
    }
    await logEvent(body.master_id, 'tickets_merged', { merged, ticket_ids: children })
    return NextResponse.json({ ok: true, merged })
  }

  // ---- developer issue (§24) ----------------------------------------------
  if (body.op === 'developer_issue') {
    const { data: t } = await admin
      .from('review_tickets')
      .select('*')
      .eq('id', body.ticket_id)
      .maybeSingle()
    if (!t) return NextResponse.json({ error: 'Ticket not found.' }, { status: 404 })
    const ids = [t.message_id, t.question_message_id].filter(Boolean) as string[]
    const { data: msgs } = await admin
      .from('chat_messages')
      .select('id, content, sources, confidence')
      .in('id', ids)
    const answer = msgs?.find((m) => m.id === t.message_id)
    const question = msgs?.find((m) => m.id === t.question_message_id)
    let permitDocumentId: string | null = null
    if (t.permit_id) {
      const { data: p } = await admin
        .from('permits')
        .select('document_id')
        .eq('id', t.permit_id)
        .maybeSingle()
      permitDocumentId = p?.document_id ?? null
    }
    // Everything a developer needs (§24) — model/prompt versions arrive with
    // the AI backend's answer records (§26); recorded as unknown until then.
    const context = {
      ticket_id: t.id,
      trip_id: t.trip_id,
      user_question: question?.content ?? null,
      ai_answer: answer?.content ?? null,
      sources_retrieved: answer?.sources ?? t.sources ?? null,
      confidence: t.confidence,
      state_code: t.state_code,
      permit_id: t.permit_id,
      document_ids: permitDocumentId ? [permitDocumentId] : [],
      model_version: t.source_versions?.model ?? null,
      prompt_version: t.source_versions?.prompt ?? null,
      error_category: body.error_category,
      moderator_comments: t.moderator_notes ?? null,
    }
    const { data: issue, error } = await admin
      .from('developer_issues')
      .insert({
        ticket_id: t.id,
        error_category: body.error_category,
        summary: body.summary || null,
        context,
        created_by: actor,
      })
      .select()
      .single()
    if (error || !issue) {
      return NextResponse.json(
        { error: 'Could not create the developer issue — is migration 0012 applied?' },
        { status: 500 },
      )
    }
    await updateTicket(t.id, { escalate_developer: true, decision: 'Create Developer Issue' })
    await logEvent(t.id, 'developer_issue_created', {
      issue_id: issue.id,
      error_category: body.error_category,
    })
    return NextResponse.json({ ok: true, issue })
  }

  // ---- publish (§16 step 4 / §17) -----------------------------------------
  if (body.op === 'publish') {
    const { data: ticket, error } = await updateTicket(body.ticket_id, {
      status: 'knowledge_updated',
      resolved_at: now,
    })
    if (error || !ticket) {
      return NextResponse.json({ error: 'Could not publish the correction.' }, { status: 500 })
    }
    // Duplicates folded into this master are closed with it (§23).
    const { data: dupes } = await admin
      .from('review_tickets')
      .select('id')
      .eq('duplicate_of', body.ticket_id)
    for (const d of dupes ?? []) {
      await updateTicket(d.id, { status: 'duplicate', resolved_at: now })
      await logEvent(d.id, 'closed_with_master', { master_id: body.ticket_id })
    }
    // TODO(backend): push the approved correction into the controlled knowledge
    // sources (state notes / Q&A / prompt) so future answers use it (§30).
    await logEvent(body.ticket_id, 'knowledge_published', {
      update_type: ticket.update_type ?? null,
      closed_duplicates: (dupes ?? []).length,
    })
    return NextResponse.json({ ok: true, ticket })
  }

  // ---- settings (§3, §22) -------------------------------------------------
  if (body.op === 'settings') {
    const { error } = await admin
      .from('moderation_settings')
      .upsert({ key: body.key, value: body.value, updated_by: actor, updated_at: now }, { onConflict: 'key' })
    if (error) {
      return NextResponse.json(
        { error: 'Could not save settings — is migration 0012 applied?' },
        { status: 500 },
      )
    }
    return NextResponse.json({ ok: true })
  }

  // ---- developer issue done -------------------------------------------------
  if (body.op === 'issue_done') {
    const { error } = await admin
      .from('developer_issues')
      .update({ status: 'done' })
      .eq('id', body.issue_id)
    if (error) return NextResponse.json({ error: 'Could not update the issue.' }, { status: 500 })
    return NextResponse.json({ ok: true })
  }

  return NextResponse.json({ error: 'Unknown operation.' }, { status: 400 })
}
