import { hashToken, verifyToken } from "@platform-modules/util/tokens";
import { createMail } from "@platform-modules/mail";
//#region src/otp-email.ts
var OtpError = class extends Error {};
var OtpExpiredError = class extends OtpError {
	name = "OtpExpiredError";
};
var OtpAttemptsExceededError = class extends OtpError {
	name = "OtpAttemptsExceededError";
};
const CODE_REJECT_LIMIT = 4294e6;
function randomCode() {
	let n;
	do
		n = crypto.getRandomValues(/* @__PURE__ */ new Uint32Array(1))[0];
	while (n >= CODE_REJECT_LIMIT);
	return String(n % 1e6).padStart(6, "0");
}
function createOtpEmail(config) {
	const mail = createMail(config.mail);
	const ttlMs = config.ttlMs ?? 6e5;
	const maxAttempts = config.maxAttempts ?? 5;
	return {
		async issueAndSend(email, message) {
			if (config.rateLimit && !await config.rateLimit(email)) throw new OtpError("rate limited");
			const code = randomCode();
			const codeHash = await hashToken(code);
			const expiresAt = Date.now() + ttlMs;
			await config.store.set(email.toLowerCase(), {
				codeHash,
				expiresAt,
				attempts: 0
			});
			await mail.send({
				from: config.from,
				to: email,
				subject: message.subject,
				text: message.text.replace(/\{\{code\}\}/g, code)
			});
			return { expiresAt };
		},
		async verify(email, code) {
			const key = email.toLowerCase();
			const record = await config.store.get(key);
			if (!record) return false;
			if (Date.now() > record.expiresAt) {
				await config.store.delete(key);
				throw new OtpExpiredError("otp expired");
			}
			if (record.attempts >= maxAttempts) throw new OtpAttemptsExceededError("otp attempts exceeded");
			await config.store.set(key, {
				...record,
				attempts: record.attempts + 1
			});
			if (!await verifyToken(code, record.codeHash)) return false;
			await config.store.delete(key);
			return true;
		}
	};
}
//#endregion
export { OtpAttemptsExceededError, OtpError, OtpExpiredError, createOtpEmail };
