import { processWorkspacePermit } from '@/lib/data/permit-processing'
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createAdminClient } from '@/lib/supabase/admin'
import { publicOrigin } from '@/lib/app-url'
import { extractRateConfirmation } from '@/lib/adapters/flask'
import { generateRefCode } from '@/lib/domain/refcode'
import { logTripEvent } from '@/lib/audit'
import { sendInitialSynchronRequest } from '@/lib/data/permit-request-email'
import type { PermitSource } from '@/types/db'
import { syncTripToHha } from '@/lib/integrations/hha-trip-sync'
import { deleteS3Object, S3_PREFIX, uploadFileToS3 } from '@/lib/storage/s3'
import { getSessionUser } from '@/lib/auth'
import { emailPattern } from '@/lib/like'
import { evaluateParticipantRules } from '@/lib/data/auto-participants'
import type { TripParticipant } from '@/types/db'
import { finalizeIntakeSubmission, guardIntakeSubmission } from '@/lib/data/intake-guard'
import { clientIp } from '@/lib/security/rate-limit'

/**
 * Public broker-intake submission. The page is public; COMPLETING a submission
 * needs a verified email (2026-09-22, §36–§38) — the guard in
 * src/lib/data/intake-guard.ts enforces sign-in, blocks, the broker's intake
 * policy, quotas, IP/page velocity, bot check, honeypot, file type by content,
 * size limits, hashing / duplicates and a risk score, and records every
 * attempt in intake_submissions before anything is stored.
 * Creates: intake_submission → trip → participants (broker owner + dispatcher)
 * → invitation → documents (+ permits & warnings when the upload path is used,
 * or a Synchron permit request when the purchase path is used).
 */

export const runtime = 'nodejs'
export const maxDuration = 300

const MAX_FILE_BYTES = 25 * 1024 * 1024
const ALLOWED_MIME = new Set([
  'application/pdf',
  'image/jpeg',
  'image/png',
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
])

const fieldsSchema = z.object({
  slug: z.string().min(1),
  carrier_company: z.string().trim().min(1).max(200),
  contact_name: z.string().trim().min(1).max(200),
  contact_email: z.string().trim().email().max(320),
  contact_phone: z.string().trim().min(5).max(40),
  contact_phone_ext: z.string().trim().max(20).optional().or(z.literal('')),
  notes: z.string().trim().max(2000).optional().or(z.literal('')),
  permit_source: z.enum(['upload', 'synchron']),
  // Broker's decision when creating a trip request (Task 15):
  // force Synchron purchase or allow the carrier's own permits.
  permit_policy: z.enum(['synchron_required', 'upload_allowed']).optional(),
  // Synchron flow only: who pays Synchron for permit processing. Required
  // with permit_policy=synchron_required (checked below, not in the schema,
  // so the public carrier intake stays unaffected).
  payment_responsible_party: z.enum(['broker', 'carrier']).optional(),
})

// naive in-memory rate limit (per instance) — replaced by edge limits later
const recent = new Map<string, number[]>()
function rateLimited(key: string): boolean {
  const now = Date.now()
  const hits = (recent.get(key) ?? []).filter((t) => now - t < 60_000)
  hits.push(now)
  recent.set(key, hits)
  return hits.length > 10
}

function validFile(f: File): boolean {
  return f.size > 0 && f.size <= MAX_FILE_BYTES && ALLOWED_MIME.has(f.type)
}

export async function POST(req: NextRequest) {
  const ip = clientIp(req.headers)
  if (rateLimited(ip)) {
    return NextResponse.json({ error: 'Too many submissions. Try again shortly.' }, { status: 429 })
  }
  const user = await getSessionUser()

  const form = await req.formData()
  const parsed = fieldsSchema.safeParse({
    slug: form.get('slug'),
    permit_policy: form.get('permit_policy') || undefined,
    payment_responsible_party: form.get('payment_responsible_party') || undefined,
    carrier_company: form.get('carrier_company'),
    contact_name: form.get('contact_name'),
    contact_email: form.get('contact_email'),
    contact_phone: form.get('contact_phone'),
    contact_phone_ext: form.get('contact_phone_ext') ?? '',
    notes: form.get('notes') ?? '',
    permit_source: form.get('permit_source'),
  })
  if (!parsed.success) {
    return NextResponse.json(
      { error: parsed.error.issues[0]?.message ?? 'Invalid submission' },
      { status: 400 },
    )
  }
  const input = parsed.data
  const honeypotFilled = !!form.get('website')

  const rateCon = form.get('rate_confirmation')
  if (!(rateCon instanceof File) || !validFile(rateCon)) {
    return NextResponse.json(
      { error: 'A rate confirmation file (PDF/JPG/PNG/DOCX, max 25MB) is required.' },
      { status: 400 },
    )
  }
  const permitFiles = form
    .getAll('permits')
    .filter((f): f is File => f instanceof File && validFile(f))

  const admin = createAdminClient()

  // 1. broker page
  const { data: page } = await admin
    .from('broker_pages')
    .select('*')
    .eq('slug', input.slug)
    .eq('enabled', true)
    .maybeSingle()
  if (!page) {
    return NextResponse.json({ error: 'This intake page is not available.' }, { status: 404 })
  }

  // 2. the guard (§36): sign-in, policy, quotas, bot check, honeypot, files, duplicates, risk — all here, never in the browser.
  const guard = await guardIntakeSubmission({
    page, user, ip, userAgent: req.headers.get('user-agent'), honeypotFilled,
    botToken: String(form.get('bot_token') ?? '') || null, invitationToken: String(form.get('invite') ?? '') || null,
    files: [rateCon, ...permitFiles],
    fields: { carrier_company: input.carrier_company, contact_name: input.contact_name, contact_email: input.contact_email, contact_phone: input.contact_phone, permit_source: input.permit_source, notes: input.notes || null },
  })
  if (!guard.ok) {
    if (guard.code === 'honeypot') return NextResponse.json({ ok: true })
    return NextResponse.json({ error: guard.error, code: guard.code }, { status: guard.status })
  }
  if (guard.review) {
    // Medium risk (§26): keep the submission, do not run the expensive pipeline; a moderator releases it.
    return NextResponse.json({ ok: true, review: true, message: 'Your submission was received and is being reviewed. You will hear back by email.' }, { status: 202 })
  }
  const { data: owner } = await admin
    .from('profiles')
    .select('*')
    .eq('id', page.owner_id)
    .single()

  // The broker cannot submit a Synchron purchase request without choosing
  // who pays (source doc §8).
  if (input.permit_policy === 'synchron_required' && !input.payment_responsible_party) {
    return NextResponse.json(
      { error: 'Choose who will pay Synchron Permits for this permit order.' },
      { status: 400 },
    )
  }

  // 3. create the trip
  const permitSource = input.permit_source as PermitSource
  const { data: trip, error: tripError } = await admin
    .from('trips')
    .insert({
      ref_code: generateRefCode(),
      created_by: null,
      broker_page_id: page.id,
      status: 'waiting_for_permits',
      permit_source: permitSource,
      permit_policy: input.permit_policy ?? null,
      // Only sent when chosen — keeps rows working until migration 0008 runs.
      ...(input.payment_responsible_party
        ? { payment_responsible_party: input.payment_responsible_party }
        : {}),
      carrier_name: input.carrier_company,
      origin: '',
      destination: '',
      commodity: '',
      load_length_in: null,
      load_width_in: null,
      load_height_in: null,
      load_weight_lbs: null,
      pickup_date: null,
      delivery_date: null,
      notes: input.notes || null,
    })
    .select()
    .single()
  if (tripError || !trip) {
    return NextResponse.json({ error: 'Could not create the trip. Try again.' }, { status: 500 })
  }

  // Everyone on the trip sees who was chosen to pay Synchron (source doc §24).
  if (input.payment_responsible_party) {
    await logTripEvent({
      tripId: trip.id,
      actorId: page.owner_id,
      actorLabel: owner?.full_name ?? page.display_name,
      action: 'payment_responsibility_selected',
      detail: { party: input.payment_responsible_party },
    })
  }

  // 4. intake submission record — created by the guard; now attached to the trip
  await finalizeIntakeSubmission(guard.submissionId, { trip_id: trip.id, status: 'accepted', processing_status: 'processing' })

  // 5. participants: broker owner (active) + dispatcher contact (invited)
  const ownerRow = {
    trip_id: trip.id,
    user_id: page.owner_id,
    email: owner?.email ?? '',
    name: owner?.full_name ?? page.display_name,
    role: 'broker',
    status: 'active',
    addition_method: 'intake',
  }
  let ownerParticipant = await admin.from('trip_participants').insert(ownerRow).select('*').maybeSingle()
  if (ownerParticipant.error?.message.includes('addition_method')) {
    const { addition_method: _m, ...legacy } = ownerRow
    void _m
    ownerParticipant = await admin.from('trip_participants').insert(legacy).select('*').maybeSingle()
  }
  // The dispatcher contact is linked at once when the email already has an account (decision D12).
  const { data: contactProfile } = await admin.from('profiles').select('id').ilike('email', emailPattern(input.contact_email)).maybeSingle()
  const dispatcherRow = {
    trip_id: trip.id,
    user_id: (contactProfile as { id: string } | null)?.id ?? null,
    email: input.contact_email,
    name: input.contact_name,
    phone: input.contact_phone,
    role: 'dispatcher',
    status: 'invited',
    addition_method: 'intake',
  }
  let { data: dispatcherParticipant } = await admin
    .from('trip_participants')
    .insert({ ...dispatcherRow, phone_ext: input.contact_phone_ext || null })
    .select()
    .single()
  if (!dispatcherParticipant) {
    // Graceful until migration 0007 adds phone_ext / 0049 adds provenance: never lose the dispatcher.
    const { addition_method: _m, ...legacy } = dispatcherRow
    void _m
    const retry = await admin.from('trip_participants').insert(legacy).select().single()
    dispatcherParticipant = retry.data
  }

  // 6. invitation token for the dispatcher
  const { data: invitation } = await admin
    .from('trip_invitations')
    .insert({
      trip_id: trip.id,
      participant_id: dispatcherParticipant?.id ?? null,
      email: input.contact_email,
      role: 'dispatcher',
    })
    .select()
    .single()

  // 7. store the rate confirmation
  const storeFile = async (file: File, kind: string) => {
    const prefix = kind === 'rate_confirmation'
      ? S3_PREFIX.rateConfirmation
      : kind === 'permit'
        ? S3_PREFIX.permit
        : S3_PREFIX.tripSupporting
    let stored
    try {
      stored = await uploadFileToS3(file, file.name, prefix)
    } catch (error) {
      console.error('S3 intake upload failed:', error instanceof Error ? error.message : error)
      return null
    }
    const { data: doc, error: docError } = await admin
      .from('documents')
      .insert({
        trip_id: trip.id,
        kind,
        storage_provider: 's3',
        storage_path: stored.key,
        external_url: stored.url,
        file_name: file.name,
        mime_type: stored.contentType,
        size_bytes: file.size,
        uploader_label: `${input.contact_name} (intake)`,
      })
      .select()
      .single()
    if (docError || !doc) {
      await deleteS3Object(stored.key).catch(() => undefined)
      return null
    }
    return { doc, stored }
  }

  const rateConStored = await storeFile(rateCon, 'rate_confirmation')
  if (!rateConStored) {
    return NextResponse.json({ error: 'Could not store the rate confirmation.' }, { status: 500 })
  }
  const extraction = await extractRateConfirmation(rateCon, rateCon.name, {
    tripId: trip.id,
    documentId: rateConStored.doc.id,
    sourceUrl: rateConStored.stored.url,
  })
  const rc = extraction.ok ? extraction.data : undefined
  const extractedTripFields = rc ? {
    origin: rc.origin ?? '',
    destination: rc.destination ?? '',
    commodity: rc.commodity ?? '',
    carrier_name: rc.carrier_name ?? input.carrier_company,
    load_length_in: rc.length_in ?? null,
    load_width_in: rc.width_in ?? null,
    load_height_in: rc.height_in ?? null,
    load_weight_lbs: rc.weight_lbs ?? null,
    pickup_date: rc.pickup_date ?? null,
    delivery_date: rc.delivery_date ?? null,
  } : {}
  if (Object.keys(extractedTripFields).length > 0) {
    await admin.from('trips').update(extractedTripFields).eq('id', trip.id)
  }

  // 8. permit path
  if (permitSource === 'upload') {
    for (const file of permitFiles) {
      const storedPermit = await storeFile(file, 'permit')
      if (!storedPermit) continue
      const { doc } = storedPermit
      const { data: permit, error: permitError } = await admin.from('permits').insert({
        trip_id: trip.id, document_id: doc.id, state_code: '', extraction_status: 'pending',
      }).select('id').single()
      if (permitError || !permit) {
        console.error('Intake permit could not be queued', { trip_id: trip.id, document_id: doc.id })
        continue
      }
      await processWorkspacePermit({ permitId: permit.id, file, origin: publicOrigin(req) })
    }
    // first processed permit activates the trip
    const { count } = await admin
      .from('permits')
      .select('id', { count: 'exact', head: true })
      .eq('trip_id', trip.id)
      .eq('extraction_status', 'processed')
    if ((count ?? 0) > 0) {
      await admin.from('trips').update({ status: 'active' }).eq('id', trip.id)
    }
  } else {
    // One Workspace trip becomes one Synchron order with state items underneath.
    await admin
      .from('service_requests')
      .insert({
        trip_id: trip.id,
        type: 'permit_request',
        notes: input.notes || null,
        requester_label: `${input.contact_name} (intake)`,
      })
  }

  // 9. audit
  await admin.from('trip_events').insert([
    {
      trip_id: trip.id,
      actor_label: `${input.contact_name} (public intake)`,
      action: 'trip_created_via_intake',
      detail: { broker_page: page.slug, permit_source: permitSource, source_ip: ip },
    },
    {
      trip_id: trip.id,
      actor_label: 'system',
      action: 'dispatcher_invited',
      detail: { email: input.contact_email },
    },
  ])

  await finalizeIntakeSubmission(guard.submissionId, { processing_status: 'done' })
  if (user && user.email.toLowerCase() === input.contact_email.toLowerCase()) {
    await admin.from('trip_participants').update({ user_id: user.id, status: 'active' }).eq('trip_id', trip.id).eq('role', 'dispatcher').ilike('email', input.contact_email)
  }
  // Auto-Participants (2026-09-30): the broker who owns the page, and the dispatcher when they already have an account.
  if (ownerParticipant.data) {
    await evaluateParticipantRules({ tripId: trip.id, participant: ownerParticipant.data as TripParticipant, trigger: 'participation', session: null })
  }
  if (dispatcherParticipant?.user_id || (user && user.email.toLowerCase() === input.contact_email.toLowerCase())) {
    const { data: dispatcherNow } = await admin.from('trip_participants').select('*').eq('trip_id', trip.id).eq('role', 'dispatcher').ilike('email', emailPattern(input.contact_email)).maybeSingle()
    if (dispatcherNow) {
      const self = user && user.email.toLowerCase() === input.contact_email.toLowerCase()
      await evaluateParticipantRules({ tripId: trip.id, participant: dispatcherNow as TripParticipant, trigger: 'participation', session: self ? { id: user.id, originId: user.originId, preview: user.previewWorkspace, contextKey: user.contextKey } : null })
    }
  }
  const sync = await syncTripToHha(trip.id)
  if (!sync.ok) console.error('Intake trip sync failed:', sync.error)

  const synchronEmail = permitSource === 'synchron'
    ? await sendInitialSynchronRequest({
        trip: { ...trip, ...extractedTripFields }, rateCon, rateConDocumentId: rateConStored.doc.id,
        brokerName: owner?.full_name ?? page.display_name, brokerCompany: page.display_name,
        dispatcherName: input.contact_name, dispatcherEmail: input.contact_email,
        dispatcherPhone: input.contact_phone, payer: input.payment_responsible_party ?? null,
        origin: publicOrigin(req),
      })
    : null

  const origin = publicOrigin(req)
  return NextResponse.json({
    ok: true,
    trip_ref: trip.ref_code,
    ...(synchronEmail ? { synchron_email_status: synchronEmail.status } : {}),
    invite_url: invitation ? `${origin}/invite/${invitation.token}` : null,
  })
}
