import test, { afterEach } from "node:test";
import assert from "node:assert/strict";
import { AdminApiError, fetchAdminJson, getAdminAccessState, getAdminErrorMessage } from "./api";

type MockFetch = typeof fetch;

const originalFetch = globalThis.fetch;

function installFetchMock(mock: MockFetch) {
  Object.defineProperty(globalThis, "fetch", {
    configurable: true,
    value: mock,
  });
}

afterEach(() => {
  installFetchMock(originalFetch);
});

test("fetchAdminJson includes cookie credentials", async () => {
  let seenCredentials: RequestCredentials | undefined;

  installFetchMock(async (_input, init) => {
    seenCredentials = init?.credentials;
    return new Response(JSON.stringify({ ok: true }), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    });
  });

  const response = await fetchAdminJson<{ ok: boolean }>("admin/access");

  assert.equal(seenCredentials, "include");
  assert.deepEqual(response, { ok: true });
});

test("fetchAdminJson classifies 401 as unauthorized", async () => {
  installFetchMock(async () => new Response(JSON.stringify({ detail: "Not authenticated" }), {
    status: 401,
    headers: { "Content-Type": "application/json" },
  }));

  await assert.rejects(
    () => fetchAdminJson("admin/access"),
    (error: unknown) =>
      error instanceof AdminApiError &&
      error.kind === "unauthorized" &&
      getAdminAccessState(error) === "unauthenticated" &&
      getAdminErrorMessage(error) === "Session expired. Log in again.",
  );
});

test("fetchAdminJson classifies 403 as forbidden", async () => {
  installFetchMock(async () => new Response(JSON.stringify({ detail: "Admin access required" }), {
    status: 403,
    headers: { "Content-Type": "application/json" },
  }));

  await assert.rejects(
    () => fetchAdminJson("admin/overview"),
    (error: unknown) =>
      error instanceof AdminApiError &&
      error.kind === "forbidden" &&
      getAdminAccessState(error) === "forbidden" &&
      getAdminErrorMessage(error) === "Admin access required.",
  );
});

test("fetchAdminJson classifies 5xx html as unavailable", async () => {
  installFetchMock(async () => new Response("<html><body>bad gateway</body></html>", {
    status: 502,
    headers: { "Content-Type": "text/html" },
  }));

  await assert.rejects(
    () => fetchAdminJson("admin/overview"),
    (error: unknown) =>
      error instanceof AdminApiError &&
      error.kind === "unavailable" &&
      getAdminErrorMessage(error) === "Admin service unavailable.",
  );
});

test("fetchAdminJson classifies network errors distinctly", async () => {
  installFetchMock(async () => {
    throw new TypeError("Failed to fetch");
  });

  await assert.rejects(
    () => fetchAdminJson("admin/overview"),
    (error: unknown) =>
      error instanceof AdminApiError &&
      error.kind === "network" &&
      getAdminAccessState(error) === "unavailable" &&
      getAdminErrorMessage(error) === "Cannot reach server.",
  );
});
