// GraphQL client (urql) 单测
// 依据：02-architecture-design.md §4.2 GraphQL 接入、F9 裁决
// 端点：/api/v1/parent/v1/graphql（双 /v1 前缀，ARB-022 §24.4）
// 覆盖：createGraphQLClient / authedFetch 注入 Authorization / 401 刷新重试 / SSR 单例

import {
  describe,
  it,
  expect,
  beforeAll,
  afterAll,
  afterEach,
  beforeEach,
  vi,
} from "vitest";
import { Client } from "urql";
import { server } from "@/test/mocks/server";
import { http, HttpResponse } from "msw";
import { createGraphQLClient } from "./graphql-client";
import { setTokens, clearAuth, getToken, getRefreshToken } from "./auth";

beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

beforeEach(() => {
  clearAuth();
});

afterEach(() => {
  clearAuth();
  vi.restoreAllMocks();
});

describe("createGraphQLClient", () => {
  it("返回 urql Client 实例", () => {
    const client = createGraphQLClient();
    expect(client).toBeInstanceOf(Client);
  });

  it("每次调用返回新实例", () => {
    const a = createGraphQLClient();
    const b = createGraphQLClient();
    expect(a).not.toBe(b);
  });

  it("请求指向双 /v1 前缀端点（ARB-022 §24.4）", async () => {
    // 通过 MSW 验证请求 URL（行为测试，不依赖 urql Client 内部结构）
    let receivedUrl = "";
    server.use(
      http.post("/api/v1/parent/v1/graphql", ({ request }) => {
        receivedUrl = request.url;
        return HttpResponse.json({ data: { test: true } }, { status: 200 });
      }),
    );

    const client = createGraphQLClient();
    await client.query(`query Test { test }`, {}).toPromise();

    expect(receivedUrl).toContain("/api/v1/parent/v1/graphql");
  });

  it("无 token 时请求头不含 Authorization", async () => {
    clearAuth();
    let receivedAuthHeader: string | null = null;
    server.use(
      http.post("/api/v1/parent/v1/graphql", ({ request }) => {
        receivedAuthHeader = request.headers.get("Authorization");
        return HttpResponse.json({ data: { test: true } }, { status: 200 });
      }),
    );

    const client = createGraphQLClient();
    await client.query(`query Test { test }`, {}).toPromise();

    expect(receivedAuthHeader).toBeNull();
  });

  it("有 token 时请求头含 Authorization Bearer", async () => {
    setTokens("access-abc", "refresh-xyz", 3600);
    let receivedAuthHeader: string | null = null;
    server.use(
      http.post("/api/v1/parent/v1/graphql", ({ request }) => {
        receivedAuthHeader = request.headers.get("Authorization");
        return HttpResponse.json({ data: { test: true } }, { status: 200 });
      }),
    );

    const client = createGraphQLClient();
    await client.query(`query Test { test }`, {}).toPromise();

    expect(receivedAuthHeader).toBe("Bearer access-abc");
  });
});

describe("getGraphQLClient 单例（客户端环境）", () => {
  it("客户端环境（window 存在）返回同一单例", async () => {
    // 重置模块以获得干净的 client 单例状态
    vi.resetModules();
    const { getGraphQLClient } = await import("./graphql-client");
    const a = getGraphQLClient();
    const b = getGraphQLClient();
    expect(a).toBe(b);
  });

  it("SSR 环境（window 未定义）每次返回新实例", async () => {
    // 暂存 window
    const originalWindow = globalThis.window;
    // @ts-expect-error 测试环境强制删除 window 模拟 SSR
    delete globalThis.window;
    try {
      vi.resetModules();
      const { getGraphQLClient } = await import("./graphql-client");
      const a = getGraphQLClient();
      const b = getGraphQLClient();
      expect(a).not.toBe(b);
    } finally {
      globalThis.window = originalWindow;
    }
  });
});

describe("authedFetch 行为（通过实际 GraphQL 请求验证）", () => {
  it("401 响应触发 refreshAccessToken 重试", async () => {
    // 设置已有 token（含可用 refresh token）
    setTokens("expired-access", "mock-refresh-token-parent-001", 3600);

    // 第一次 401，第二次 200
    let callCount = 0;
    server.use(
      http.post("/api/v1/parent/v1/graphql", () => {
        callCount++;
        if (callCount === 1) {
          return HttpResponse.json(
            { errors: [{ message: "Unauthorized" }] },
            { status: 401 },
          );
        }
        return HttpResponse.json({ data: { test: true } }, { status: 200 });
      }),
    );

    const client = createGraphQLClient();
    const result = await client.query(`query Test { test }`, {}).toPromise();

    // 重试后应至少调用 2 次
    expect(callCount).toBeGreaterThanOrEqual(2);
    // 刷新后 access token 更新为 MSW fixture 中的 renewed 值
    expect(getToken()).toBe("mock-access-token-parent-001-renewed");
    // 不再报 401
    expect(result.error).toBeUndefined();
  });

  it("401 后无 refresh token 不重试（返回原响应）", async () => {
    // 不设置任何 token
    clearAuth();

    let callCount = 0;
    server.use(
      http.post("/api/v1/parent/v1/graphql", () => {
        callCount++;
        return HttpResponse.json(
          { errors: [{ message: "Unauthorized" }] },
          { status: 401 },
        );
      }),
    );

    const client = createGraphQLClient();
    await client.query(`query Test { test }`, {}).toPromise();

    // 仅一次请求，未重试
    expect(callCount).toBe(1);
    expect(getToken()).toBeNull();
    expect(getRefreshToken()).toBeNull();
  });

  it("200 响应不触发 refresh", async () => {
    setTokens("valid-access", "mock-refresh-token-parent-001", 3600);

    let callCount = 0;
    server.use(
      http.post("/api/v1/parent/v1/graphql", () => {
        callCount++;
        return HttpResponse.json({ data: { ok: true } }, { status: 200 });
      }),
    );

    const client = createGraphQLClient();
    await client.query(`query Test { ok }`, {}).toPromise();

    expect(callCount).toBe(1);
    // token 未被刷新
    expect(getToken()).toBe("valid-access");
  });
});
