import 'server-only'

import QRCode from 'qrcode'
import { createAdminClient } from '@/lib/supabase/admin'
import { invalidateAccount } from '@/lib/auth/accounts'
import { findEnvUserById } from '@/lib/auth/env-users'
import { decryptSecret, encryptSecret } from '@/lib/security/crypto'
import {
  findRecoveryCode, generateRecoveryCodes, generateTotpSecret, hashRecoveryCode, otpauthUri, verifyTotp,
} from '@/lib/security/totp'
import { logSecurityEvent } from '@/lib/security/events'

/**
 * MFA service (2026-09-22). State lives on auth_accounts (migration 0027).
 * Every write invalidates the account cache so sign-in sees it at once.
 */
const ISSUER = 'HeavyHaul Agent'
const missing = (e: { message?: string } | null | undefined) => !!e && /column|does not exist|schema cache/i.test(e.message ?? '')
const NEEDS_MIGRATION = 'Two-factor authentication needs database migration 0027.'

interface MfaRow { user_id: string; totp_secret_enc: string | null; totp_pending_enc: string | null; totp_enabled_at: string | null; totp_recovery_hashes: string[] | null }

async function row(userId: string): Promise<MfaRow | null | 'missing'> {
  const { data, error } = await createAdminClient().from('auth_accounts').select('user_id, totp_secret_enc, totp_pending_enc, totp_enabled_at, totp_recovery_hashes').eq('user_id', userId).maybeSingle()
  if (error) return missing(error) ? 'missing' : null
  return (data ?? null) as MfaRow | null
}

async function write(userId: string, patch: Record<string, unknown>, actorLabel: string) {
  const env = findEnvUserById(userId)
  if (!env) return { ok: false as const, error: 'Account not found.' }
  const now = new Date().toISOString()
  const { error } = await createAdminClient().from('auth_accounts').upsert(
    { user_id: userId, username: env.username.toLowerCase(), ...patch, totp_updated_by: actorLabel, updated_at: now },
    { onConflict: 'user_id' },
  )
  invalidateAccount(userId)
  if (error) return { ok: false as const, error: missing(error) ? NEEDS_MIGRATION : error.message }
  return { ok: true as const }
}

export interface MfaState {
  available: boolean
  enabled: boolean
  enabledAt: string | null
  recoveryCodesLeft: number
}

export async function getMfaState(userId: string): Promise<MfaState> {
  const r = await row(userId)
  if (r === 'missing') return { available: false, enabled: false, enabledAt: null, recoveryCodesLeft: 0 }
  return { available: true, enabled: !!r?.totp_secret_enc, enabledAt: r?.totp_enabled_at ?? null, recoveryCodesLeft: r?.totp_recovery_hashes?.length ?? 0 }
}

/** Fast check for sign-in: does this account require a second factor? */
export async function mfaEnabledFor(userId: string): Promise<boolean> {
  const r = await row(userId)
  return r !== 'missing' && !!r?.totp_secret_enc
}

/** Step 1 of enrolment: a fresh secret, shown as a QR code and as text. Not active until confirmed. */
export async function beginMfaEnrollment(user: { id: string; email: string; label: string }) {
  const secret = generateTotpSecret()
  const w = await write(user.id, { totp_pending_enc: encryptSecret(secret) }, user.label)
  if (!w.ok) return w
  const uri = otpauthUri({ issuer: ISSUER, account: user.email, secret })
  const qrDataUrl = await QRCode.toDataURL(uri, { margin: 1, width: 220 })
  return { ok: true as const, secret, uri, qrDataUrl }
}

/** Step 2: the first code proves the app holds the secret; MFA turns on and recovery codes are issued once. */
export async function confirmMfaEnrollment(user: { id: string; label: string }, code: string, ip: string | null) {
  const r = await row(user.id)
  if (r === 'missing') return { ok: false as const, error: NEEDS_MIGRATION }
  if (!r?.totp_pending_enc) return { ok: false as const, error: 'Start enrolment first.' }
  if (!verifyTotp(decryptSecret(r.totp_pending_enc), code)) return { ok: false as const, error: 'That code is not valid. Check the time on your phone and try the next code.' }
  const codes = generateRecoveryCodes()
  const w = await write(user.id, { totp_secret_enc: r.totp_pending_enc, totp_pending_enc: null, totp_enabled_at: new Date().toISOString(), totp_recovery_hashes: codes.map(hashRecoveryCode) }, user.label)
  if (!w.ok) return w
  await logSecurityEvent({ event: 'mfa_enrolled', userId: user.id, actorUserId: user.id, actorLabel: user.label, ip })
  return { ok: true as const, recoveryCodes: codes }
}

/** Turning MFA off requires a current code (or a recovery code). */
export async function disableMfa(user: { id: string; label: string }, code: string, ip: string | null) {
  const v = await verifySecondFactor(user.id, code)
  if (!v.ok) return { ok: false as const, error: v.error }
  const w = await write(user.id, { totp_secret_enc: null, totp_pending_enc: null, totp_enabled_at: null, totp_recovery_hashes: [] }, user.label)
  if (!w.ok) return w
  await logSecurityEvent({ event: 'mfa_disabled', userId: user.id, actorUserId: user.id, actorLabel: user.label, ip })
  return { ok: true as const }
}

export async function regenerateRecoveryCodes(user: { id: string; label: string }, code: string, ip: string | null) {
  const v = await verifySecondFactor(user.id, code)
  if (!v.ok) return { ok: false as const, error: v.error }
  const codes = generateRecoveryCodes()
  const w = await write(user.id, { totp_recovery_hashes: codes.map(hashRecoveryCode) }, user.label)
  if (!w.ok) return w
  await logSecurityEvent({ event: 'mfa_enrolled', userId: user.id, actorUserId: user.id, actorLabel: user.label, ip, detail: { recovery_codes: 'regenerated' } })
  return { ok: true as const, recoveryCodes: codes }
}

/** Sign-in step 2: a 6-digit app code, or a single-use recovery code (consumed). */
export async function verifySecondFactor(userId: string, code: string): Promise<{ ok: true; via: 'totp' | 'recovery'; recoveryLeft?: number } | { ok: false; error: string }> {
  const r = await row(userId)
  if (r === 'missing' || !r?.totp_secret_enc) return { ok: false, error: 'Two-factor authentication is not enabled on this account.' }
  const given = code.trim()
  if (verifyTotp(decryptSecret(r.totp_secret_enc), given)) return { ok: true, via: 'totp' }
  const hashes = r.totp_recovery_hashes ?? []
  const idx = findRecoveryCode(given, hashes)
  if (idx >= 0) {
    const left = hashes.filter((_, i) => i !== idx)
    await write(userId, { totp_recovery_hashes: left }, 'recovery code used')
    return { ok: true, via: 'recovery', recoveryLeft: left.length }
  }
  return { ok: false, error: 'That code is not valid.' }
}

/** HeavyHaul admin resets someone's MFA (lost phone). Their next sign-in is password only until they re-enrol. */
export async function adminResetMfa(userId: string, actor: { id: string; label: string }, ip: string | null) {
  const w = await write(userId, { totp_secret_enc: null, totp_pending_enc: null, totp_enabled_at: null, totp_recovery_hashes: [] }, actor.label)
  if (!w.ok) return w
  await logSecurityEvent({ event: 'mfa_reset_by_admin', userId, actorUserId: actor.id, actorLabel: actor.label, ip })
  return { ok: true as const }
}
