'use server'

import { redirect } from 'next/navigation'
import { headers } from 'next/headers'
import { getSessionUser } from '@/lib/auth'
import { findEnvUserByRole, isRoleSwitchEnabled, type EnvUser } from '@/lib/auth/env-users'
import { getAccountOverride, effectiveRole, verifyLogin } from '@/lib/auth/accounts'
import { clearMfaPendingCookie, clearSessionCookie, readMfaPendingCookie, setMfaPendingCookie } from '@/lib/auth/session'
import { mfaEnabledFor, verifySecondFactor } from '@/lib/security/mfa'
import { findEnvUserById } from '@/lib/auth/env-users'
import { establishSession } from '@/lib/auth/establish-session'
import { findSelfAccountById, selfAccountAsEnvUser } from '@/lib/auth/self-accounts'
import { recaptchaHostname, verifyRecaptcha } from '@/lib/security/recaptcha'
import { isInternalRole } from '@/lib/domain/roles'
import type { UserRole } from '@/types/db'
import { LOGIN_LIMITS, clientIp, rateLimit, rateLimitReset, rateLimitStatus } from '@/lib/security/rate-limit'
import { logSecurityEvent } from '@/lib/security/events'

/** Legacy username/password sign-in; Google uses Supabase Auth separately. */

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') ?? '').trim()
  const password = String(formData.get('password') ?? '')
  const h = await headers()
  const ip = clientIp(h)
  const userAgent = h.get('user-agent')
  const userKey = `login:user:${username.toLowerCase()}`
  const ipKey = `login:ip:${ip}`

  // Brute-force protection (2026-09-22): 10 failures per username or 30 per
  // IP in 15 minutes locks sign-in for the rest of the window. Same generic
  // message either way — no hint whether the username exists.
  const locked = [rateLimitStatus(userKey, LOGIN_LIMITS.perUser, LOGIN_LIMITS.windowMs), rateLimitStatus(ipKey, LOGIN_LIMITS.perIp, LOGIN_LIMITS.windowMs)].find((r) => !r.allowed)
  if (locked) {
    await logSecurityEvent({ event: 'login_locked', username, ip, userAgent, detail: { retry_after_seconds: locked.retryAfterSeconds } })
    return { error: `Too many sign-in attempts. Try again in ${Math.ceil(locked.retryAfterSeconds / 60)} minute${locked.retryAfterSeconds > 60 ? 's' : ''}.` }
  }

  const captcha = await verifyRecaptcha(String(formData.get('recaptchaToken') ?? ''), 'password_login', recaptchaHostname(h.get('host')))
  if (!captcha.ok) return { error: captcha.error }

  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) {
    rateLimit(userKey, LOGIN_LIMITS.perUser, LOGIN_LIMITS.windowMs)
    rateLimit(ipKey, LOGIN_LIMITS.perIp, LOGIN_LIMITS.windowMs)
    await logSecurityEvent({ event: 'login_failed', username, ip, userAgent })
    return { error: 'Invalid username or password.' }
  }
  rateLimitReset(userKey)

  // Second factor (2026-09-22): the password alone never opens a session on an
  // MFA-enabled account — a 5-minute pending cookie carries the person to the
  // code step instead.
  if (await mfaEnabledFor(user.id)) {
    await logSecurityEvent({ event: 'login_mfa_required', username, userId: user.id, ip, userAgent })
    await setMfaPendingCookie({ sub: user.id, username: user.username, next: safeNext(formData) })
    redirect('/login/mfa')
  }

  await logSecurityEvent({ event: 'login_success', username, userId: user.id, ip, userAgent, detail: { role: user.role } })

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

/** Sign-in step 2: a 6-digit authenticator code or a single-use recovery code. */
export async function verifyMfa(_prev: { error?: string } | undefined, formData: FormData) {
  const pending = await readMfaPendingCookie()
  if (!pending) redirect('/login?expired=mfa')
  const code = String(formData.get('code') ?? '').trim()
  const h = await headers()
  const ip = clientIp(h)
  const userAgent = h.get('user-agent')
  const key = `mfa:${pending.sub}`
  const gate = rateLimit(key, 6, 5 * 60_000)
  if (!gate.allowed) {
    await logSecurityEvent({ event: 'login_locked', username: pending.username, userId: pending.sub, ip, userAgent, detail: { step: 'mfa' } })
    return { error: 'Too many code attempts. Sign in again in a few minutes.' }
  }
  const result = await verifySecondFactor(pending.sub, code)
  if (!result.ok) {
    await logSecurityEvent({ event: 'login_mfa_failed', username: pending.username, userId: pending.sub, ip, userAgent })
    return { error: result.error }
  }
  const self = findEnvUserById(pending.sub) ? null : await findSelfAccountById(pending.sub)
  const env = findEnvUserById(pending.sub) ?? (self ? selfAccountAsEnvUser(self) : null)
  if (!env) redirect('/login')
  const user = { ...env, role: effectiveRole(env, await getAccountOverride(env.id)) }
  if (pending.auth_method === 'google' && isInternalRole(user.role)) redirect('/login')
  rateLimitReset(key)
  await clearMfaPendingCookie()
  await logSecurityEvent({ event: 'login_success', username: user.username, userId: user.id, ip, userAgent, detail: { role: user.role, mfa: result.via, recovery_codes_left: result.recoveryLeft } })
  await establishSession(user, isInternalRole(user.role), user.id, pending.auth_method)
  redirect(pending.next ?? 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)) }
  const h = await headers()
  await logSecurityEvent({ event: 'role_switch', userId: target.id, actorUserId: current.originId, actorLabel: current.name || current.email, ip: clientIp(h), userAgent: h.get('user-agent'), detail: { to_role: role } })
  // 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))
}

export async function signOut() {
  const current = await getSessionUser()
  const h = await headers()
  if (current) await logSecurityEvent({ event: 'signout', userId: current.id, ip: clientIp(h), userAgent: h.get('user-agent') })
  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
}
