/**
 * Backup-code generation, hashing, and verification — auth-2fa.
 *
 * Backup codes are one-time-use alternatives to phone OTP for 2FA recovery.
 * Format: XXXX-XXXX (two groups of 4) uppercase alphanumeric from the
 * unambiguous alphabet excluding look-alike characters (0/O, 1/I/L).
 *
 * All hashing via crypto.subtle.digest (SHA-256). All comparisons timing-safe.
 */

import { timingSafeEqual } from './crypto'

/** Unambiguous alphanumeric alphabet (excludes 0, O, 1, I, L). */
const ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789'
const ALPHABET_LEN = ALPHABET.length

/** Encode 4 random chars from the unambiguous alphabet. */
function randomGroup(): string {
  const bytes = crypto.getRandomValues(new Uint8Array(4))
  return Array.from(bytes)
    .map((b) => ALPHABET[b! % ALPHABET_LEN]!)
    .join('')
}

/**
 * Generate `count` backup codes (default 8), each `XXXX-XXXX` format.
 * Source randomness from crypto.getRandomValues.
 */
export function generateBackupCodes(count = 8): string[] {
  return Array.from({ length: count }, () => `${randomGroup()}-${randomGroup()}`)
}

/** Normalize a plaintext backup code before hashing (uppercase, keep hyphen). */
function normalize(code: string): string {
  return code.toUpperCase()
}

async function sha256Hex(input: string): Promise<string> {
  const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(input))
  return Array.from(new Uint8Array(digest))
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('')
}

/** SHA-256 hex of the normalized backup code — the DB column value. */
export function hashBackupCode(plaintext: string): Promise<string> {
  return sha256Hex(normalize(plaintext))
}

/**
 * True when `plaintext` hashes (normalized) to `codeHash`.
 * Uses timingSafeEqual — never ===.
 */
export async function verifyBackupCode(plaintext: string, codeHash: string): Promise<boolean> {
  const computed = await hashBackupCode(plaintext)
  return timingSafeEqual(computed, codeHash)
}
