/**
 * Admin TOTP enrollment + verification — foundation-auth-rbac (Task 13).
 *
 *   POST /totp/setup/start  (setup_token)        -> generate secret, store in KV
 *                                                   (encrypted), return otpauth URL.
 *   POST /totp/setup/verify (setup_token, {code}) -> verify against KV secret,
 *                                                   persist encrypted to DB,
 *                                                   issue full admin session.
 *   POST /totp/verify       (temp_admin_token,{code}) -> verify, issue session.
 *
 * Short-lived setup/temp tokens are signed JWTs (purpose-scoped). The TOTP
 * secret is held encrypted in KV during enrollment, then AES-256-GCM encrypted
 * with ADMIN_ENCRYPTION_KEY in admin_users.totp_secret.
 *
 */
import { Hono } from 'hono'
import { generateSecret, verify } from 'otplib'
import {
  decryptSecret,
  encryptSecret,
  signAdminSession,
  verifySignedToken,
} from '@zync/auth'
import { createDb, findAdminById, setAdminTotpSecret } from '@zync/db/queries'
import type { AppEnv } from '../../../types'
import { totpCodeSchema } from '../../../schemas/admin-auth'
import { setAdminCookie } from '../../../lib/cookies'
import {
  clearFailures,
  isLockedOut,
  recordFailure,
} from '../../../lib/login-throttle'

import { getAdminOrigins } from '../../../lib/origins'

const KV_SETUP_TTL = 60 * 15 // 15 min enrollment window

function checkOrigin(o: string | undefined, environment: string | undefined): boolean {
  const allowedOrigins = getAdminOrigins({ environment })
  return !!o && allowedOrigins.has(o)
}

/** Pull a purpose-scoped JWT from the Authorization header and verify it. */
async function verifyPurposeToken(
  authHeader: string | undefined,
  secret: string,
  expectedPurpose: string,
): Promise<string | null> {
  if (!authHeader || !authHeader.startsWith('Bearer ')) return null
  const token = authHeader.slice('Bearer '.length).trim()
  try {
    const claims = await verifySignedToken(token, secret)
    if (claims.purpose !== expectedPurpose || typeof claims.sub !== 'string') return null
    return claims.sub
  } catch {
    return null
  }
}

export const adminTotpRoute = new Hono<AppEnv>()

// --- Setup: start (generate + stash secret) ---
adminTotpRoute.post('/totp/setup/start', async (c) => {
  if (!checkOrigin(c.req.header('Origin'), (c.env as { ENVIRONMENT?: string }).ENVIRONMENT)) {
    return c.json({ error: 'Forbidden: invalid Origin' }, 403)
  }
  const adminId = await verifyPurposeToken(
    c.req.header('Authorization'),
    c.env.JWT_SECRET,
    'admin_totp_setup',
  )
  if (!adminId) return c.json({ error: 'Invalid setup token' }, 401)

  const db = createDb(c.env)
  const admin = await findAdminById(db, adminId)
  if (!admin) return c.json({ error: 'Admin not found' }, 404)

  const secret = generateSecret()
  // Stash the secret encrypted in KV (not DB yet) for the enrollment window.
  const encrypted = await encryptSecret(secret, c.env.ADMIN_ENCRYPTION_KEY)
  await c.env.KV.put(`admin_totp_setup:${adminId}`, encrypted, {
    expirationTtl: KV_SETUP_TTL,
  })

  const label = encodeURIComponent(`Zync Admin:${admin.email}`)
  const qrCodeUrl = `otpauth://totp/${label}?secret=${secret}&issuer=Zync`
  return c.json({ qr_code_url: qrCodeUrl, secret }, 200)
})

// --- Setup: verify (persist + issue session) ---
adminTotpRoute.post('/totp/setup/verify', async (c) => {
  if (!checkOrigin(c.req.header('Origin'), (c.env as { ENVIRONMENT?: string }).ENVIRONMENT)) {
    return c.json({ error: 'Forbidden: invalid Origin' }, 403)
  }
  const adminId = await verifyPurposeToken(
    c.req.header('Authorization'),
    c.env.JWT_SECRET,
    'admin_totp_setup',
  )
  if (!adminId) return c.json({ error: 'Invalid setup token' }, 401)

  const parsed = totpCodeSchema.safeParse(await c.req.json())
  if (!parsed.success) return c.json({ error: 'Invalid code' }, 400)

  const stashed = await c.env.KV.get(`admin_totp_setup:${adminId}`)
  if (!stashed) return c.json({ error: 'Setup expired, restart enrollment' }, 410)
  const secret = await decryptSecret(stashed, c.env.ADMIN_ENCRYPTION_KEY)

  if (!(await verify({ token: parsed.data.code, secret })).valid) {
    return c.json({ error: 'Invalid code' }, 401)
  }

  // Persist encrypted secret to DB, drop the KV stash.
  const db = createDb(c.env)
  const encrypted = await encryptSecret(secret, c.env.ADMIN_ENCRYPTION_KEY)
  await setAdminTotpSecret(db, adminId, encrypted)
  await c.env.KV.delete(`admin_totp_setup:${adminId}`)

  const adminJwt = await signAdminSession(
    { sub: adminId, type: 'admin', totp_verified: true },
    c.env.JWT_SECRET,
  )
  setAdminCookie(c, adminJwt)
  return c.json({ message: 'TOTP enrolled' }, 200)
})

// --- Verify (login second factor) ---
adminTotpRoute.post('/totp/verify', async (c) => {
  if (!checkOrigin(c.req.header('Origin'), (c.env as { ENVIRONMENT?: string }).ENVIRONMENT)) {
    return c.json({ error: 'Forbidden: invalid Origin' }, 403)
  }
  const adminId = await verifyPurposeToken(
    c.req.header('Authorization'),
    c.env.JWT_SECRET,
    'admin_totp_verify',
  )
  if (!adminId) return c.json({ error: 'Invalid token' }, 401)

  const ip = c.req.header('CF-Connecting-IP') ?? 'unknown'
  const rl = await (async () => { try { const _r = await c.env.RATE_LIMITER_AUTH?.limit({ key: `admin_totp:${ip}` }); return _r ?? { success: true }; } catch { return { success: true }; } })()
  if (!rl.success) return c.json({ error: 'Too many requests' }, 429)

  if (await isLockedOut(c.env, `admin_totp:${adminId}`)) {
    return c.json({ error: 'Locked out' }, 429)
  }

  const parsed = totpCodeSchema.safeParse(await c.req.json())
  if (!parsed.success) return c.json({ error: 'Invalid code' }, 400)

  const db = createDb(c.env)
  const admin = await findAdminById(db, adminId)
  if (!admin || !admin.totpSecret) return c.json({ error: 'TOTP not enrolled' }, 409)
  const secret = await decryptSecret(admin.totpSecret, c.env.ADMIN_ENCRYPTION_KEY)

  if (!(await verify({ token: parsed.data.code, secret })).valid) {
    await recordFailure(c.env, `admin_totp:${adminId}`)
    return c.json({ error: 'Invalid code' }, 401)
  }
  await clearFailures(c.env, `admin_totp:${adminId}`)

  const adminJwt = await signAdminSession(
    { sub: adminId, type: 'admin', totp_verified: true },
    c.env.JWT_SECRET,
  )
  setAdminCookie(c, adminJwt)
  return c.json({ message: 'Authenticated' }, 200)
})
