// QR token utilities for Drinkkaart using Web Crypto API
// Compatible with Cloudflare Workers (no node:crypto dependency).

const QR_TOKEN_TTL_MS = 5 * 60 * 1000; // 5 minutes

interface QrPayload {
	drinkkaartId: string;
	userId: string;
	iat: number;
	exp: number;
}

function toBase64Url(buffer: ArrayBuffer): string {
	return btoa(String.fromCharCode(...new Uint8Array(buffer)))
		.replace(/\+/g, "-")
		.replace(/\//g, "_")
		.replace(/=/g, "");
}

function base64UrlToBuffer(str: string): Uint8Array<ArrayBuffer> {
	const base64 = str.replace(/-/g, "+").replace(/_/g, "/");
	const bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
	// Ensure the underlying buffer is a plain ArrayBuffer (required by crypto.subtle)
	return new Uint8Array(bytes.buffer.slice(0));
}

/** Generate a cryptographically secure 32-byte hex secret for a new Drinkkaart. */
export function generateQrSecret(): string {
	const bytes = new Uint8Array(32);
	crypto.getRandomValues(bytes);
	return Array.from(bytes)
		.map((b) => b.toString(16).padStart(2, "0"))
		.join("");
}

/**
 * Sign a QR token for the given drinkkaart / user.
 * Returns the compact token string and the expiry date.
 */
export async function signQrToken(
	payload: Omit<QrPayload, "iat" | "exp">,
	qrSecret: string,
	authSecret: string,
): Promise<{ token: string; expiresAt: Date }> {
	const now = Date.now();
	const exp = now + QR_TOKEN_TTL_MS;
	const full: QrPayload = { ...payload, iat: now, exp };

	const encodedPayload = btoa(JSON.stringify(full))
		.replace(/\+/g, "-")
		.replace(/\//g, "_")
		.replace(/=/g, "");

	const keyData = new TextEncoder().encode(qrSecret + authSecret);
	const key = await crypto.subtle.importKey(
		"raw",
		keyData,
		{ name: "HMAC", hash: "SHA-256" },
		false,
		["sign"],
	);

	const sigBuffer = await crypto.subtle.sign(
		"HMAC",
		key,
		new TextEncoder().encode(encodedPayload),
	);
	const sig = toBase64Url(sigBuffer);

	return { token: `${encodedPayload}.${sig}`, expiresAt: new Date(exp) };
}

/**
 * Verify a QR token. Throws if the signature is invalid or the token is expired.
 * Returns the decoded payload.
 */
export async function verifyQrToken(
	token: string,
	qrSecret: string,
	authSecret: string,
): Promise<QrPayload> {
	const parts = token.split(".");
	if (parts.length !== 2) throw new Error("MALFORMED");

	const [encodedPayload, providedSig] = parts as [string, string];

	const keyData = new TextEncoder().encode(qrSecret + authSecret);
	const key = await crypto.subtle.importKey(
		"raw",
		keyData,
		{ name: "HMAC", hash: "SHA-256" },
		false,
		["verify"],
	);

	const sigBytes = base64UrlToBuffer(providedSig);
	const valid = await crypto.subtle.verify(
		"HMAC",
		key,
		sigBytes,
		new TextEncoder().encode(encodedPayload),
	);
	if (!valid) throw new Error("INVALID_SIGNATURE");

	const payload: QrPayload = JSON.parse(
		atob(encodedPayload.replace(/-/g, "+").replace(/_/g, "/")),
	);

	if (Date.now() > payload.exp) throw new Error("EXPIRED");

	return payload;
}

/** Format a cents integer as a Belgian euro string, e.g. 1250 → "€ 12,50". */
export function formatCents(cents: number): string {
	return new Intl.NumberFormat("nl-BE", {
		style: "currency",
		currency: "EUR",
	}).format(cents / 100);
}
