// useChildErrorBook Hook 单测
// 依据：02-architecture-design.md §4.2 GraphQL 接入
// 覆盖：无 currentChildId 暂停 / 加载态 / 成功返回 / 错误态 / 三个查询合并

import {
  describe,
  it,
  expect,
  beforeAll,
  afterAll,
  afterEach,
  beforeEach,
} from "vitest";
import { renderHook, waitFor, cleanup } from "@testing-library/react";
import {
  Provider as UrqlProvider,
  Client,
  cacheExchange,
  fetchExchange,
} from "urql";
import { graphql, HttpResponse } from "msw";
import type { ReactNode } from "react";
import { server } from "@/test/mocks/server";
import { useChildStore } from "@/store/child-store";
import { useChildErrorBook } from "./useChildErrorBook";

function createTestClient(): Client {
  return new Client({
    url: "/api/v1/parent/v1/graphql",
    exchanges: [cacheExchange, fetchExchange],
    fetchOptions: { headers: { "X-Requested-With": "XMLHttpRequest" } },
  });
}

function renderUseChildErrorBook() {
  const client = createTestClient();
  const wrapper = ({ children }: { children: ReactNode }) => (
    <UrqlProvider value={client}>{children}</UrqlProvider>
  );
  return renderHook(() => useChildErrorBook(), { wrapper });
}

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

beforeEach(() => {
  localStorage.clear();
  useChildStore.setState({
    children: [],
    currentChildId: null,
    isLoading: false,
  });
});

describe("useChildErrorBook", () => {
  it("无 currentChildId 时暂停查询，返回空数据", () => {
    const { result } = renderUseChildErrorBook();
    expect(result.current.stats).toBeNull();
    expect(result.current.topWrongQuestions).toEqual([]);
    expect(result.current.weakKps).toEqual([]);
    expect(result.current.loading).toBe(false);
    expect(result.current.error).toBeUndefined();
  });

  it("有 currentChildId 时加载成功返回错题本数据", async () => {
    useChildStore.setState({ currentChildId: "student-001" });
    const { result } = renderUseChildErrorBook();
    await waitFor(() => {
      expect(result.current.stats).not.toBeNull();
    });
    expect(result.current.stats?.totalCount).toBe(128);
    expect(result.current.stats?.newCount).toBe(23);
    expect(result.current.stats?.learningCount).toBe(45);
    expect(result.current.stats?.masteredCount).toBe(60);
    expect(result.current.stats?.dueReviewCount).toBe(18);
    expect(result.current.stats?.masteredRate).toBe(0.469);
    expect(result.current.topWrongQuestions).toHaveLength(4);
    expect(result.current.topWrongQuestions[0]!.subject).toBe("数学");
    expect(result.current.weakKps).toHaveLength(4);
    expect(result.current.weakKps[0]!.knowledgePointName).toBe("一元二次方程");
    expect(result.current.loading).toBe(false);
    expect(result.current.error).toBeUndefined();
  });

  it("加载中 loading 为 true", () => {
    useChildStore.setState({ currentChildId: "student-001" });
    const { result } = renderUseChildErrorBook();
    expect(result.current.loading).toBe(true);
  });

  it("ChildErrorBookStats 返回错误时透传 error", async () => {
    useChildStore.setState({ currentChildId: "student-001" });
    server.use(
      graphql.query("ChildErrorBookStats", () =>
        HttpResponse.json(
          { errors: [{ message: "错题统计查询失败" }] },
          { status: 200 },
        ),
      ),
    );

    const { result } = renderUseChildErrorBook();
    await waitFor(() => {
      expect(result.current.error).toBeDefined();
    });
    expect(result.current.stats).toBeNull();
    expect(result.current.error?.message).toContain("错题统计查询失败");
  });

  it("ChildTopWrongQuestions 返回错误时透传 error", async () => {
    useChildStore.setState({ currentChildId: "student-001" });
    server.use(
      graphql.query("ChildTopWrongQuestions", () =>
        HttpResponse.json(
          { errors: [{ message: "高频错题查询失败" }] },
          { status: 200 },
        ),
      ),
    );

    const { result } = renderUseChildErrorBook();
    await waitFor(() => {
      expect(result.current.error).toBeDefined();
    });
    expect(result.current.topWrongQuestions).toEqual([]);
    expect(result.current.error?.message).toContain("高频错题查询失败");
  });

  it("ChildWeakKps 返回错误时透传 error", async () => {
    useChildStore.setState({ currentChildId: "student-001" });
    server.use(
      graphql.query("ChildWeakKps", () =>
        HttpResponse.json(
          { errors: [{ message: "薄弱知识点查询失败" }] },
          { status: 200 },
        ),
      ),
    );

    const { result } = renderUseChildErrorBook();
    await waitFor(() => {
      expect(result.current.error).toBeDefined();
    });
    expect(result.current.weakKps).toEqual([]);
    expect(result.current.error?.message).toContain("薄弱知识点查询失败");
  });

  it("三个查询返回空数据时返回空数组与 null stats", async () => {
    useChildStore.setState({ currentChildId: "student-001" });
    server.use(
      graphql.query("ChildErrorBookStats", () =>
        HttpResponse.json({ data: { childErrorBookStats: null } }),
      ),
      graphql.query("ChildTopWrongQuestions", () =>
        HttpResponse.json({ data: { childTopWrongQuestions: [] } }),
      ),
      graphql.query("ChildWeakKps", () =>
        HttpResponse.json({ data: { childWeakKps: [] } }),
      ),
    );

    const { result } = renderUseChildErrorBook();
    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });
    expect(result.current.stats).toBeNull();
    expect(result.current.topWrongQuestions).toEqual([]);
    expect(result.current.weakKps).toEqual([]);
  });
});
