/**
 * T5 · Passkey/WebAuthn authentication ceremony. Enforces single-use challenge, origin/rpId
 * binding, and the signCount-regression guard (a counter that goes backwards signals a cloned
 * authenticator → reject). Yields an `AuthenticatedPrincipal` with `amr:['passkey']`.
 */
import { randomBytes } from 'node:crypto'
import { encodeBase64UrlBytes } from 'relay-contracts'
import type { AuthenticatedPrincipal, WebAuthnCredential } from '../../types.js'
import {
  WebAuthnError,
  type AuthenticationOptions,
  type AuthenticationResponse,
  type WebAuthnVerifier,
} from './verifier.js'

const CHALLENGE_BYTES = 32
const DEFAULT_TIMEOUT_MS = 60_000

export function startAuthentication(_accountId: string, rpId: string): Promise<AuthenticationOptions> {
  return Promise.resolve({
    rpId,
    challenge: encodeBase64UrlBytes(new Uint8Array(randomBytes(CHALLENGE_BYTES))),
    timeoutMs: DEFAULT_TIMEOUT_MS,
  })
}

export async function finishAuthentication(
  cred: WebAuthnCredential,
  resp: AuthenticationResponse,
  expectedChallenge: string,
  rpId: string,
  origin: string,
  verifier: WebAuthnVerifier,
  now: number,
): Promise<{ principal: AuthenticatedPrincipal; newSignCount: number }> {
  if (resp.clientChallenge !== expectedChallenge) throw new WebAuthnError('challenge mismatch')
  if (resp.origin !== origin) throw new WebAuthnError('origin mismatch')
  if (resp.rpId !== rpId) throw new WebAuthnError('rpId mismatch')
  if (resp.credentialId !== cred.credentialId) throw new WebAuthnError('credential id mismatch')

  const result = await verifier.verifyAuthentication(
    resp,
    expectedChallenge,
    rpId,
    origin,
    cred.publicKey,
    cred.credentialId,
    cred.signCount,
  )
  if (!result.verified) throw new WebAuthnError('assertion not verified')
  if (result.newSignCount <= cred.signCount && !(result.newSignCount === 0 && cred.signCount === 0)) {
    throw new WebAuthnError('signCount regression (cloned authenticator)')
  }

  const principal: AuthenticatedPrincipal = {
    kind: 'human',
    accountId: cred.accountId,
    principalId: cred.credentialId,
    amr: ['passkey'],
    authAt: now,
    stepUpAt: null,
  }
  return { principal, newSignCount: result.newSignCount }
}
