'use server'

import { redirect } from 'next/navigation'
import { getSessionUser } from '@/lib/auth'
import { findEnvUserByRole, isRoleSwitchEnabled } from '@/lib/auth/env-users'
import { getAccountOverride, effectiveRole, verifyLogin } from '@/lib/auth/accounts'
import { clearSessionCookie, setSessionCookie } from '@/lib/auth/session'
import { createAdminClient } from '@/lib/supabase/admin'
import { isInternalRole } from '@/lib/domain/roles'
import { emailPattern } from '@/lib/like'
import { slugify, slugAlternatives } from '@/lib/domain/slug'
import type { EnvUser } from '@/lib/auth/env-users'
import type { UserRole } from '@/types/db'

/**
 * .env-based authentication: accounts live in the AUTH_USERS environment
 * variable (see .env.example). There is no self-serve signup — the
 * administrator provisions users.
 */

const SWITCHABLE_ROLES: UserRole[] = ['broker', 'dispatcher', 'driver', 'admin']

/** Where a role lands after sign-in when no explicit `next` was requested. */
const homeFor = (role: UserRole) => (role === 'admin' ? '/admin/moderation' : '/dashboard')

export async function signIn(_prev: { error?: string } | undefined, formData: FormData) {
  const username = String(formData.get('username') ?? '')
  const password = String(formData.get('password') ?? '')

  let user: EnvUser | null
  try {
    // An admin-set password or role (auth_accounts) takes precedence over
    // AUTH_USERS — that is what makes reset and role change real.
    user = await verifyLogin(username, password)
  } catch (err) {
    return { error: err instanceof Error ? err.message : 'Auth configuration error' }
  }
  if (!user) return { error: 'Invalid username or password.' }

  // Admins keep the pilot tooling for the whole session, even after switching
  // into a customer role to test. Admin only — nothing else grants it.
  await establishSession(user, isInternalRole(user.role), user.id)
  redirect(safeNext(formData) ?? homeFor(user.role))
}

/**
 * Pilot role switch (AUTH_ROLE_SWITCH=true): the header bar lets an INTERNAL
 * tester become the first configured account of another role — a REAL
 * session change, so admin pages, per-user trips, chat privacy and the audit
 * trail all behave as that account. No-op unless the flag is on.
 *
 * Two locks, both required (Task 96, 2026-09-09):
 *   1. the AUTH_ROLE_SWITCH flag — turns the capability off entirely
 *   2. the CALLER must be internal — Nash: "that one should be visible only
 *      for admin and developer roles. No other users should see it."
 *
 * Lock 2 is the one that matters. Before it, this action checked only the
 * flag and the target role, never who was asking — so with the flag on in a
 * deployed environment any signed-in driver or broker could post here and be
 * handed a real admin session. Hiding the bar in the UI would have left that
 * open; the check has to be here.
 */
export async function switchRole(formData: FormData) {
  const current = await getSessionUser()
  if (!current) redirect('/login')
  if (!isRoleSwitchEnabled()) redirect('/dashboard')
  if (!current.internal) redirect('/dashboard')
  const role = String(formData.get('role') ?? '') as UserRole
  if (!SWITCHABLE_ROLES.includes(role)) redirect('/dashboard')
  const envTarget = findEnvUserByRole(role)
  if (!envTarget) redirect('/dashboard')
  // Honour an admin-set role on the target account, as sign-in does.
  const target = { ...envTarget, role: effectiveRole(envTarget, await getAccountOverride(envTarget.id)) }
  // The session stays internal so the tester can switch back out of a
  // customer role — the whole point of the bar. It remembers which admin
  // started it, so revoking that admin ends it too.
  await establishSession(target, true, current.originId)
  redirect(homeFor(target.role))
}

/** Profile row, broker intake page, invitation linking, session cookie. */
async function establishSession(user: EnvUser, internal: boolean, originSub: string) {
  // Keep a profile row in Postgres so foreign keys (trips, participants,
  // documents…) reference a real record.
  const admin = createAdminClient()
  await admin.from('profiles').upsert(
    {
      id: user.id,
      email: user.email,
      full_name: user.name,
      phone: user.phone,
      company_name: user.company,
      default_role: user.role,
    },
    { onConflict: 'id' },
  )

  // Brokers automatically receive their unique public intake page.
  if (user.role === 'broker') {
    await ensureBrokerPage(user.id, user.company || user.name)
  }

  // Link any pending participant rows for this email to the user id.
  await admin
    .from('trip_participants')
    .update({ user_id: user.id })
    .is('user_id', null)
    // Exact match: an unescaped `_` would link another person's invitation
    // (and with it, their trip) to this account.
    .ilike('email', emailPattern(user.email))

  await setSessionCookie({
    sub: user.id,
    email: user.email,
    name: user.name,
    role: user.role,
    company: user.company,
    internal,
    origin_sub: originSub,
  })
}

/** Creates the broker intake page with a unique slug (idempotent). */
export async function ensureBrokerPage(ownerId: string, companyName: string) {
  const admin = createAdminClient()
  const { data: existing } = await admin
    .from('broker_pages')
    .select('id')
    .eq('owner_id', ownerId)
    .maybeSingle()
  if (existing) return

  const base = slugify(companyName) || 'broker'
  const candidates = [base, ...slugAlternatives(base)]
  for (const slug of candidates) {
    const { error } = await admin.from('broker_pages').insert({
      owner_id: ownerId,
      slug,
      display_name: companyName,
    })
    if (!error) return
    if (!error.message.includes('duplicate')) break
  }
  await admin.from('broker_pages').insert({
    owner_id: ownerId,
    slug: `${base}-${Math.random().toString(36).slice(2, 7)}`,
    display_name: companyName,
  })
}

export async function signOut() {
  await clearSessionCookie()
  redirect('/login')
}

/** Only allow same-app relative redirect targets; null when none was given. */
function safeNext(formData: FormData): string | null {
  const next = String(formData.get('next') ?? '')
  return next.startsWith('/') && !next.startsWith('//') ? next : null
}
