'use server'

import { redirect } from 'next/navigation'
import { z } from 'zod'
import { getSessionUser } from '@/lib/auth'
import { createAdminClient } from '@/lib/supabase/admin'
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'

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 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'),
  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),
})

const MAX_FILE = 25 * 1024 * 1024

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'),
    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 || rateCon.size > MAX_FILE) {
    return { error: 'The rate confirmation file is required (max 25MB).' }
  }
  const permitFiles = formData
    .getAll('permits')
    .filter((f): f is File => f instanceof File && f.size > 0 && f.size <= MAX_FILE)

  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: user.company ?? user.name,
      unit_number: input.unit_number || null,
      pickup_date: input.pickup_date || null,
      delivery_date: input.delivery_date || null,
    })
    .select()
    .single()
  if (error || !trip) return { error: 'Could not create the trip.' }

  // creator joins as dispatcher/carrier side
  await admin.from('trip_participants').insert({
    trip_id: trip.id,
    user_id: user.id,
    email: user.email,
    name: user.name,
    role: ROLE_MAP[user.role],
    status: 'active',
  })

  // store files
  const storeFile = async (file: File, kind: string) => {
    const path = `${trip.id}/${crypto.randomUUID()}-${file.name.replace(/[^\w.\-]/g, '_')}`
    const { error: upErr } = await admin.storage
      .from('trip-documents')
      .upload(path, file, { contentType: file.type })
    if (upErr) return null
    const { data: doc } = await admin
      .from('documents')
      .insert({
        trip_id: trip.id,
        kind,
        storage_path: path,
        file_name: file.name,
        mime_type: file.type,
        size_bytes: file.size,
        uploaded_by: user.id,
        uploader_label: user.name || user.email,
      })
      .select()
      .single()
    return doc
  }

  await storeFile(rateCon, 'rate_confirmation')
  for (const file of permitFiles) {
    const doc = await storeFile(file, 'permit')
    if (doc) {
      // Extraction fills the fields when the AI connects; honest pending until then.
      await admin.from('permits').insert({
        trip_id: trip.id,
        document_id: doc.id,
        state_code: '',
        extraction_status: 'pending',
      })
    }
  }

  // Synchron path: record the permit order (email to Synchron = backend later)
  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,
    })
  }

  // 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,
    }
    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: never lose the invitee.
      const retry = await admin.from('trip_participants').insert(inviteRow).select().single()
      participant = retry.data
    }
    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)

  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}`)
}
