import 'server-only'

import { createAdminClient } from '@/lib/supabase/admin'
import { logTripEvent } from '@/lib/audit'
import { ACCESS_STATUSES } from '@/lib/domain/participants'
import { resolveCarrier, type CarrierResolution, type CarrierSummary } from '@/lib/domain/carrier-assignment'
import type { Company, Trip, TripParticipant } from '@/types/db'

/**
 * The carrier on a trip — reading it, attaching it by default, and letting
 * the dispatcher choose (Nash, 2026-09-19). Rules in
 * src/lib/domain/carrier-assignment.ts; this is the I/O.
 */

const summary = (c: Pick<Company, 'id' | 'display_name' | 'legal_name' | 'mc_number' | 'dot_number'>): CarrierSummary => ({
  id: c.id,
  name: c.display_name || c.legal_name,
  mc: c.mc_number,
  dot: c.dot_number,
})

/** Carrier companies each user is an approved member of. Keyed by user id. */
export async function carrierCompaniesForUsers(userIds: string[]): Promise<Map<string, CarrierSummary[]>> {
  const out = new Map<string, CarrierSummary[]>()
  const ids = [...new Set(userIds.filter(Boolean))]
  if (ids.length === 0) return out
  const admin = createAdminClient()
  const { data: memberships } = await admin
    .from('company_memberships')
    .select('user_id, company_id')
    .in('user_id', ids)
    .eq('status', 'approved')
  const rows = (memberships ?? []) as { user_id: string; company_id: string }[]
  if (rows.length === 0) return out
  const { data: companies } = await admin
    .from('companies')
    .select('id, display_name, legal_name, mc_number, dot_number, company_type, merged_into')
    .in('id', [...new Set(rows.map((r) => r.company_id))])
    .eq('company_type', 'carrier')
    .is('merged_into', null)
  const byId = new Map(((companies ?? []) as Company[]).map((c) => [c.id, summary(c)]))
  for (const r of rows) {
    const c = byId.get(r.company_id)
    if (!c) continue
    const list = out.get(r.user_id) ?? []
    list.push(c)
    out.set(r.user_id, list)
  }
  return out
}

/** Every carrier company on the platform — the admin's option list. */
export async function allCarrierCompanies(): Promise<CarrierSummary[]> {
  const admin = createAdminClient()
  const { data } = await admin
    .from('companies')
    .select('id, display_name, legal_name, mc_number, dot_number')
    .eq('company_type', 'carrier')
    .is('merged_into', null)
    .order('display_name')
  return ((data ?? []) as Company[]).map(summary)
}

async function loadAssigned(companyId: string | null | undefined): Promise<CarrierSummary | null> {
  if (!companyId) return null
  const admin = createAdminClient()
  const { data } = await admin
    .from('companies')
    .select('id, display_name, legal_name, mc_number, dot_number')
    .eq('id', companyId)
    .maybeSingle()
  return data ? summary(data as Company) : null
}

/**
 * Write the assignment. Idempotent on the same company; never overwrites a
 * DIFFERENT company silently unless `force` (the dispatcher choosing again).
 */
export async function assignTripCarrier(params: {
  tripId: string
  carrier: CarrierSummary
  actorId: string | null
  actorLabel: string
  method: 'auto' | 'selected'
  force?: boolean
}): Promise<void> {
  const admin = createAdminClient()
  let q = admin
    .from('trips')
    .update({ carrier_company_id: params.carrier.id, carrier_name: params.carrier.name })
    .eq('id', params.tripId)
  if (!params.force) q = q.is('carrier_company_id', null)
  const { data, error } = await q.select('id')
  if (error) throw new Error(`assign carrier: ${error.message}`)
  if (!data || data.length === 0) return // already assigned (not forced) — nothing changed, nothing logged
  await logTripEvent({
    tripId: params.tripId,
    actorId: params.actorId,
    actorLabel: params.actorLabel,
    action: 'carrier_assigned',
    detail: { company_id: params.carrier.id, name: params.carrier.name, mc: params.carrier.mc, dot: params.carrier.dot, method: params.method },
  })
}

/**
 * The carrier as the workspace should show it. When the trip has none and
 * its dispatcher relates to exactly one carrier, that carrier is attached
 * right here — "by default we're going to pull that relation" — so the next
 * read finds it `assigned`. Idempotent: the update only lands on a null.
 */
export async function loadTripCarrier(
  trip: Pick<Trip, 'id' | 'carrier_company_id' | 'carrier_name'>,
  participants: Pick<TripParticipant, 'role' | 'status' | 'user_id'>[],
  viewer: { id: string; role: string; label: string },
): Promise<{ resolution: CarrierResolution; adminOptions: CarrierSummary[] }> {
  const assigned = await loadAssigned(trip.carrier_company_id)
  const dispatcherIds = participants
    .filter((p) => p.role === 'dispatcher' && (ACCESS_STATUSES as readonly string[]).includes(p.status) && p.user_id)
    .map((p) => p.user_id as string)
  const relations = await carrierCompaniesForUsers(dispatcherIds)
  const resolution = resolveCarrier({
    assigned,
    hasDispatcherAccount: dispatcherIds.length > 0,
    dispatcherCarriers: dispatcherIds.flatMap((id) => relations.get(id) ?? []),
    tripCarrierName: trip.carrier_name,
  })
  if (resolution.kind === 'auto') {
    await assignTripCarrier({ tripId: trip.id, carrier: resolution.carrier, actorId: null, actorLabel: 'HeavyHaul Agent', method: 'auto' })
  }
  const adminOptions = viewer.role === 'admin' && resolution.kind !== 'assigned' ? await allCarrierCompanies() : []
  return { resolution, adminOptions }
}

/** The carrier relations the viewer may choose from for a trip (dispatcher: their own; admin: all). */
export async function carrierOptionsForViewer(viewer: { id: string; role: string }, participantRole: string | null): Promise<CarrierSummary[]> {
  if (viewer.role === 'admin') return allCarrierCompanies()
  if (participantRole !== 'dispatcher') return []
  return (await carrierCompaniesForUsers([viewer.id])).get(viewer.id) ?? []
}
