import { afterEach, expect, test, vi } from "vitest";
import { apiRequest, setAccessToken, setRefreshHandler, setUnauthorizedHandler } from "./request";

afterEach(() => {
  vi.useRealTimers();
  setAccessToken("");
  setRefreshHandler(null);
  setUnauthorizedHandler(null);
  vi.unstubAllGlobals();
});

test("retries once after refreshing a stale permission token on 403", async () => {
  setAccessToken("old-token");
  setRefreshHandler(async () => {
    setAccessToken("new-token");
    return true;
  });
  vi.stubGlobal(
    "fetch",
    vi.fn(async () => {
      if (vi.mocked(fetch).mock.calls.length === 1) {
        return new Response(JSON.stringify({ code: 403, message: "没有操作权限" }), { status: 403 });
      }
      return new Response(JSON.stringify({ code: 0, data: { ok: true } }));
    })
  );

  await expect(apiRequest("/v1/admin/countries", { body: {}, method: "POST" })).resolves.toEqual({ ok: true });

  expect(fetch).toHaveBeenCalledTimes(2);
  expect(vi.mocked(fetch).mock.calls[0][1]?.headers).toMatchObject({ Authorization: "Bearer old-token" });
  expect(vi.mocked(fetch).mock.calls[1][1]?.headers).toMatchObject({ Authorization: "Bearer new-token" });
});

test("shares one refresh request across concurrent stale access token responses", async () => {
  setAccessToken("old-token");
  let refreshCount = 0;
  setRefreshHandler(async () => {
    refreshCount += 1;
    await new Promise((resolve) => window.setTimeout(resolve, 0));
    setAccessToken("new-token");
    return true;
  });
  vi.stubGlobal(
    "fetch",
    vi.fn(async (_url, init) => {
      const auth = (init?.headers as Record<string, string> | undefined)?.Authorization;
      if (auth === "Bearer old-token") {
        return new Response(JSON.stringify({ code: 40100, message: "访问凭证已失效" }), { status: 401 });
      }
      return new Response(JSON.stringify({ code: 0, data: { ok: true } }));
    })
  );

  await expect(Promise.all([apiRequest("/v1/admin/countries"), apiRequest("/v1/admin/regions")])).resolves.toEqual([
    { ok: true },
    { ok: true }
  ]);

  expect(refreshCount).toBe(1);
  expect(fetch).toHaveBeenCalledTimes(4);
});

test("retries idempotent reads after a transient network failure", async () => {
  vi.useFakeTimers();
  vi.stubGlobal(
    "fetch",
    vi
      .fn()
      .mockRejectedValueOnce(new TypeError("Failed to fetch"))
      .mockResolvedValueOnce(new Response(JSON.stringify({ code: 0, data: { ok: true } })))
  );

  const request = apiRequest("/v1/admin/apps");

  await vi.advanceTimersByTimeAsync(350);
  await expect(request).resolves.toEqual({ ok: true });
  expect(fetch).toHaveBeenCalledTimes(2);
});

test("does not replay mutations after a network failure", async () => {
  vi.stubGlobal("fetch", vi.fn().mockRejectedValueOnce(new TypeError("Failed to fetch")));

  await expect(apiRequest("/v1/admin/countries", { body: {}, method: "POST" })).rejects.toThrow("Failed to fetch");
  expect(fetch).toHaveBeenCalledTimes(1);
});

test("preserves the HTTP status on API errors for optimistic concurrency handling", async () => {
  vi.stubGlobal(
    "fetch",
    vi.fn().mockResolvedValueOnce(
      new Response(JSON.stringify({ code: 409, message: "账号权限版本冲突" }), { status: 409 })
    )
  );

  await expect(apiRequest("/v1/admin/external-admin-users/71/permissions", { body: {}, method: "PUT" })).rejects.toMatchObject({
    message: "账号权限版本冲突",
    status: 409
  });
});
