/**
 * Session tokens for the single-admin panel (edge-safe: WebCrypto only).
 *
 * Token format: base64url(JSON {u, exp}).base64url(HMAC-SHA256(secret, payload))
 * Cookie is httpOnly + SameSite=Lax (+ Secure in production).
 */
export const SESSION_COOKIE = "admin_session";
export const SESSION_MAX_AGE_SECONDS = 24 * 60 * 60;

function b64urlEncode(bytes: Uint8Array): string {
  let s = "";
  for (const b of bytes) s += String.fromCharCode(b);
  return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

function b64urlDecode(input: string): Uint8Array<ArrayBuffer> {
  const padded =
    input.replace(/-/g, "+").replace(/_/g, "/") +
    "===".slice((input.length + 3) % 4);
  const bin = atob(padded);
  const out = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
  return out;
}

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

export function getSessionSecret(): string | null {
  const s = process.env.ADMIN_SESSION_SECRET;
  return s && s.length >= 32 ? s : null;
}

export async function createSession(username: string): Promise<string | null> {
  const secret = getSessionSecret();
  if (!secret) return null;
  const payload = b64urlEncode(
    new TextEncoder().encode(
      JSON.stringify({
        u: username,
        exp: Math.floor(Date.now() / 1000) + SESSION_MAX_AGE_SECONDS,
      }),
    ),
  );
  const sig = b64urlEncode(
    new Uint8Array(
      await crypto.subtle.sign(
        "HMAC",
        await importKey(secret),
        new TextEncoder().encode(payload),
      ),
    ),
  );
  return `${payload}.${sig}`;
}

/** Returns the username when the token is authentic and unexpired. */
export async function verifySession(token: string): Promise<string | null> {
  const secret = getSessionSecret();
  if (!secret) return null;
  const dot = token.indexOf(".");
  if (dot <= 0) return null;
  const payload = token.slice(0, dot);
  const sig = token.slice(dot + 1);
  let sigBytes: Uint8Array<ArrayBuffer>;
  try {
    sigBytes = b64urlDecode(sig);
  } catch {
    return null;
  }
  // The signature covers the raw payload TEXT (base64url ASCII), JWT-style —
  // only the signature half is base64url-decoded.
  const payloadBytes = new TextEncoder().encode(payload);
  const valid = await crypto.subtle.verify(
    "HMAC",
    await importKey(secret),
    sigBytes.buffer,
    payloadBytes,
  );
  if (!valid) return null;
  try {
    // Payload half is base64url-encoded JSON — decode it before parsing.
    const data = JSON.parse(
      new TextDecoder().decode(b64urlDecode(payload)),
    ) as { u?: unknown; exp?: unknown };
    if (typeof data.u !== "string" || typeof data.exp !== "number") return null;
    if (data.exp < Math.floor(Date.now() / 1000)) return null;
    return data.u;
  } catch {
    return null;
  }
}
