import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { getSessionUser } from '@/lib/auth'
import { createAdminClient } from '@/lib/supabase/admin'
import { lookupCompanyByMc } from '@/lib/adapters/company-lookup'
import {
  approveClaim, companyHasAdmin, denyClaim, isCompanySchemaMissing, isCorporateContact, logCompanyEvent,
} from '@/lib/data/companies'
import { emailDomain, isFreeMailDomain, normalizeMc, websiteDomain } from '@/lib/domain/company'
import type { Company, CompanyClaim, VerificationMethod } from '@/types/db'

/**
 * "Verify your company access" (Task 89) — Nash's one-button flow:
 * pop-up asks for the MC number → Verify → a request goes to the email on
 * file for that company → the user is told where it went.
 *
 * ops:
 *   request  { mc }   create the company (or reuse it), the claim and a
 *                     pending membership; returns where the request went
 *   resend            new token + expiry on the user's pending claim
 *   cancel            withdraw the pending claim ("Use a different MC number")
 *   corporate_decide  { claim_id, decision } — the signed-in CORPORATE CONTACT
 *                     (account email = the company's corporate email or a
 *                     notification recipient) approves / denies / reports a
 *                     pending request from inside the app (Task 83). Same
 *                     §8 Method 2 decision as the emailed link.
 *
 * Email SENDING is the email backend (later) — the approval link is returned
 * so the pending screen can show it, exactly like trip invites today.
 * Verification is optional: nothing else in the product checks it.
 */

const schema = z.discriminatedUnion('op', [
  z.object({ op: z.literal('request'), mc: z.string().trim().min(1).max(20) }),
  z.object({ op: z.literal('resend') }),
  z.object({ op: z.literal('cancel') }),
  z.object({
    op: z.literal('corporate_decide'),
    claim_id: z.string().uuid(),
    decision: z.enum(['approve', 'deny', 'report']),
  }),
])

const NEEDS_MIGRATION = 'Company verification needs database migration 0013.'

export async function POST(req: NextRequest) {
  const user = await getSessionUser()
  if (!user) return NextResponse.json({ error: 'Sign in first.' }, { status: 401 })
  // Nash: brokers and carriers/dispatchers; "the drivers do not need any validation".
  if (user.role !== 'broker' && user.role !== 'dispatcher') {
    return NextResponse.json({ error: 'Company verification is for broker and carrier accounts.' }, { status: 403 })
  }

  const parsed = schema.safeParse(await req.json().catch(() => ({})))
  if (!parsed.success) return NextResponse.json({ error: 'Invalid request.' }, { status: 400 })
  const input = parsed.data
  const admin = createAdminClient()
  const actorLabel = user.name || user.email
  const ip = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? null

  if (input.op === 'corporate_decide') {
    const { data: claimRow, error } = await admin
      .from('company_claims')
      .select('*')
      .eq('id', input.claim_id)
      .maybeSingle()
    if (isCompanySchemaMissing(error)) return NextResponse.json({ error: NEEDS_MIGRATION }, { status: 503 })
    const claim = (claimRow ?? null) as CompanyClaim | null
    if (!claim) return NextResponse.json({ error: 'Request not found.' }, { status: 404 })
    if (claim.claim_status !== 'pending_corporate_approval') {
      return NextResponse.json({ error: 'This request has already been decided.' }, { status: 409 })
    }
    if (new Date(claim.expires_at).getTime() < Date.now()) {
      return NextResponse.json({ error: 'This request has expired — ask the person to resend it.' }, { status: 410 })
    }
    if (claim.user_id === user.id) {
      return NextResponse.json({ error: 'You cannot approve your own request.' }, { status: 403 })
    }
    const { data: companyRow } = await admin.from('companies').select('*').eq('id', claim.company_id).maybeSingle()
    const company = (companyRow ?? null) as Company | null
    // Never trust the page: the decision is only valid from the corporate
    // contact on file.
    if (!company || !isCorporateContact(user.email, company)) {
      return NextResponse.json({ error: 'Only the corporate contact on file for this company can decide this request.' }, { status: 403 })
    }
    if (input.decision === 'approve') {
      const result = await approveClaim({
        claim,
        approverLabel: actorLabel,
        approverUserId: user.id,
        approverEmail: user.email,
        ip,
        via: 'corporate_email',
      })
      if (!result.ok) return NextResponse.json({ error: result.error }, { status: 400 })
      return NextResponse.json({ ok: true, status: 'approved', role: result.role })
    }
    await denyClaim({
      claim,
      approverLabel: actorLabel,
      approverUserId: user.id,
      approverEmail: user.email,
      ip,
      reported: input.decision === 'report',
    })
    return NextResponse.json({ ok: true, status: input.decision === 'report' ? 'admin_review_required' : 'rejected' })
  }

  // The user's newest claim, if any.
  const existing = await admin
    .from('company_claims')
    .select('*')
    .eq('user_id', user.id)
    .order('created_at', { ascending: false })
    .limit(1)
  if (isCompanySchemaMissing(existing.error)) {
    return NextResponse.json({ error: NEEDS_MIGRATION }, { status: 503 })
  }
  const current = ((existing.data ?? [])[0] ?? null) as CompanyClaim | null

  if (input.op === 'resend') {
    if (!current || current.claim_status !== 'pending_corporate_approval') {
      return NextResponse.json({ error: 'There is no pending request to resend.' }, { status: 400 })
    }
    const { data: updated } = await admin
      .from('company_claims')
      .update({
        approval_token: randomToken(),
        expires_at: new Date(Date.now() + 14 * 86_400_000).toISOString(),
        updated_at: new Date().toISOString(),
      })
      .eq('id', current.id)
      .select()
      .single()
    await logCompanyEvent({
      companyId: current.company_id,
      actorUserId: user.id,
      actorLabel,
      eventType: 'approval_resent',
      newValue: { claim_id: current.id, sent_to: current.approval_sent_to },
      ip,
    })
    return NextResponse.json({ ok: true, claim: updated })
  }

  if (input.op === 'cancel') {
    if (!current || ['approved', 'revoked'].includes(current.claim_status)) {
      return NextResponse.json({ error: 'There is no open request to cancel.' }, { status: 400 })
    }
    await withdraw(current, actorLabel, user.id, ip)
    return NextResponse.json({ ok: true })
  }

  // ---- op: request ----
  const mc = normalizeMc(input.mc)
  if (!mc) {
    return NextResponse.json({ error: 'Enter the MC number as digits (5–8), e.g. 088374.' }, { status: 400 })
  }

  // An approved member already represents a company — nothing to request.
  const { count: approvedCount } = await admin
    .from('company_memberships')
    .select('id', { count: 'exact', head: true })
    .eq('user_id', user.id)
    .eq('status', 'approved')
  if ((approvedCount ?? 0) > 0) {
    return NextResponse.json({ error: 'You are already an approved company agent.' }, { status: 409 })
  }
  // A new request replaces an open one.
  if (current && !['approved', 'revoked', 'rejected'].includes(current.claim_status)) {
    await withdraw(current, actorLabel, user.id, ip)
  }

  const lookup = await lookupCompanyByMc(mc)
  const companyType = user.role === 'dispatcher' ? 'carrier' : 'broker'
  let company: Company
  if (lookup.company) {
    company = lookup.company
  } else {
    const { data, error } = await admin
      .from('companies')
      .insert({
        legal_name: lookup.display_name,
        display_name: lookup.display_name,
        company_type: companyType,
        mc_number: mc,
        corporate_email: lookup.corporate_email,
        is_placeholder: true,
      })
      .select()
      .single()
    if (error || !data) {
      return NextResponse.json({ error: 'Could not create the company profile.' }, { status: 500 })
    }
    company = data as Company
    await admin.from('company_verification_records').insert({
      company_id: company.id,
      source: 'local',
      source_data: { mc, note: 'No public-record match; FMCSA lookup connects with the backend.' },
      match_score: 'none',
      status: 'no_match',
    })
  }
  if (company.locked) {
    return NextResponse.json({ error: 'This company profile is locked. Contact support.' }, { status: 423 })
  }

  // Where the request goes (§17 priority): existing company admin → the
  // corporate email on file → manual queue. The demo email is what the FMCSA
  // lookup will replace.
  const hasAdmin = await companyHasAdmin(company.id)
  const sentTo = company.corporate_email
  const method: VerificationMethod = hasAdmin
    ? 'company_admin_approval'
    : sentTo
      ? 'corporate_email_approval'
      : 'manual_review'
  const userDomain = emailDomain(user.email)
  const domainMatch =
    !!userDomain && !isFreeMailDomain(user.email) && userDomain === websiteDomain(company.website)

  // Article §19 triggers worth recording for the admin queue.
  const reviewReasons: string[] = []
  if (lookup.source === 'demo') reviewReasons.push('no_fmcsa_match')
  if (isFreeMailDomain(user.email)) reviewReasons.push('free_mail_user')
  if (!sentTo && !hasAdmin) reviewReasons.push('no_corporate_email')

  const { data: claim, error: claimError } = await admin
    .from('company_claims')
    .insert({
      user_id: user.id,
      company_id: company.id,
      requested_role: 'broker_agent',
      claim_status: method === 'manual_review' ? 'admin_review_required' : 'pending_corporate_approval',
      verification_method: domainMatch && method === 'corporate_email_approval' ? 'corporate_email_domain' : method,
      mc_number: mc,
      approval_sent_to: hasAdmin ? 'company admin' : sentTo,
      review_reason: reviewReasons.length > 0 ? reviewReasons.join(',') : null,
    })
    .select()
    .single()
  if (claimError || !claim) {
    return NextResponse.json({ error: 'Could not create the request.' }, { status: 500 })
  }
  await admin.from('company_memberships').upsert(
    { user_id: user.id, company_id: company.id, role: 'broker_agent', status: 'pending' },
    { onConflict: 'user_id,company_id' },
  )
  await logCompanyEvent({
    companyId: company.id,
    actorUserId: user.id,
    actorLabel,
    eventType: 'claim_created',
    newValue: { claim_id: claim.id, mc, method, sent_to: claim.approval_sent_to, lookup: lookup.source },
    ip,
  })

  // TODO(backend): send the `company_access_approval` template to
  // `approval_sent_to` with the approve / deny / report links.
  return NextResponse.json({
    ok: true,
    claim,
    company: { id: company.id, display_name: company.display_name, company_type: company.company_type },
    lookup: lookup.source,
    sent_to: claim.approval_sent_to,
    has_admin: hasAdmin,
  })
}

async function withdraw(claim: CompanyClaim, actorLabel: string, userId: string, ip: string | null) {
  const admin = createAdminClient()
  const now = new Date().toISOString()
  await admin
    .from('company_claims')
    .update({ claim_status: 'revoked', decided_at: now, updated_at: now })
    .eq('id', claim.id)
  await admin
    .from('company_memberships')
    .update({ status: 'revoked', revoked_by: actorLabel, revoked_at: now })
    .eq('user_id', claim.user_id)
    .eq('company_id', claim.company_id)
    .eq('status', 'pending')
  await logCompanyEvent({
    companyId: claim.company_id,
    actorUserId: userId,
    actorLabel,
    eventType: 'claim_withdrawn',
    newValue: { claim_id: claim.id },
    ip,
  })
}

function randomToken(): string {
  const bytes = new Uint8Array(24)
  crypto.getRandomValues(bytes)
  return [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('')
}
