// HMAC-signiertes Session-Cookie.
// Single-User-Auth: ein Passwort aus env, ein HMAC-Secret aus env.
// Token = base64url(JSON({iat, exp})) + "." + base64url(HMAC-SHA256).

import { createHmac, timingSafeEqual, randomBytes } from "node:crypto";

const COOKIE_NAME = "eldov_admin";
const DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 Tage

type Payload = {
  iat: number; // ms
  exp: number; // ms
  v: number;   // Version, ermöglicht forced-logout bei Secret-Rotation
};

function b64urlEncode(buf: Buffer): string {
  return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

function b64urlDecode(s: string): Buffer {
  s = s.replace(/-/g, "+").replace(/_/g, "/");
  const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - (s.length % 4));
  return Buffer.from(s + pad, "base64");
}

function getSecret(): Buffer {
  const s = process.env.SESSION_SECRET ?? "";
  if (s.length < 32) {
    throw new Error(
      "SESSION_SECRET fehlt oder ist zu kurz (mindestens 32 Zeichen). Siehe .env.local.example."
    );
  }
  return Buffer.from(s, "utf8");
}

function getPassword(): string {
  const p = process.env.ADMIN_PASSWORD ?? "";
  if (p.length < 8) {
    throw new Error("ADMIN_PASSWORD fehlt oder ist zu kurz (mindestens 8 Zeichen).");
  }
  return p;
}

export function passwordMatches(input: string): boolean {
  const expected = getPassword();
  if (input.length !== expected.length) return false;
  const a = Buffer.from(input, "utf8");
  const b = Buffer.from(expected, "utf8");
  return timingSafeEqual(a, b);
}

export function signSession(ttlMs: number = DEFAULT_TTL_MS, version: number = 1): string {
  const now = Date.now();
  const payload: Payload = { iat: now, exp: now + ttlMs, v: version };
  const payloadJson = Buffer.from(JSON.stringify(payload), "utf8");
  const payloadB64 = b64urlEncode(payloadJson);
  const sig = createHmac("sha256", getSecret()).update(payloadB64).digest();
  const sigB64 = b64urlEncode(sig);
  return `${payloadB64}.${sigB64}`;
}

export function verifySession(token: string | undefined | null): boolean {
  if (!token || typeof token !== "string") return false;
  const dot = token.indexOf(".");
  if (dot < 1 || dot === token.length - 1) return false;
  const payloadB64 = token.slice(0, dot);
  const sigB64 = token.slice(dot + 1);
  let expectedSig: Buffer;
  let payloadBuf: Buffer;
  try {
    payloadBuf = b64urlDecode(payloadB64);
    expectedSig = b64urlDecode(sigB64);
  } catch {
    return false;
  }
  const actualSig = createHmac("sha256", getSecret()).update(payloadB64).digest();
  if (actualSig.length !== expectedSig.length) return false;
  if (!timingSafeEqual(actualSig, expectedSig)) return false;
  let payload: Payload;
  try {
    payload = JSON.parse(payloadBuf.toString("utf8")) as Payload;
  } catch {
    return false;
  }
  if (typeof payload.exp !== "number" || payload.exp < Date.now()) return false;
  if (typeof payload.iat !== "number") return false;
  return true;
}

export const SESSION_COOKIE_NAME = COOKIE_NAME;

// Test-Hook: erzeugt einen Nonce, der in Tests als Pseudo-Passwort benutzt wird.
export function __testSecret(): string {
  return randomBytes(32).toString("hex");
}
