import { describe, expect, it, vi } from "vitest";

async function loadApi(apiUrl = "") {
  vi.resetModules();
  vi.stubEnv("VITE_API_URL", apiUrl);
  vi.stubEnv("VITE_API_PREFIX", "");
  return import("./api");
}

function jsonResponse(body: unknown, init?: ResponseInit) {
  return new Response(JSON.stringify(body), {
    status: 200,
    headers: { "Content-Type": "application/json" },
    ...init,
  });
}

describe("api helpers", () => {
  it("normalizes nullish API arrays", async () => {
    const { itemsOrEmpty } = await loadApi();

    expect(itemsOrEmpty(null)).toEqual([]);
    expect(itemsOrEmpty(undefined)).toEqual([]);
    expect(itemsOrEmpty(["a"])).toEqual(["a"]);
  });

  it("builds asset URLs from API base while preserving absolute URLs", async () => {
    const { assetUrl } = await loadApi("https://api.example.com");

    expect(assetUrl("/uploads/file.png")).toBe(
      "https://api.example.com/uploads/file.png",
    );
    expect(assetUrl("https://cdn.example.com/file.png")).toBe(
      "https://cdn.example.com/file.png",
    );
    expect(assetUrl(undefined)).toBe("");
  });

  it("fetches JSON with API base and throws response text on failure", async () => {
    const { getJSON } = await loadApi("https://api.example.com");
    const fetchMock = vi
      .fn()
      .mockResolvedValueOnce(jsonResponse({ ok: true }))
      .mockResolvedValueOnce(new Response("boom", { status: 500 }));
    vi.stubGlobal("fetch", fetchMock);

    await expect(getJSON("/api/resources")).resolves.toEqual({ ok: true });
    expect(fetchMock).toHaveBeenCalledWith(
      "https://api.example.com/api/resources",
    );
    await expect(getJSON("/api/fail")).rejects.toThrow("boom");
  });

  it("cleans expired JSON cache entries", async () => {
    vi.useFakeTimers();
    try {
      vi.setSystemTime(new Date("2026-05-31T00:00:00Z"));
      const { getJSON, readJSONCache } = await loadApi();
      const fetchMock = vi.fn().mockResolvedValue(jsonResponse({ items: [1] }));
      vi.stubGlobal("fetch", fetchMock);

      await getJSON("/api/posts");
      expect(readJSONCache("/api/posts")).toEqual({ items: [1] });

      vi.setSystemTime(new Date("2026-05-31T00:05:01Z"));
      expect(readJSONCache("/api/posts")).toBeNull();
      expect(window.localStorage.length).toBe(0);
    } finally {
      vi.useRealTimers();
    }
  });

  it("prunes old JSON cache entries beyond the entry limit", async () => {
    vi.useFakeTimers();
    try {
      const { getJSON, readJSONCache } = await loadApi();
      const fetchMock = vi.fn((url: string) =>
        Promise.resolve(jsonResponse({ url })),
      );
      vi.stubGlobal("fetch", fetchMock);

      for (let index = 0; index < 81; index += 1) {
        vi.setSystemTime(new Date(2026, 4, 31, 0, 0, index));
        await getJSON(`/api/cache-${index}`);
      }

      expect(readJSONCache("/api/cache-0")).toBeNull();
      expect(readJSONCache("/api/cache-80")).toEqual({ url: "/api/cache-80" });
      expect(window.localStorage.length).toBe(80);
    } finally {
      vi.useRealTimers();
    }
  });

  it("posts JSON with optional bearer token", async () => {
    const { postJSON } = await loadApi();
    const fetchMock = vi.fn().mockResolvedValue(jsonResponse({ id: 1 }));
    vi.stubGlobal("fetch", fetchMock);

    await expect(
      postJSON("/api/admin/resources", { title: "Demo" }, "token-123"),
    ).resolves.toEqual({ id: 1 });

    expect(fetchMock).toHaveBeenCalledWith("/api/admin/resources", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: "Bearer token-123",
      },
      body: JSON.stringify({ title: "Demo" }),
    });
  });
});
