/** HMAC-SHA256 signed tokens for magic link responses. */

import { magicLinkConfig } from "./config";

export interface MagicLinkPayload {
  /** space slug */
  s: string;
  /** target type */
  t: "poll" | "rsvp";
  /** target id (sessionId or eventId) */
  i: string;
  /** respondent name/label */
  n: string;
  /** expiry (unix seconds) */
  e: number;
}

interface TokenData extends MagicLinkPayload {
  /** hex signature */
  h: string;
}

async function getKey(): Promise<CryptoKey> {
  return crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(magicLinkConfig.hmacSecret),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign", "verify"],
  );
}

function toBase64Url(data: string): string {
  return btoa(data).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

function fromBase64Url(b64: string): string {
  return atob(b64.replace(/-/g, "+").replace(/_/g, "/"));
}

function toHex(buf: ArrayBuffer): string {
  return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
}

async function sign(payload: string): Promise<string> {
  const key = await getKey();
  const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(payload));
  return toHex(sig);
}

export async function createMagicToken(
  space: string,
  type: "poll" | "rsvp",
  targetId: string,
  respondentName: string,
): Promise<string> {
  const expiry = Math.floor(Date.now() / 1000) + magicLinkConfig.tokenExpiryDays * 86400;
  const payload: MagicLinkPayload = { s: space, t: type, i: targetId, n: respondentName, e: expiry };
  const payloadStr = `${payload.s}:${payload.t}:${payload.i}:${payload.n}:${payload.e}`;
  const sig = await sign(payloadStr);
  const token: TokenData = { ...payload, h: sig };
  return toBase64Url(JSON.stringify(token));
}

export interface VerifiedMagicToken {
  space: string;
  type: "poll" | "rsvp";
  targetId: string;
  respondentName: string;
  /** Short hash for dedup keying */
  tokenHash: string;
}

export async function verifyMagicToken(token: string): Promise<VerifiedMagicToken> {
  let data: TokenData;
  try {
    data = JSON.parse(fromBase64Url(token));
  } catch {
    throw new Error("Invalid token format");
  }

  if (!data.s || !data.t || !data.i || !data.n || !data.e || !data.h) {
    throw new Error("Malformed token");
  }

  if (data.t !== "poll" && data.t !== "rsvp") {
    throw new Error("Invalid target type");
  }

  if (data.e < Math.floor(Date.now() / 1000)) {
    throw new Error("Token expired");
  }

  const payloadStr = `${data.s}:${data.t}:${data.i}:${data.n}:${data.e}`;
  const expected = await sign(payloadStr);
  if (data.h !== expected) {
    throw new Error("Invalid signature");
  }

  // Short hash for dedup (first 12 chars of signature)
  const tokenHash = data.h.slice(0, 12);

  return {
    space: data.s,
    type: data.t,
    targetId: data.i,
    respondentName: data.n,
    tokenHash,
  };
}
