import 'server-only'

import { createHash, timingSafeEqual } from 'node:crypto'
import { z } from 'zod'
import type { UserRole } from '@/types/db'

/**
 * .env-based authentication.
 * Users are defined in the AUTH_USERS environment variable as a JSON array:
 *
 * AUTH_USERS='[
 *   {"username":"Nash_Turcan","password":"...","name":"Nash Turcan","role":"broker","company":"Nash Turcan"}
 * ]'
 *
 * Fields:
 *   username  (required) login identifier, matched case-insensitively
 *   password  (required)
 *   name      (optional) display name, defaults to the username
 *   role      (optional) broker | dispatcher | driver | admin — default broker
 *
 * Pilot testing tooling (the role-switch bar, design previews, the ?view=
 * override) is for ADMINS ONLY (requirement, 2026-09-11: "Keep all pilot
 * testing bars/features visible and active only for Admins"). It is derived
 * from the role, not from a separate flag, so no non-admin account can be
 * given it by a line in AUTH_USERS.
 *   company   (optional) required in practice for brokers (names the intake page)
 *   email     (optional) real email; enables automatic linking of trip
 *             invitations sent to that address
 *
 * Every user id is derived deterministically from the username, so database
 * references stay stable across restarts.
 */

const envUserSchema = z.object({
  username: z.string().trim().min(1),
  password: z.string().min(1),
  name: z.string().optional(),
  role: z.enum(['broker', 'dispatcher', 'driver', 'admin']).default('broker'),
  company: z.string().optional(),
  phone: z.string().optional(),
  email: z.string().email().optional(),
})

export interface EnvUser {
  id: string
  username: string
  email: string
  name: string
  role: UserRole
  company: string | null
  phone: string | null
  /** Admin — sees the pilot/preview tooling. Always `role === 'admin'`. */
  internal: boolean
}

/** Deterministic UUID from the login identifier so Postgres uuid columns keep working. */
export function identifierToUserId(identifier: string): string {
  const hex = createHash('sha256').update(identifier.trim().toLowerCase()).digest('hex')
  // format as UUID v4-shaped string (deterministic, not random)
  return [
    hex.slice(0, 8),
    hex.slice(8, 12),
    '4' + hex.slice(13, 16),
    ((parseInt(hex[16], 16) & 0x3) | 0x8).toString(16) + hex.slice(17, 20),
    hex.slice(20, 32),
  ].join('-')
}

function toEnvUser(entry: z.infer<typeof envUserSchema>): EnvUser {
  const username = entry.username.trim()
  return {
    id: identifierToUserId(username),
    username,
    // Real email when provided; otherwise a stable internal placeholder so
    // email-shaped fields elsewhere always have a value.
    email: (entry.email ?? `${username.toLowerCase()}@users.local`).toLowerCase(),
    name: entry.name?.trim() || username.replace(/_/g, ' '),
    role: entry.role,
    company: entry.company ?? null,
    phone: entry.phone ?? null,
    internal: entry.role === 'admin',
  }
}

export function parseEnvUsers(raw: string | undefined): EnvUser[] {
  if (!raw?.trim()) return []
  let json: unknown
  try {
    json = JSON.parse(raw)
  } catch {
    throw new Error('AUTH_USERS is not valid JSON — see .env.example for the format.')
  }
  return z.array(envUserSchema).parse(json).map(toEnvUser)
}

function loadUsers(): Map<string, { user: EnvUser; password: string }> {
  const raw = process.env.AUTH_USERS
  const map = new Map<string, { user: EnvUser; password: string }>()
  if (!raw?.trim()) return map
  for (const entry of z.array(envUserSchema).parse(JSON.parse(raw))) {
    const user = toEnvUser(entry)
    map.set(user.username.toLowerCase(), { user, password: entry.password })
  }
  return map
}

function safeEqual(a: string, b: string): boolean {
  const ab = Buffer.from(a)
  const bb = Buffer.from(b)
  if (ab.length !== bb.length) {
    // still burn constant time on a same-length compare
    timingSafeEqual(Buffer.alloc(bb.length), bb)
    return false
  }
  return timingSafeEqual(ab, bb)
}

/** Verify credentials against .env users. Returns the user or null. */
export function verifyCredentials(username: string, password: string): EnvUser | null {
  const entry = loadUsers().get(username.trim().toLowerCase())
  if (!entry) return null
  return safeEqual(password, entry.password) ? entry.user : null
}

export function findEnvUserByUsername(username: string): EnvUser | null {
  return loadUsers().get(username.trim().toLowerCase())?.user ?? null
}

export function findEnvUserById(id: string): EnvUser | null {
  for (const { user } of loadUsers().values()) {
    if (user.id === id) return user
  }
  return null
}

/* ---------------- Pilot role switch (testing convenience) ----------------
 * AUTH_ROLE_SWITCH=true shows a role bar under the app header: a signed-in
 * tester can become the first configured account of any role with one click
 * (a real session change). One username + one password test every role.
 * Off (unset/false) = the bar is not rendered and the action is a no-op.
 * Turn it OFF before real customers get logins.
 */

export function isRoleSwitchEnabled(): boolean {
  return /^(1|true|yes|on)$/i.test((process.env.AUTH_ROLE_SWITCH ?? '').trim())
}

export const ROLE_SWITCH_LABELS: Record<UserRole, string> = {
  broker: 'Broker',
  dispatcher: 'Carrier / Dispatcher',
  driver: 'Driver',
  admin: 'Admin · Moderator',
}

/** Roles that have a configured account, in display order (empty when the switch is off). */
export function listSwitchableRoles(): UserRole[] {
  if (!isRoleSwitchEnabled()) return []
  const order: UserRole[] = ['broker', 'dispatcher', 'driver', 'admin']
  return order.filter((role) => findEnvUserByRole(role))
}

/** First configured account with this role (declaration order in AUTH_USERS). */
export function findEnvUserByRole(role: UserRole): EnvUser | null {
  for (const { user } of loadUsers().values()) {
    if (user.role === role) return user
  }
  return null
}

/**
 * Every configured login, for the admin user console (Task 95).
 * `EnvUser` deliberately has no password field — credentials must never
 * leave this module.
 */
export function listEnvUsers(): EnvUser[] {
  return [...loadUsers().values()].map((entry) => entry.user)
}
