// node --experimental-strip-types --test
import { test } from "node:test";
import assert from "node:assert/strict";
import { authedHostId, enrollHost, registerPushToken, wakeableRecord } from "../src/push.ts";

/** Minimal KV stub — the get/put surface the push module uses. */
function kvStub() {
  const store = new Map<string, string>();
  const kv = {
    get: async (key: string) => store.get(key) ?? null,
    put: async (key: string, value: string) => {
      store.set(key, value);
    },
  } as unknown as KVNamespace;
  return { store, kv };
}

async function enroll(kv: KVNamespace): Promise<{ hostId: string; hostSecret: string }> {
  return (await (await enrollHost(kv)).json()) as { hostId: string; hostSecret: string };
}

test("enrollment mints a credential and stores only its hash", async () => {
  const { kv, store } = kvStub();
  const { hostId, hostSecret } = await enroll(kv);
  assert.ok(hostId.length >= 32);
  assert.equal(hostSecret.length, 64); // 32 random bytes, hex
  const stored = store.get(`hostauth:${hostId}`)!;
  assert.notEqual(stored, hostSecret); // hash, not the secret
  assert.equal(stored.length, 64); // sha-256 hex
});

test("bearer resolves to the enrolled host; forgeries don't", async () => {
  const { kv } = kvStub();
  const { hostId, hostSecret } = await enroll(kv);
  assert.equal(await authedHostId(kv, `${hostId}.${hostSecret}`), hostId);
  assert.equal(await authedHostId(kv, `${hostId}.${"0".repeat(64)}`), null); // wrong secret
  assert.equal(await authedHostId(kv, `nobody.${hostSecret}`), null); // unknown host
  assert.equal(await authedHostId(kv, null), null);
  assert.equal(await authedHostId(kv, "malformed"), null);
});

test("register validates, stores the owner, and defaults to sandbox", async () => {
  const { kv, store } = kvStub();
  assert.equal((await registerPushToken(kv, "h1", { deviceId: "d1" })).status, 422);
  assert.equal((await registerPushToken(kv, "h1", null)).status, 422);

  const res = await registerPushToken(kv, "h1", { deviceId: "iphone-abc", token: "feedbeef" });
  assert.equal(res.status, 200);
  const rec = JSON.parse(store.get("iphone-abc")!);
  assert.equal(rec.token, "feedbeef");
  assert.equal(rec.env, "sandbox");
  assert.equal(rec.hostId, "h1");
});

test("register stores the phone's APNs topic (per-channel bundle id) when given", async () => {
  const { kv, store } = kvStub();
  await registerPushToken(kv, "h1", {
    deviceId: "d-canary",
    token: "t",
    topic: "xyz.blakeslee.nucleic-remote.canary",
  });
  assert.equal(JSON.parse(store.get("d-canary")!).topic, "xyz.blakeslee.nucleic-remote.canary");
  // Omitted topic → absent (the caller falls back to the configured APNS_TOPIC).
  await registerPushToken(kv, "h1", { deviceId: "d-base", token: "t" });
  assert.equal(JSON.parse(store.get("d-base")!).topic, undefined);
});

test("register respects a production stamp and falls back on junk", async () => {
  const { kv, store } = kvStub();
  await registerPushToken(kv, "h1", { deviceId: "d2", token: "t2", env: "production" });
  assert.equal(JSON.parse(store.get("d2")!).env, "production");
  await registerPushToken(kv, "h1", { deviceId: "d3", token: "t3", env: "prod???" });
  assert.equal(JSON.parse(store.get("d3")!).env, "sandbox");
});

test("a host can wake only devices it registered; the admin can wake any", async () => {
  const { kv } = kvStub();
  await registerPushToken(kv, "host-a", { deviceId: "phone-a", token: "ta" });
  await registerPushToken(kv, "host-b", { deviceId: "phone-b", token: "tb" });

  assert.equal((await wakeableRecord(kv, "host-a", "phone-a", false))?.token, "ta");
  assert.equal(await wakeableRecord(kv, "host-a", "phone-b", false), null); // someone else's
  assert.equal(await wakeableRecord(kv, "host-a", "phone-x", false), null); // unknown
  assert.equal((await wakeableRecord(kv, "admin", "phone-b", true))?.token, "tb");
});

test("room-registered records are not wakeable out-of-band", async () => {
  const { kv, store } = kvStub();
  // As Room.webSocketMessage writes them: owner-tagged with the room's DO id.
  store.set("phone-r", JSON.stringify({ token: "tr", env: "sandbox", hostId: "room:do-1234" }));
  assert.equal(await wakeableRecord(kv, "host-a", "phone-r", false), null);
});

test("registration refreshes the owning host's credential", async () => {
  const { kv, store } = kvStub();
  const { hostId, hostSecret } = await enroll(kv);
  const before = store.get(`hostauth:${hostId}`);
  await registerPushToken(kv, hostId, { deviceId: "d9", token: "t9" });
  assert.equal(store.get(`hostauth:${hostId}`), before); // same hash survives the refresh
  assert.equal(await authedHostId(kv, `${hostId}.${hostSecret}`), hostId); // still valid
});
