import { hmacSign, hmacVerify } from "@platform-modules/util/crypto";
//#region src/antispam.ts
/**
* Server-issued, HMAC-signed render token. The timestamp is signed so a client cannot
* forge `renderedAt` — `verifyRenderToken` recomputes the MAC over the EMBEDDED timestamp
* before trusting it. Host emits at form-render, embeds as a hidden field.
* Fail-closed: throws on an empty secret (delegated to `hmacSign`).
*/
async function issueRenderToken(secret, now) {
	const t = String(now);
	return `${t}.${await hmacSign(t, secret)}`;
}
/**
* Constant-time verify of a render token. Recomputes the HMAC over the embedded timestamp
* and compares (timing-safe) BEFORE reading the age — a forged/tampered token yields
* `{ ok: false }` with NO `ageMs` leaked. Fail-closed on empty secret.
*/
async function verifyRenderToken(secret, token, now) {
	const dot = token.indexOf(".");
	if (dot <= 0) return { ok: false };
	const t = token.slice(0, dot);
	const sig = token.slice(dot + 1);
	if (!await hmacVerify(t, secret, sig)) return { ok: false };
	const issued = Number(t);
	if (!Number.isFinite(issued)) return { ok: false };
	return {
		ok: true,
		ageMs: now - issued
	};
}
/**
* Run the configured anti-spam checks. Returns a verdict — the CALLER decides reject-vs-quarantine.
* Policy is FLAG, not silently drop (data-loss floor): `recordSubmission` stores spam=true.
* The time-trap reads `ageMs` from the signed render token, never a client `renderedAt`.
*/
async function checkAntiSpam(form, raw, ctx) {
	const cfg = form.antispam;
	if (!cfg) return { ok: true };
	if (cfg.honeypot) {
		const v = raw[cfg.honeypot];
		if (v !== void 0 && v !== null && String(v) !== "") return {
			ok: false,
			reason: "honeypot"
		};
	}
	if (cfg.minFillMs !== void 0) {
		if (!ctx.renderToken || !ctx.secret) return {
			ok: false,
			reason: "bad-token"
		};
		const v = await verifyRenderToken(ctx.secret, ctx.renderToken, ctx.now);
		if (!v.ok || v.ageMs === void 0) return {
			ok: false,
			reason: "bad-token"
		};
		if (v.ageMs < cfg.minFillMs) return {
			ok: false,
			reason: "too-fast"
		};
	}
	if (cfg.challenge) {
		if (!await cfg.challenge.verify(ctx.challengeToken ?? "", { ip: ctx.ip })) return {
			ok: false,
			reason: "challenge-failed"
		};
	}
	return { ok: true };
}
//#endregion
export { checkAntiSpam, issueRenderToken, verifyRenderToken };
