import 'server-only'

import { createAdminClient } from '@/lib/supabase/admin'
import type { SessionUser } from '@/lib/auth'
import { isMissingColumn } from '@/lib/db-compat'
import type { TripCompletion } from '@/lib/domain/status'
import { emailPattern } from '@/lib/like'
import { allPages, inChunks } from '@/lib/data/chunked'
import type { Trip, TripWarning } from '@/types/db'

/**
 * Who can see which trips (2026-09-11).
 *
 * Requirement: "Admins see everything (e.g., all trips across all brokers on
 * the broker dashboard). Brokers, drivers, and dispatchers should only see
 * trips linked to their email or where they are assigned. Guests see
 * nothing."
 *
 *   • admin      → every trip on the platform
 *   • everyone   → a trip only when they have a live participant row on it,
 *                  matched by account id ("assigned") or by email address
 *                  ("linked to their email"). Removed participants see nothing.
 *   • not signed in → nothing: every caller requires a session first.
 *
 * Every way a trip is created adds its creator as a participant (the carrier
 * wizard, the simple form, and the broker intake page, which adds the page
 * owner), so participant rows are the single source of truth. Three older
 * extra paths are gone on purpose: "created it", "owns the intake page it came
 * from" and company-wide visibility (Task 92). Each could show a person a
 * trip they are no longer on — e.g. a creator who was removed — which the
 * requirement rules out.
 */

/** Admins see every trip. Checked on the CURRENT role, so an admin testing as
 *  the driver account through the pilot bar sees exactly that driver's trips. */
export function canSeeAllTrips(user: Pick<SessionUser, 'role'>): boolean {
  return user.role === 'admin'
}

/** Live participant rows for this person — by account id or by exact email. */
async function participantTripIds(user: SessionUser, tripId?: string): Promise<string[]> {
  const admin = createAdminClient()
  let byIdQuery = admin
    .from('trip_participants')
    .select('trip_id')
    .neq('status', 'removed')
    .eq('user_id', user.id)
  // Exact, case-insensitive: `_` and `%` in an address are escaped, so a
  // near-identical email can never inherit someone else's trips.
  let byEmailQuery = admin
    .from('trip_participants')
    .select('trip_id')
    .neq('status', 'removed')
    .ilike('email', emailPattern(user.email))
  if (tripId) {
    byIdQuery = byIdQuery.eq('trip_id', tripId)
    byEmailQuery = byEmailQuery.eq('trip_id', tripId)
  }
  const [byId, byEmail] = await Promise.all([byIdQuery, byEmailQuery])
  const ids = new Set<string>()
  for (const row of byId.data ?? []) ids.add(row.trip_id)
  for (const row of byEmail.data ?? []) ids.add(row.trip_id)
  return [...ids]
}

export async function getVisibleTripIds(user: SessionUser): Promise<string[]> {
  if (canSeeAllTrips(user)) {
    const admin = createAdminClient()
    const rows = await allPages<{ id: string }>((from, to) =>
      admin.from('trips').select('id').order('created_at').range(from, to),
    )
    return rows.map((r) => r.id)
  }
  return participantTripIds(user)
}

export async function canAccessTrip(user: SessionUser, tripId: string): Promise<boolean> {
  if (canSeeAllTrips(user)) return true
  // Checked for THIS trip directly rather than by loading every trip id.
  return (await participantTripIds(user, tripId)).includes(tripId)
}

export async function getVisibleTrips(
  user: SessionUser,
): Promise<{ trips: Trip[]; warnings: TripWarning[] }> {
  const admin = createAdminClient()

  if (canSeeAllTrips(user)) {
    const [trips, warnings] = await Promise.all([
      allPages<Trip>((from, to) =>
        admin.from('trips').select('*').order('updated_at', { ascending: false }).range(from, to),
      ),
      allPages<TripWarning>((from, to) =>
        admin.from('warnings').select('*').eq('resolved', false).order('created_at').range(from, to),
      ),
    ])
    return { trips, warnings }
  }

  const ids = await participantTripIds(user)
  if (ids.length === 0) return { trips: [], warnings: [] }
  const [tripsRes, warningsRes] = await Promise.all([
    inChunks<Trip>(ids, (chunk) => admin.from('trips').select('*').in('id', chunk)),
    inChunks<TripWarning>(ids, (chunk) =>
      admin.from('warnings').select('*').in('trip_id', chunk).eq('resolved', false),
    ),
  ])
  return {
    // Chunks come back in arbitrary order; restore newest-first.
    trips: tripsRes.data.sort((a, b) => b.updated_at.localeCompare(a.updated_at)),
    warnings: warningsRes.data,
  }
}

/**
 * Per-participant completion state for each trip (2026-09-07).
 *
 * Completion is personal: marking a trip complete only ever writes the
 * caller's own participant row. Everyone else is asked, never forced —
 * `othersCompleted` is what drives that prompt.
 *
 * Tolerates migration 0010 not being applied yet: the columns simply read as
 * "nobody has completed anything", so every surface falls back to the
 * trip-wide status exactly as before.
 */
export async function getTripCompletions(
  user: SessionUser,
  tripIds: string[],
): Promise<Record<string, TripCompletion>> {
  const out: Record<string, TripCompletion> = {}
  if (tripIds.length === 0) return out

  type Row = {
    id: string
    trip_id: string
    user_id: string | null
    email: string
    name: string
    role: string
    completed_at?: string | null
    completion_prompt_dismissed_at?: string | null
  }

  const admin = createAdminClient()
  const COLUMNS =
    'id, trip_id, user_id, email, name, role, completed_at, completion_prompt_dismissed_at'
  // Chunked: an admin passes every trip id on the platform.
  const wide = await inChunks<Row>(tripIds, (chunk) =>
    admin.from('trip_participants').select(COLUMNS).in('trip_id', chunk).neq('status', 'removed'),
  )

  let rows: Row[] = wide.data
  if (isMissingColumn(wide.error as { message?: string } | null, 'completed_at', 'completion_prompt_dismissed_at')) {
    // Migration 0010 not applied — no personal completions exist yet.
    const narrow = await inChunks<Row>(tripIds, (chunk) =>
      admin
        .from('trip_participants')
        .select('id, trip_id, user_id, email, name, role')
        .in('trip_id', chunk)
        .neq('status', 'removed'),
    )
    rows = narrow.data
  }

  const byTrip = new Map<string, Row[]>()
  for (const row of rows) {
    const list = byTrip.get(row.trip_id)
    if (list) list.push(row)
    else byTrip.set(row.trip_id, [row])
  }

  const email = user.email.toLowerCase()
  for (const tripId of tripIds) {
    const rows = byTrip.get(tripId) ?? []
    // Same resolution order as getMyParticipant: user id first, then email.
    const me =
      rows.find((r) => r.user_id === user.id) ??
      rows.find((r) => r.email.toLowerCase() === email) ??
      null
    out[tripId] = {
      myParticipantId: me?.id ?? null,
      completedAt: me?.completed_at ?? null,
      promptDismissedAt: me?.completion_prompt_dismissed_at ?? null,
      othersCompleted: rows
        .filter((r) => r.id !== me?.id && r.completed_at)
        .map((r) => ({ name: r.name, role: r.role, completedAt: r.completed_at! })),
    }
  }
  return out
}
