/**
 * `@platform-modules/util/tokens` — opaque CSPRNG tokens (store the hash, never the token).
 */
import { timingSafeEqual } from './crypto'

function bytesToBase64Url(bytes: Uint8Array): string {
  let binary = ''
  for (const b of bytes) binary += String.fromCharCode(b)
  return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}

/** N cryptographically-random bytes (default 32), base64url-encoded (no padding). */
export function generateOpaqueToken(bytes = 32): string {
  const tokenBytes = crypto.getRandomValues(new Uint8Array(bytes))
  return bytesToBase64Url(tokenBytes)
}

/** SHA-256 hex digest of `token` — the value stored in the DB. */
export async function hashToken(token: string): Promise<string> {
  const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(token))
  return Array.from(new Uint8Array(digest))
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('')
}

/** True when `token` hashes to `hash`. Timing-safe — never `===`. */
export async function verifyToken(token: string, hash: string): Promise<boolean> {
  const computed = await hashToken(token)
  return timingSafeEqual(computed, hash)
}
