import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import {
  ExternalApiError,
  externalRequest,
  readExternalCsrfToken,
  rememberExternalCsrfToken,
  setExternalUnauthorizedHandler
} from "./client.js";

describe("externalRequest", () => {
  beforeEach(() => {
    rememberExternalCsrfToken("");
    document.cookie = "hyapp_external_csrf=; Max-Age=0; Path=/";
  });

  afterEach(() => {
    setExternalUnauthorizedHandler(null);
    vi.unstubAllGlobals();
  });

  test("uses only the isolated cookie session and external API base", async () => {
    const fetchMock = vi.fn(async () => jsonResponse({ code: 0, data: { items: [] } }));
    vi.stubGlobal("fetch", fetchMock);

    await externalRequest("/app/users", { query: { keyword: "163000", page: 1 } });

    const [url, init] = fetchMock.mock.calls[0];
    expect(url).toBe("/api/v1/external/app/users?keyword=163000&page=1");
    expect(init.credentials).toBe("include");
    expect(init.headers.get("Authorization")).toBeNull();
    expect(init.headers.get("X-App-Code")).toBeNull();
    expect(init.headers.get("X-CSRF-Token")).toBeNull();
  });

  test("adds CSRF to writes from the dedicated cookie", async () => {
    document.cookie = "hyapp_external_csrf=csrf%20token; Path=/";
    const fetchMock = vi.fn(async () => jsonResponse({ code: 0, data: { ok: true } }));
    vi.stubGlobal("fetch", fetchMock);

    await externalRequest("/auth/change-password", { body: { currentPassword: "old", newPassword: "new" }, method: "POST" });

    const [, init] = fetchMock.mock.calls[0];
    expect(init.headers.get("X-CSRF-Token")).toBe("csrf token");
    expect(JSON.parse(init.body)).toEqual({ currentPassword: "old", newPassword: "new" });
  });

  test("keeps csrfToken in memory when the API-scoped cookie is unreadable", async () => {
    const fetchMock = vi
      .fn()
      .mockResolvedValueOnce(jsonResponse({ code: 0, data: { csrfToken: "memory-csrf", user: { username: "operator" } } }))
      .mockResolvedValueOnce(jsonResponse({ code: 0, data: {} }));
    vi.stubGlobal("fetch", fetchMock);

    await externalRequest("/auth/me");
    await externalRequest("/auth/logout", { method: "POST" });

    expect(fetchMock.mock.calls[1][1].headers.get("X-CSRF-Token")).toBe("memory-csrf");
    expect(readExternalCsrfToken("")).toBe("memory-csrf");
  });

  test("invalidates the external session on protected 401", async () => {
    const handler = vi.fn();
    setExternalUnauthorizedHandler(handler);
    vi.stubGlobal("fetch", vi.fn(async () => jsonResponse({ code: "UNAUTHORIZED", message: "请登录" }, 401)));

    await expect(externalRequest("/admin/rooms")).rejects.toEqual(expect.objectContaining({ status: 401 }));
    expect(handler).toHaveBeenCalledTimes(1);
    expect(new ExternalApiError("x")).toBeInstanceOf(Error);
  });
});

function jsonResponse(body, status = 200) {
  return new Response(JSON.stringify(body), { headers: { "content-type": "application/json" }, status });
}
