// POST /api/idempiere-auth/webauthn/login-options
//   body: { userName?: string }
//
// Tap-only login: without a userName the allow-list holds the credential IDs of ALL enrolled
// employee keys (capped by WEBAUTHN_GLOBAL_ALLOWLIST_MAX, default 150). The YubiKey answers for
// the credential it owns and login-verify maps it back to the user. With a userName only that
// user's keys are sent (fallback when the global list is disabled or too large → the response
// carries `needsUserName: true` and the sign-in page asks for the username first).
//
// `challengeToken` duplicates the signed challenge cookie for cookie-less clients (native app).
import { getRequestIP } from 'h3'
import { buildAuthenticationOptions, getRpConfig } from '../../../utils/webauthnHelper'
import { issueChallenge } from '../../../utils/webauthnChallengeStore'
import { listAllCredentials, listCredentialsForUserName } from '../../../utils/webauthnIndex'

// The public allow-list reveals opaque credential IDs (useless without the key). Still throttle
// scraping: per IP, per process (PM2 doubles the effective rate — accepted, as in authCooldown.ts).
const WINDOW_MS = 60_000
const MAX_PER_WINDOW = 40
const hits = new Map<string, { n: number; resetAt: number }>()
function throttle(ip: string) {
  const now = Date.now()
  if (hits.size > 2000) for (const [k, v] of hits) if (v.resetAt < now) hits.delete(k)
  const e = hits.get(ip)
  if (!e || e.resetAt < now) { hits.set(ip, { n: 1, resetAt: now + WINDOW_MS }); return }
  e.n += 1
  if (e.n > MAX_PER_WINDOW) {
    throw createError({ statusCode: 429, statusMessage: 'Too many security-key requests, retry in a minute' })
  }
}

export default defineEventHandler(async (event) => {
  throttle('ip:' + (getRequestIP(event, { xForwardedFor: true }) ?? 'unknown'))

  const body = await readBody(event).catch(() => ({} as any))
  const userName = String(body?.userName ?? '').trim()
  const { globalAllowlist, globalAllowlistMax } = getRpConfig()

  let creds: Awaited<ReturnType<typeof listAllCredentials>> = []
  let needsUserName = false
  try {
    if (userName) {
      creds = await listCredentialsForUserName(event, userName)
    } else if (globalAllowlist) {
      creds = await listAllCredentials(event)
      if (creds.length > globalAllowlistMax) { creds = []; needsUserName = true }
    } else {
      needsUserName = true
    }
  } catch (e: any) {
    console.warn('[webauthn] credential index unavailable:', e?.message)
    throw createError({ statusCode: 503, statusMessage: 'Security-key login temporarily unavailable' })
  }

  if (needsUserName) return { needsUserName: true }

  // Unknown username / nobody enrolled: still return a syntactically valid ceremony (empty
  // allow-list → the browser reports "no matching key") instead of revealing which names exist.
  const options = await buildAuthenticationOptions({
    allowCredentials: creds.map(c => ({ id: c.key.credentialId, transports: c.key.transports })),
  })
  const challengeToken = issueChallenge(event, options.challenge, { kind: 'login' })

  return { ...options, challengeToken, credentialCount: creds.length }
})
