import { createHmac, timingSafeEqual } from "node:crypto";
import type { Entitlements } from "@app/shared-types";

/**
 * Signerad entitlement-token för offline-läge (spec §44):
 * "Premium får inte vara permanent lokal boolean. Använd signerad
 * entitlement-token med begränsad giltighet och grace period."
 *
 * Kompakt HS256-JWT implementerad med node:crypto (inga extra beroenden).
 * Appen cachar token och litar på den offline tills exp + grace har passerat.
 */

export interface EntitlementTokenPayload {
  sub: string; // userId
  plan: Entitlements["plan"];
  status: string;
  maxMembers: number;
  scansPerMonth: number;
  weekPlanning: boolean;
  advancedNutrition: boolean;
  communityPublish: boolean;
  iat: number;
  exp: number;
  /** Unix-sekunder: sista tidpunkt token accepteras offline (grace). */
  graceExp: number;
}

const b64url = (buf: Buffer): string =>
  buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
const fromB64url = (s: string): Buffer =>
  Buffer.from(s.replace(/-/g, "+").replace(/_/g, "/"), "base64");

export function signEntitlementToken(
  userId: string,
  entitlements: Entitlements,
  secret: string,
  options: { ttlHours?: number; graceDays?: number; now?: Date } = {},
): string {
  const now = options.now ?? new Date();
  const ttlHours = options.ttlHours ?? 24;
  const graceDays = options.graceDays ?? 7;
  const iat = Math.floor(now.getTime() / 1000);
  const exp = iat + ttlHours * 3600;

  const payload: EntitlementTokenPayload = {
    sub: userId,
    plan: entitlements.plan,
    status: entitlements.status,
    maxMembers: entitlements.maxHouseholdMembers,
    scansPerMonth: entitlements.aiScansPerMonth,
    weekPlanning: entitlements.weekPlanning,
    advancedNutrition: entitlements.advancedNutrition,
    communityPublish: entitlements.communityPublish,
    iat,
    exp,
    graceExp: exp + graceDays * 86_400,
  };

  const header = b64url(Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })));
  const body = b64url(Buffer.from(JSON.stringify(payload)));
  const signature = b64url(createHmac("sha256", secret).update(`${header}.${body}`).digest());
  return `${header}.${body}.${signature}`;
}

export type TokenVerification =
  | { valid: true; payload: EntitlementTokenPayload; withinGrace: boolean }
  | { valid: false; reason: "malformed" | "bad_signature" | "expired_beyond_grace" };

export function verifyEntitlementToken(
  token: string,
  secret: string,
  now: Date = new Date(),
): TokenVerification {
  const parts = token.split(".");
  if (parts.length !== 3) return { valid: false, reason: "malformed" };
  const [header, body, signature] = parts as [string, string, string];

  const expected = createHmac("sha256", secret).update(`${header}.${body}`).digest();
  const actual = fromB64url(signature);
  if (expected.length !== actual.length || !timingSafeEqual(expected, actual)) {
    return { valid: false, reason: "bad_signature" };
  }

  let payload: EntitlementTokenPayload;
  try {
    payload = JSON.parse(fromB64url(body).toString("utf8")) as EntitlementTokenPayload;
  } catch {
    return { valid: false, reason: "malformed" };
  }

  const nowSec = Math.floor(now.getTime() / 1000);
  if (nowSec > payload.graceExp) return { valid: false, reason: "expired_beyond_grace" };
  return { valid: true, payload, withinGrace: nowSec > payload.exp };
}
