import { createHmac, timingSafeEqual } from 'node:crypto';

/**
 * Signed "form issued at" tokens. The pilot page is static, so the form asks the server for a token
 * when it mounts; on submit we check the signature and that a human-plausible time has passed.
 * Format: `<issuedAtMs>.<base64url(HMAC-SHA256(secret, purpose:issuedAtMs))>`.
 */

export const MIN_FILL_MS = 3_000;
export const MAX_TOKEN_AGE_MS = 24 * 60 * 60 * 1000;

export type FormTokenCheck =
  | { ok: true; ageMs: number }
  | { ok: false; reason: 'missing' | 'malformed' | 'bad-signature' | 'too-fast' | 'expired' };

function signature(secret: string, purpose: string, issuedAt: number): Buffer {
  return createHmac('sha256', secret).update(`${purpose}:${issuedAt}`).digest();
}

export function signFormToken(issuedAt: number, secret: string, purpose: string): string {
  return `${issuedAt}.${signature(secret, purpose, issuedAt).toString('base64url')}`;
}

export function verifyFormToken(
  token: unknown,
  now: number,
  secret: string,
  purpose: string,
  { minAgeMs = MIN_FILL_MS, maxAgeMs = MAX_TOKEN_AGE_MS }: { minAgeMs?: number; maxAgeMs?: number } = {},
): FormTokenCheck {
  if (typeof token !== 'string' || token.length === 0) return { ok: false, reason: 'missing' };
  const match = /^(\d{1,16})\.([A-Za-z0-9_-]{43})$/.exec(token);
  if (!match) return { ok: false, reason: 'malformed' };
  const issuedAt = Number(match[1]);
  const given = Buffer.from(match[2] ?? '', 'base64url');
  const expected = signature(secret, purpose, issuedAt);
  if (given.length !== expected.length || !timingSafeEqual(given, expected)) return { ok: false, reason: 'bad-signature' };
  const ageMs = now - issuedAt;
  if (ageMs < minAgeMs) return { ok: false, reason: 'too-fast' };
  if (ageMs > maxAgeMs) return { ok: false, reason: 'expired' };
  return { ok: true, ageMs };
}
