// node --experimental-strip-types --test
import { test } from "node:test";
import assert from "node:assert/strict";
import { apnsJwt, approvalPayload, sendApns, type ApnsConfig } from "../src/apns.ts";

function toPem(der: ArrayBuffer): string {
  const b64 = Buffer.from(new Uint8Array(der)).toString("base64").replace(/(.{64})/g, "$1\n");
  return `-----BEGIN PRIVATE KEY-----\n${b64}\n-----END PRIVATE KEY-----\n`;
}

async function freshKey() {
  const kp = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]);
  const pkcs8 = await crypto.subtle.exportKey("pkcs8", kp.privateKey);
  return { pem: toPem(pkcs8), publicKey: kp.publicKey };
}

const T0 = 1_767_322_245_000;

test("apnsJwt is a verifiable ES256 token with the right claims", async () => {
  const { pem, publicKey } = await freshKey();
  const cfg: ApnsConfig = {
    keyP8: pem, keyId: "ABCDE12345", teamId: "L7UDTQ6F5W", topic: "xyz.blakeslee.nucleic.remote", env: "sandbox",
  };
  const jwt = await apnsJwt(cfg, T0, {}); // fresh cache
  const [h, c, s] = jwt.split(".");
  const header = JSON.parse(Buffer.from(h, "base64url").toString());
  const claims = JSON.parse(Buffer.from(c, "base64url").toString());

  assert.equal(header.alg, "ES256");
  assert.equal(header.kid, "ABCDE12345");
  assert.equal(claims.iss, "L7UDTQ6F5W");
  assert.equal(claims.iat, Math.floor(T0 / 1000));

  const ok = await crypto.subtle.verify(
    { name: "ECDSA", hash: "SHA-256" },
    publicKey,
    Buffer.from(s, "base64url"),
    Buffer.from(`${h}.${c}`),
  );
  assert.equal(ok, true);
});

test("apnsJwt reuses a cached token within the window", async () => {
  const { pem } = await freshKey();
  const cfg: ApnsConfig = { keyP8: pem, keyId: "K", teamId: "T", topic: "xyz.blakeslee.nucleic.remote", env: "sandbox" };
  const cache = {};
  const a = await apnsJwt(cfg, T0, cache);
  const b = await apnsJwt(cfg, T0 + 10 * 60 * 1000, cache); // +10 min < 40 min
  assert.equal(a, b);
});

test("approvalPayload carries no content", () => {
  const p = approvalPayload();
  const aps = (p as { aps: Record<string, unknown> }).aps;
  assert.equal(aps["interruption-level"], "time-sensitive");
  assert.equal(/transcript|diff|prompt|secret/.test(JSON.stringify(p)), false);
});

test("sendApns posts to the sandbox host with APNS headers", async () => {
  const { pem } = await freshKey();
  const cfg: ApnsConfig = {
    keyP8: pem, keyId: "ABCDE12345", teamId: "L7UDTQ6F5W", topic: "xyz.blakeslee.nucleic.remote", env: "sandbox",
  };
  let captured: { url: string; headers: Record<string, string> } | null = null;
  const fakeFetch = (async (url: string, init: { headers: Record<string, string> }) => {
    captured = { url, headers: init.headers };
    return new Response(null, { status: 200, headers: { "apns-id": "xyz" } });
  }) as unknown as typeof fetch;

  const r = await sendApns(cfg, "devtoken", approvalPayload(), T0, fakeFetch);
  assert.equal(r.status, 200);
  assert.equal(r.id, "xyz");
  assert.ok(captured);
  assert.match(captured!.url, /^https:\/\/api\.sandbox\.push\.apple\.com\/3\/device\/devtoken$/);
  assert.equal(captured!.headers["apns-topic"], "xyz.blakeslee.nucleic.remote");
  assert.equal(captured!.headers["apns-push-type"], "alert");
  assert.match(captured!.headers["authorization"], /^bearer /);
});

test("sendApns surfaces an APNS rejection reason", async () => {
  const { pem } = await freshKey();
  const cfg: ApnsConfig = { keyP8: pem, keyId: "K", teamId: "T", topic: "xyz.blakeslee.nucleic.remote", env: "production" };
  const fakeFetch = (async () =>
    new Response(JSON.stringify({ reason: "BadDeviceToken" }), { status: 400 })) as unknown as typeof fetch;
  const r = await sendApns(cfg, "bad", approvalPayload(), T0, fakeFetch);
  assert.equal(r.status, 400);
  assert.equal(r.reason, "BadDeviceToken");
});
