import { test, expect } from "bun:test";
import { createContext } from "@wrnexus/core";
import { createJwtKeyring, signJwt, verifyJwt, jwtAuth, JwtError } from "../src/index.ts";

const SECRET = "test-secret-key";

test("sign + verify round-trip preserves claims", async () => {
  const token = await signJwt({ sub: "u1", role: "admin" }, SECRET);
  const claims = await verifyJwt(token, SECRET);
  expect(claims.sub).toBe("u1");
  expect(claims.role).toBe("admin");
  expect(typeof claims.iat).toBe("number");
});

test("tampering or wrong secret fails verification", async () => {
  const token = await signJwt({ sub: "u1" }, SECRET);
  await expect(verifyJwt(token, "other-secret")).rejects.toThrow(JwtError);
  const tampered = token.slice(0, -2) + (token.endsWith("a") ? "bb" : "aa");
  await expect(verifyJwt(tampered, SECRET)).rejects.toThrow();
});

test("rejects a signed token whose header declares another algorithm", async () => {
  const token = await signJwt({ sub: "u1" }, SECRET);
  const [, body] = token.split(".");
  const header = btoa(JSON.stringify({ alg: "none", typ: "JWT" }))
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
  await expect(verifyJwt(`${header}.${body}.invalid`, SECRET)).rejects.toThrow(
    "Unsupported token header",
  );
});

test("expiry is enforced", async () => {
  const token = await signJwt({ sub: "u1" }, SECRET, { expiresIn: 100, now: 1000 });
  expect((await verifyJwt(token, SECRET, { now: 1050 })).sub).toBe("u1"); // still valid
  await expect(verifyJwt(token, SECRET, { now: 1200 })).rejects.toThrow("expired");
});

function ctxWith(auth?: string) {
  const url = new URL("http://x/api/me");
  return createContext(new Request(url, { headers: auth ? { authorization: auth } : {} }), url);
}

test("jwtAuth sets ctx.user for a valid bearer token", async () => {
  const token = await signJwt({ sub: "u9" }, SECRET);
  const ctx = ctxWith(`Bearer ${token}`);
  const res = await jwtAuth({ secret: SECRET })(ctx, () => new Response("ok"));
  expect(res.status).toBe(200);
  expect((ctx.user as { sub: string }).sub).toBe("u9");
});

test("jwtAuth 401s a missing/invalid token when required", async () => {
  const missing = await jwtAuth({ secret: SECRET })(ctxWith(), () => new Response("ok"));
  expect(missing.status).toBe(401);
  const bad = await jwtAuth({ secret: SECRET })(ctxWith("Bearer nope"), () => new Response("ok"));
  expect(bad.status).toBe(401);
});

test("jwtAuth optional mode passes through anonymously", async () => {
  const ctx = ctxWith();
  const res = await jwtAuth({ secret: SECRET, required: false })(ctx, () => new Response("ok"));
  expect(res.status).toBe(200);
  expect(ctx.user).toBeUndefined();
});

test("JWT keyrings reject duplicates and return defensive copies", () => {
  expect(() =>
    createJwtKeyring([
      { id: "one", secret: "secret-one" },
      { id: "one", secret: "secret-two" },
    ]),
  ).toThrow("DUPLICATE");

  const keyring = createJwtKeyring([{ id: "one", secret: "secret-one", active: true }]);
  const key = keyring.active();
  key.secret = "changed";
  expect(keyring.active().secret).toBe("secret-one");
});
