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

export type SessionPayload = {
  typ: "admin";
  sub: string;
  name: string;
  email: string;
  role_id: string;
  role_name: string;
  permissions: unknown;
  iat: number;
  exp: number;
};

export type MerchantSessionPayload = {
  typ: "merchant";
  sub: string;
  merchant_id: string;
  name: string;
  email: string;
  role_name: string;
  iat: number;
  exp: number;
};

function base64UrlEncode(value: string | Buffer) {
  return Buffer.from(value).toString("base64url");
}

function base64UrlDecode(value: string) {
  return Buffer.from(value, "base64url").toString("utf8");
}

function sign(unsigned: string, secret: string) {
  return createHmac("sha256", secret).update(unsigned).digest("base64url");
}

export function createSessionToken(payload: Omit<SessionPayload, "iat" | "exp">, secret: string, ttlSeconds: number) {
  const now = Math.floor(Date.now() / 1000);
  const body: SessionPayload = {
    ...payload,
    iat: now,
    exp: now + ttlSeconds
  };
  const encoded = base64UrlEncode(JSON.stringify(body));
  const signature = sign(encoded, secret);
  return `${encoded}.${signature}`;
}

export function verifySessionToken(token: string, secret: string): SessionPayload | null {
  const [encoded, signature] = token.split(".");
  if (!encoded || !signature) {
    return null;
  }

  const expected = sign(encoded, secret);
  const expectedBuffer = Buffer.from(expected);
  const actualBuffer = Buffer.from(signature);
  if (expectedBuffer.length !== actualBuffer.length || !timingSafeEqual(expectedBuffer, actualBuffer)) {
    return null;
  }

  let payload: SessionPayload;
  try {
    payload = JSON.parse(base64UrlDecode(encoded)) as SessionPayload;
  } catch (_error) {
    return null;
  }

  if (payload.typ !== "admin" || !payload.sub || !payload.role_id || !payload.exp) {
    return null;
  }
  if (payload.exp <= Math.floor(Date.now() / 1000)) {
    return null;
  }

  return payload;
}

export function createMerchantSessionToken(
  payload: Omit<MerchantSessionPayload, "iat" | "exp">,
  secret: string,
  ttlSeconds: number
) {
  const now = Math.floor(Date.now() / 1000);
  const body: MerchantSessionPayload = {
    ...payload,
    iat: now,
    exp: now + ttlSeconds
  };
  const encoded = base64UrlEncode(JSON.stringify(body));
  const signature = sign(encoded, secret);
  return `${encoded}.${signature}`;
}

export function verifyMerchantSessionToken(token: string, secret: string): MerchantSessionPayload | null {
  const [encoded, signature] = token.split(".");
  if (!encoded || !signature) {
    return null;
  }

  const expected = sign(encoded, secret);
  const expectedBuffer = Buffer.from(expected);
  const actualBuffer = Buffer.from(signature);
  if (expectedBuffer.length !== actualBuffer.length || !timingSafeEqual(expectedBuffer, actualBuffer)) {
    return null;
  }

  let payload: MerchantSessionPayload;
  try {
    payload = JSON.parse(base64UrlDecode(encoded)) as MerchantSessionPayload;
  } catch (_error) {
    return null;
  }

  if (payload.typ !== "merchant" || !payload.sub || !payload.merchant_id || !payload.exp) {
    return null;
  }
  if (payload.exp <= Math.floor(Date.now() / 1000)) {
    return null;
  }

  return payload;
}
