'use server'

import { redirect } from 'next/navigation'
import { z } from 'zod'
import { getSessionUser } from '@/lib/auth'
import { createAdminClient } from '@/lib/supabase/admin'
import { evaluateParticipantRules, type ResolverSession } from '@/lib/data/auto-participants'
import type { TripParticipant } from '@/types/db'
import { generateRefCode } from '@/lib/domain/refcode'
import { logTripEvent } from '@/lib/audit'
import { touchContactsForInvites } from '@/lib/data/contacts'
import { emailPattern } from '@/lib/like'
import type { TripRole, UserRole } from '@/types/db'
import { extractRateConfirmation } from '@/lib/adapters/flask'
import { processWorkspacePermit } from '@/lib/data/permit-processing'
import { sendInitialSynchronRequest } from '@/lib/data/permit-request-email'
import { syncTripToHha } from '@/lib/integrations/hha-trip-sync'
import { SITE_URL } from '@/lib/app-url'
import { deleteS3Object, S3_PREFIX, uploadFileToS3 } from '@/lib/storage/s3'
import { WIZARD_FILE_LIMIT_BYTES, WIZARD_FILE_LIMIT_LABEL, fileTooLargeMessage } from '@/lib/domain/upload-limits'

const schema = z.object({
  origin: z.string().trim().min(1, 'Origin is required'),
  destination: z.string().trim().min(1, 'Destination is required'),
  commodity: z.string().trim().min(1, 'Commodity is required'),
  carrier_name: z.string().trim().optional().or(z.literal('')),
  unit_number: z.string().trim().optional().or(z.literal('')),
  pickup_date: z.string().optional().or(z.literal('')),
  delivery_date: z.string().optional().or(z.literal('')),
  notes: z.string().trim().max(2000).optional().or(z.literal('')),
})

const ROLE_MAP: Record<UserRole, TripRole> = {
  broker: 'broker',
  dispatcher: 'dispatcher',
  driver: 'driver',
  admin: 'admin',
}

export async function createTrip(_prev: { error?: string } | undefined, formData: FormData) {
  const user = await getSessionUser()
  if (!user) redirect('/login')

  const parsed = schema.safeParse(Object.fromEntries(formData))
  if (!parsed.success) {
    return { error: parsed.error.issues[0]?.message ?? 'Invalid input' }
  }
  const input = parsed.data
  const myTripRole = ROLE_MAP[user.role]

  const admin = createAdminClient()
  const { data: trip, error } = await admin
    .from('trips')
    .insert({
      ref_code: generateRefCode(),
      created_by: user.id,
      status: 'waiting_for_permits',
      permit_source: 'upload',
      origin: input.origin,
      destination: input.destination,
      commodity: input.commodity,
      carrier_name: input.carrier_name || (user.company ?? ''),
      unit_number: input.unit_number || null,
      pickup_date: input.pickup_date || null,
      delivery_date: input.delivery_date || null,
      notes: input.notes || null,
    })
    .select()
    .single()
  if (error || !trip) return { error: 'Could not create the trip.' }

  await admin.from('trip_participants').insert({
    trip_id: trip.id,
    user_id: user.id,
    email: user.email,
    name: user.name,
    role: myTripRole,
    status: 'active',
  })

  await logTripEvent({
    tripId: trip.id,
    actorId: user.id,
    actorLabel: user.name || user.email,
    action: 'trip_created',
    detail: { origin: input.origin, destination: input.destination },
  })

  redirect(`/trips/${trip.id}`)
}

/* ============================================================
 * Carrier "Create Trip" wizard (Task 17): rate con first, editable
 * extracted fields, unit number, permits (upload or order from our
 * trusted partner Synchron Permits), then invite people.
 * ============================================================ */

const optionalNumber = z
  .union([z.number(), z.string()])
  .optional()
  .or(z.literal(''))
  .transform((v) => {
    if (v === undefined || v === '' || v === null) return null
    const n = typeof v === 'number' ? v : Number(String(v).replace(/[^\d.-]/g, ''))
    return Number.isFinite(n) && n >= 0 ? n : null
  })

const wizardSchema = z.object({
  origin: z.string().trim().min(1, 'Origin is required'),
  destination: z.string().trim().min(1, 'Destination is required'),
  commodity: z.string().trim().min(1, 'Commodity is required'),
  // Confirmed on step 2 from the rate confirmation (Nash, 2026-10-01, W2).
  carrier_name: z.string().trim().max(200).optional().or(z.literal('')),
  load_length_in: optionalNumber,
  load_width_in: optionalNumber,
  load_height_in: optionalNumber,
  load_weight_lbs: optionalNumber,
  unit_number: z.string().trim().optional().or(z.literal('')),
  pickup_date: z.string().optional().or(z.literal('')),
  delivery_date: z.string().optional().or(z.literal('')),
  permit_path: z.enum(['upload', 'synchron']),
  invites: z
    .array(
      z.object({
        role: z.enum(['driver', 'broker']),
        name: z.string().trim().min(1),
        email: z.string().trim().email(),
        // Nash: phone is required on invites; the extension stays optional.
        phone: z.string().trim().min(5).max(40),
        phone_ext: z.string().trim().max(20).optional().or(z.literal('')),
      }),
    )
    .max(20),
})

// Per-file limit for the wizard: 1 MB (decision W4, 2026-10-01). The same
// rule the browser applies, so the server never silently accepts more.
const MAX_FILE = WIZARD_FILE_LIMIT_BYTES

const resolverSession = (u: { id: string; originId: string; previewWorkspace?: string | null; contextKey?: string | null }): ResolverSession =>
  ({ id: u.id, originId: u.originId, preview: u.previewWorkspace, contextKey: u.contextKey })

export async function createCarrierTrip(
  _prev: { error?: string } | undefined,
  formData: FormData,
) {
  const user = await getSessionUser()
  if (!user) redirect('/login')

  let invites: unknown = []
  try {
    invites = JSON.parse(String(formData.get('invites') ?? '[]'))
  } catch {
    return { error: 'Invalid invite list.' }
  }
  const parsed = wizardSchema.safeParse({
    origin: formData.get('origin'),
    destination: formData.get('destination'),
    commodity: formData.get('commodity'),
    unit_number: formData.get('unit_number') ?? '',
    pickup_date: formData.get('pickup_date') ?? '',
    delivery_date: formData.get('delivery_date') ?? '',
    permit_path: formData.get('permit_path'),
    carrier_name: formData.get('carrier_name') ?? '',
    load_length_in: formData.get('load_length_in') ?? '',
    load_width_in: formData.get('load_width_in') ?? '',
    load_height_in: formData.get('load_height_in') ?? '',
    load_weight_lbs: formData.get('load_weight_lbs') ?? '',
    invites,
  })
  if (!parsed.success) {
    return { error: parsed.error.issues[0]?.message ?? 'Invalid input' }
  }
  const input = parsed.data

  const rateCon = formData.get('rate_confirmation')
  if (!(rateCon instanceof File) || rateCon.size === 0) {
    return { error: 'The rate confirmation file is required.' }
  }
  if (rateCon.size > MAX_FILE) return { error: fileTooLargeMessage(rateCon.name, rateCon.size) }
  const allPermits = formData.getAll('permits').filter((f): f is File => f instanceof File && f.size > 0)
  const tooLarge = allPermits.find((f) => f.size > MAX_FILE)
  if (tooLarge) return { error: `${fileTooLargeMessage(tooLarge.name, tooLarge.size)} (limit ${WIZARD_FILE_LIMIT_LABEL} per file)` }
  const permitFiles = allPermits

  const admin = createAdminClient()
  const { data: trip, error } = await admin
    .from('trips')
    .insert({
      ref_code: generateRefCode(),
      created_by: user.id,
      status: 'waiting_for_permits',
      permit_source: input.permit_path,
      origin: input.origin,
      destination: input.destination,
      commodity: input.commodity,
      carrier_name: input.carrier_name || user.company || user.name,
      unit_number: input.unit_number || null,
      pickup_date: input.pickup_date || null,
      delivery_date: input.delivery_date || null,
      load_length_in: input.load_length_in,
      load_width_in: input.load_width_in,
      load_height_in: input.load_height_in,
      load_weight_lbs: input.load_weight_lbs,
    })
    .select()
    .single()
  if (error || !trip) return { error: 'Could not create the trip.' }

  // creator joins as dispatcher/carrier side
  const creatorRow = {
    trip_id: trip.id,
    user_id: user.id,
    email: user.email,
    name: user.name,
    role: ROLE_MAP[user.role],
    status: 'active',
    addition_method: 'trip_creator',
  }
  let creator = await admin.from('trip_participants').insert(creatorRow).select('*').maybeSingle()
  if (creator.error?.message.includes('addition_method')) {
    const { addition_method: _m, ...legacy } = creatorRow
    void _m
    creator = await admin.from('trip_participants').insert(legacy).select('*').maybeSingle()
  }
  // Auto-Participants (2026-09-30): the creator's rules fire in the mode they created the trip in.
  if (creator.data) {
    await evaluateParticipantRules({ tripId: trip.id, participant: creator.data as TripParticipant, trigger: 'participation', session: resolverSession(user) })
  }

  // store files
  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 carrier 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,
        uploaded_by: user.id,
        uploader_label: user.name || user.email,
      })
      .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) {
    await extractRateConfirmation(rateCon, rateCon.name, {
      tripId: trip.id,
      documentId: rateConStored.doc.id,
      sourceUrl: rateConStored.stored.url,
    })
  }
  const queuedPermits: Array<{ permitId: string; file: File }> = []
  for (const file of permitFiles) {
    const permitStored = await storeFile(file, 'permit')
    if (permitStored) {
      const { data: permit, error: permitError } = await admin.from('permits').insert({
        trip_id: trip.id, document_id: permitStored.doc.id,
        state_code: '', extraction_status: 'pending',
      }).select('id').single()
      if (permitError || !permit) {
        console.error('Carrier permit could not be queued', { trip_id: trip.id, document_id: permitStored.doc.id })
      } else queuedPermits.push({ permitId: permit.id, file })
    }
  }

  // Synchron path: one email opens one provider order for this trip. Additional
  // state requests reuse the same Workspace reference.
  if (input.permit_path === 'synchron') {
    await admin.from('service_requests').insert({
      trip_id: trip.id,
      type: 'permit_request',
      state_code: null,
      notes: 'Permit order placed at trip creation.',
      requested_by: user.id,
      requester_label: user.name || user.email,
    })
    const sync = await syncTripToHha(trip.id)
    if (!sync.ok) console.error('Carrier trip sync failed:', sync.error)
    if (rateConStored) {
      const email = await sendInitialSynchronRequest({
        trip, rateCon, rateConDocumentId: rateConStored.doc.id,
        brokerName: user.name || user.email, brokerCompany: user.company || '',
        dispatcherName: user.name || user.email, dispatcherEmail: user.email,
        dispatcherPhone: '', payer: null,
        origin: process.env.NEXT_PUBLIC_APP_URL?.replace(/\/+$/, '') || SITE_URL,
      })
      if (email.status !== 'sent') console.error('Synchron order request email:', email.reason || email.status)
    }
  }

  // Step 2 invites: auto-attach existing users by email; others get an
  // invitation token. TODO(backend): welcome email with password-reset link
  // for brand-new people ("three months later he gets the same reset prompt").
  for (const inv of input.invites) {
    const { data: existing } = await admin
      .from('profiles')
      .select('id')
      .ilike('email', emailPattern(inv.email))
      .maybeSingle()
    const inviteRow = {
      trip_id: trip.id,
      user_id: existing?.id ?? null,
      email: inv.email.toLowerCase(),
      name: inv.name,
      phone: inv.phone,
      role: inv.role,
      status: existing ? 'active' : 'invited',
      invited_by: user.id,
      addition_method: 'manual_invite',
    }
    let { data: participant } = await admin
      .from('trip_participants')
      .insert({ ...inviteRow, phone_ext: inv.phone_ext || null })
      .select()
      .single()
    if (!participant) {
      // Graceful until migration 0007 adds phone_ext / 0049 adds provenance: never lose the invitee.
      const { addition_method: _m, ...legacy } = inviteRow
      void _m
      const retry = await admin.from('trip_participants').insert(legacy).select().single()
      participant = retry.data
    }
    if (participant?.user_id) {
      await evaluateParticipantRules({ tripId: trip.id, participant: participant as TripParticipant, trigger: 'participation', session: resolverSession(user) })
    }
    await admin.from('trip_invitations').insert({
      trip_id: trip.id,
      participant_id: participant?.id ?? null,
      email: inv.email.toLowerCase(),
      role: inv.role,
      invited_by: user.id,
    })
    await logTripEvent({
      tripId: trip.id,
      actorId: user.id,
      actorLabel: user.name || user.email,
      action: 'participant_invited',
      detail: { email: inv.email, role: inv.role, auto_attached: !!existing },
    })
  }

  // Keep the people a dispatcher actually runs at the top of the picker
  // (Task 99). Only updates contacts that already exist — whether a trip
  // invite should also SAVE someone is still Nash's call (Q6).
  await touchContactsForInvites(user.id, input.invites)

  // Process after attaching recipients, using the same queue as imports/uploads.
  for (const queued of queuedPermits) {
    await processWorkspacePermit({ ...queued, origin: process.env.NEXT_PUBLIC_APP_URL?.replace(/\/+$/, '') || SITE_URL })
  }

  await logTripEvent({
    tripId: trip.id,
    actorId: user.id,
    actorLabel: user.name || user.email,
    action: 'trip_created',
    detail: { origin: input.origin, destination: input.destination, via: 'carrier_wizard' },
  })

  redirect(`/trips/${trip.id}`)
}
