// useChildSwitcher Hook 单测
// 依据：02-architecture-design.md §4.3 状态管理分层、ARB-020 §22.5 ISSUE-009 双层裁决
// 覆盖：switchChild 双层处理（前端 store + 后端 mutation） / hasMultipleChildren / currentChild 派生

import {
  describe,
  it,
  expect,
  beforeAll,
  afterAll,
  afterEach,
  beforeEach,
  vi,
} from "vitest";
import { renderHook, waitFor, act, 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 { useChildSwitcher } from "./useChildSwitcher";
import type { ChildInfo } from "@/types";

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

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

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

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

describe("useChildSwitcher", () => {
  it("加载成功后返回子女列表并默认选中第一个", async () => {
    const { result } = renderUseChildSwitcher();
    await waitFor(() => {
      expect(result.current.children).toHaveLength(2);
    });
    expect(result.current.currentChildId).toBe("student-001");
    expect(result.current.currentChild?.id).toBe("student-001");
    expect(result.current.currentChild?.name).toBe("张小明");
    expect(result.current.loading).toBe(false);
    expect(result.current.error).toBeUndefined();
  });

  it("hasMultipleChildren 多子女时为 true", async () => {
    const { result } = renderUseChildSwitcher();
    await waitFor(() => {
      expect(result.current.children).toHaveLength(2);
    });
    expect(result.current.hasMultipleChildren).toBe(true);
  });

  it("hasMultipleChildren 单子女时为 false", async () => {
    server.resetHandlers(
      graphql.query("MyChildren", () =>
        HttpResponse.json({
          data: {
            myChildren: [
              {
                id: "student-001",
                name: "张小明",
                grade: "grade.7",
                schoolName: "实验中学",
                classId: "class-7-1",
                className: "初一(1)班",
              },
            ],
          },
        }),
      ),
    );

    const { result } = renderUseChildSwitcher();
    await waitFor(() => {
      expect(result.current.children).toHaveLength(1);
    });
    expect(result.current.hasMultipleChildren).toBe(false);
  });

  it("hasMultipleChildren 归档子女不计入", async () => {
    const archivedChildren: ChildInfo[] = [
      {
        id: "student-001",
        name: "张小明",
        grade: "grade.7",
        schoolName: "实验中学",
        classId: "class-7-1",
        className: "初一(1)班",
        isArchived: true,
      },
      {
        id: "student-002",
        name: "张小红",
        grade: "grade.5",
        schoolName: "实验小学",
        classId: "class-5-2",
        className: "五年级(2)班",
      },
    ];
    server.resetHandlers(
      graphql.query("MyChildren", () =>
        HttpResponse.json({ data: { myChildren: archivedChildren } }),
      ),
    );

    const { result } = renderUseChildSwitcher();
    await waitFor(() => {
      expect(result.current.children).toHaveLength(2);
    });
    // 只有一个非归档 → hasMultipleChildren=false
    expect(result.current.hasMultipleChildren).toBe(false);
    // 默认选中第一个非归档
    expect(result.current.currentChildId).toBe("student-002");
  });

  it("switchChild 调用前端 store 切换（乐观更新）", async () => {
    const { result } = renderUseChildSwitcher();
    await waitFor(() => {
      expect(result.current.children).toHaveLength(2);
    });
    const initialId = result.current.currentChildId;
    expect(initialId).not.toBeNull();

    act(() => {
      // 切换到另一个子女
      const targetId =
        initialId === "student-001" ? "student-002" : "student-001";
      result.current.switchChild(targetId);
    });
    // 乐观更新：store 立即切换
    expect(useChildStore.getState().currentChildId).not.toBe(initialId);
  });

  it("switchChild 同时触发后端 SwitchChild mutation", async () => {
    const { result } = renderUseChildSwitcher();
    await waitFor(() => {
      expect(result.current.children).toHaveLength(2);
    });

    const fetchSpy = vi.spyOn(globalThis, "fetch");
    act(() => {
      result.current.switchChild("student-002");
    });
    // 等待 mutation fetch 发出
    await waitFor(() => {
      const gqlCalls = fetchSpy.mock.calls.filter(([url]) =>
        String(url).includes("/graphql"),
      );
      expect(gqlCalls.length).toBeGreaterThan(0);
    });
    // mutation body 含 childId
    const gqlCall = fetchSpy.mock.calls.find(([url]) =>
      String(url).includes("/graphql"),
    );
    const body = JSON.parse(String(gqlCall![1]?.body)) as {
      query?: string;
      variables?: { childId?: string };
    };
    expect(body.query).toContain("switchChild");
    expect(body.variables?.childId).toBe("student-002");
    fetchSpy.mockRestore();
  });

  it("mutation 失败时仅 warn 不回滚前端缓存", async () => {
    server.resetHandlers(
      graphql.query("MyChildren", () =>
        HttpResponse.json({
          data: {
            myChildren: [
              {
                id: "student-001",
                name: "张小明",
                grade: "grade.7",
                schoolName: "实验中学",
                classId: "class-7-1",
                className: "初一(1)班",
              },
              {
                id: "student-002",
                name: "张小红",
                grade: "grade.5",
                schoolName: "实验小学",
                classId: "class-5-2",
                className: "五年级(2)班",
              },
            ],
          },
        }),
      ),
      graphql.mutation("SwitchChild", () =>
        HttpResponse.json(
          { errors: [{ message: "审计失败" }] },
          { status: 200 },
        ),
      ),
    );

    const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
    const { result } = renderUseChildSwitcher();
    await waitFor(() => {
      expect(result.current.children).toHaveLength(2);
    });

    act(() => {
      result.current.switchChild("student-002");
    });
    // 乐观更新：缓存已切换
    expect(useChildStore.getState().currentChildId).toBe("student-002");
    // 等待 mutation 完成并 warn
    await waitFor(() => {
      expect(warnSpy).toHaveBeenCalled();
    });
    // 缓存不回滚
    expect(useChildStore.getState().currentChildId).toBe("student-002");
    warnSpy.mockRestore();
  });

  it("currentChildId 不在列表中时 setChildren 重置为有效值", async () => {
    useChildStore.setState({ currentChildId: "student-999" });
    const { result } = renderUseChildSwitcher();
    await waitFor(() => {
      expect(result.current.children).toHaveLength(2);
    });
    // store.setChildren 会重置无效 currentChildId
    expect(result.current.currentChild).not.toBeNull();
  });
});
