// LeaveRequestForm 组件单测
// 依据：02-architecture-design.md §15 组件设计、project_rules §3.8 输入验证
// 覆盖：表单渲染 / 子女选项 / 类型选项 / 校验（必填 + reason 长度 + 日期范围）/ 提交成功 / loading 态 / error 显示

import { describe, it, expect, beforeEach, vi } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { LeaveRequestForm } from "./LeaveRequestForm";
import type { ChildInfo, LeaveRequestInput } from "@/types";
import type { CombinedError } from "urql";

// mock useMyChildren
const mockUseMyChildren = vi.fn();
vi.mock("@/hooks/useMyChildren", () => ({
  useMyChildren: () => mockUseMyChildren(),
}));

// mock useCreateLeaveRequest
const mockUseCreateLeaveRequest = vi.fn();
vi.mock("@/hooks/useCreateLeaveRequest", () => ({
  useCreateLeaveRequest: () => mockUseCreateLeaveRequest(),
}));

const mockChildren: ChildInfo[] = [
  {
    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)班",
  },
];

function createLeaveMockSuccess() {
  return vi.fn().mockResolvedValue({ success: true });
}

function setupHappyPath() {
  mockUseMyChildren.mockReturnValue({
    children: mockChildren,
    loading: false,
    error: undefined,
  });
  mockUseCreateLeaveRequest.mockReturnValue({
    createLeave: createLeaveMockSuccess(),
    loading: false,
    error: undefined,
  });
}

beforeEach(() => {
  mockUseMyChildren.mockReset();
  mockUseCreateLeaveRequest.mockReset();
});

describe("LeaveRequestForm 渲染", () => {
  beforeEach(setupHappyPath);

  it("渲染表单标题", () => {
    render(<LeaveRequestForm />);
    expect(screen.getByText("提交请假申请")).toBeInTheDocument();
  });

  it("渲染所有字段标签", () => {
    render(<LeaveRequestForm />);
    expect(screen.getByText("子女")).toBeInTheDocument();
    expect(screen.getByText("请假类型")).toBeInTheDocument();
    expect(screen.getByText("开始日期")).toBeInTheDocument();
    expect(screen.getByText("结束日期")).toBeInTheDocument();
    expect(screen.getByText("请假原因")).toBeInTheDocument();
  });

  it("渲染提交按钮", () => {
    render(<LeaveRequestForm />);
    expect(screen.getByRole("button", { name: "提交申请" })).toBeInTheDocument();
  });

  it("渲染子女选项（2 个）", () => {
    render(<LeaveRequestForm />);
    const select = screen.getByLabelText("子女") as HTMLSelectElement;
    const options = select.querySelectorAll("option");
    // 2 个子女
    expect(options).toHaveLength(2);
    expect(options[0]).toHaveTextContent("张小明");
    expect(options[1]).toHaveTextContent("张小红");
  });

  it("渲染请假类型选项（4 种）", () => {
    render(<LeaveRequestForm />);
    const select = screen.getByLabelText("请假类型") as HTMLSelectElement;
    const options = select.querySelectorAll("option");
    expect(options).toHaveLength(4);
    expect(options[0]).toHaveTextContent("病假");
    expect(options[1]).toHaveTextContent("事假");
    expect(options[2]).toHaveTextContent("家庭假");
    expect(options[3]).toHaveTextContent("其他");
  });
});

describe("LeaveRequestForm 校验", () => {
  beforeEach(setupHappyPath);

  it("空表单提交显示必填错误", async () => {
    const user = userEvent.setup();
    // 先清空 reason（虽然默认已空，但确保 startDate/endDate 也是空）
    render(<LeaveRequestForm />);
    await user.click(screen.getByRole("button", { name: "提交申请" }));
    // childId 默认有值（第一个子女），所以不会报错；但 startDate/endDate/reason 会报错
    await waitFor(() => {
      expect(screen.getByText("请选择开始日期")).toBeInTheDocument();
      expect(screen.getByText("请选择结束日期")).toBeInTheDocument();
      expect(screen.getByText("请假原因至少 10 个字")).toBeInTheDocument();
    });
  });

  it("请假原因不足 10 字显示错误", async () => {
    const user = userEvent.setup();
    render(<LeaveRequestForm />);
    await user.type(screen.getByLabelText("请假原因"), "感冒了");
    await user.click(screen.getByRole("button", { name: "提交申请" }));
    await waitFor(() => {
      expect(screen.getByText("请假原因至少 10 个字")).toBeInTheDocument();
    });
  });

  it("结束日期早于开始日期显示错误", async () => {
    const user = userEvent.setup();
    render(<LeaveRequestForm />);
    await user.type(screen.getByLabelText("开始日期"), "2026-07-15");
    await user.type(screen.getByLabelText("结束日期"), "2026-07-10");
    await user.type(
      screen.getByLabelText("请假原因"),
      "家中临时有事需要陪同办理",
    );
    await user.click(screen.getByRole("button", { name: "提交申请" }));
    await waitFor(() => {
      expect(screen.getByText("结束日期不能早于开始日期")).toBeInTheDocument();
    });
  });
});

describe("LeaveRequestForm 提交", () => {
  beforeEach(setupHappyPath);

  it("提交成功调用 createLeave 传入正确 input", async () => {
    const user = userEvent.setup();
    const createLeave = createLeaveMockSuccess();
    mockUseCreateLeaveRequest.mockReturnValue({
      createLeave,
      loading: false,
      error: undefined,
    });

    render(<LeaveRequestForm />);
    // childId 默认 student-001，type 默认 sick
    await user.type(screen.getByLabelText("开始日期"), "2026-07-15");
    await user.type(screen.getByLabelText("结束日期"), "2026-07-16");
    await user.type(
      screen.getByLabelText("请假原因"),
      "感冒发烧需要在家休息观察",
    );
    await user.click(screen.getByRole("button", { name: "提交申请" }));

    await waitFor(() => {
      expect(createLeave).toHaveBeenCalledTimes(1);
    });
    const expectedInput: LeaveRequestInput = {
      childId: "student-001",
      type: "sick",
      startDate: "2026-07-15",
      endDate: "2026-07-16",
      reason: "感冒发烧需要在家休息观察",
    };
    expect(createLeave).toHaveBeenCalledWith(expectedInput);
  });

  it("提交成功后调用 onSuccess 回调", async () => {
    const user = userEvent.setup();
    const onSuccess = vi.fn();
    mockUseCreateLeaveRequest.mockReturnValue({
      createLeave: createLeaveMockSuccess(),
      loading: false,
      error: undefined,
    });

    render(<LeaveRequestForm onSuccess={onSuccess} />);
    await user.type(screen.getByLabelText("开始日期"), "2026-07-15");
    await user.type(screen.getByLabelText("结束日期"), "2026-07-16");
    await user.type(
      screen.getByLabelText("请假原因"),
      "感冒发烧需要在家休息观察",
    );
    await user.click(screen.getByRole("button", { name: "提交申请" }));

    await waitFor(() => {
      expect(onSuccess).toHaveBeenCalledTimes(1);
    });
  });

  it("提交成功后重置表单（reason 清空）", async () => {
    const user = userEvent.setup();
    mockUseCreateLeaveRequest.mockReturnValue({
      createLeave: createLeaveMockSuccess(),
      loading: false,
      error: undefined,
    });

    render(<LeaveRequestForm />);
    const reasonInput = screen.getByLabelText("请假原因") as HTMLTextAreaElement;
    await user.type(reasonInput, "感冒发烧需要在家休息观察");
    await user.type(screen.getByLabelText("开始日期"), "2026-07-15");
    await user.type(screen.getByLabelText("结束日期"), "2026-07-16");
    await user.click(screen.getByRole("button", { name: "提交申请" }));

    await waitFor(() => {
      expect(reasonInput.value).toBe("");
    });
  });

  it("createLeave 返回 failure 时不调用 onSuccess", async () => {
    const user = userEvent.setup();
    const onSuccess = vi.fn();
    const createLeave = vi
      .fn()
      .mockResolvedValue({ success: false, error: { message: "失败" } as unknown as CombinedError });
    mockUseCreateLeaveRequest.mockReturnValue({
      createLeave,
      loading: false,
      error: undefined,
    });

    render(<LeaveRequestForm onSuccess={onSuccess} />);
    await user.type(screen.getByLabelText("开始日期"), "2026-07-15");
    await user.type(screen.getByLabelText("结束日期"), "2026-07-16");
    await user.type(
      screen.getByLabelText("请假原因"),
      "感冒发烧需要在家休息观察",
    );
    await user.click(screen.getByRole("button", { name: "提交申请" }));

    await waitFor(() => {
      expect(createLeave).toHaveBeenCalled();
    });
    // 等待一拍确保 onSuccess 不会被调用
    await new Promise((resolve) => setTimeout(resolve, 50));
    expect(onSuccess).not.toHaveBeenCalled();
  });
});

describe("LeaveRequestForm loading 态", () => {
  it("loading=true 时按钮禁用并显示提交中", () => {
    mockUseMyChildren.mockReturnValue({
      children: mockChildren,
      loading: false,
      error: undefined,
    });
    mockUseCreateLeaveRequest.mockReturnValue({
      createLeave: createLeaveMockSuccess(),
      loading: true,
      error: undefined,
    });

    render(<LeaveRequestForm />);
    const button = screen.getByRole("button");
    expect(button).toBeDisabled();
    expect(button).toHaveTextContent("提交中...");
  });
});

describe("LeaveRequestForm error 显示", () => {
  it("有 error 时渲染错误信息", () => {
    mockUseMyChildren.mockReturnValue({
      children: mockChildren,
      loading: false,
      error: undefined,
    });
    const mockError = { message: "服务器错误" } as unknown as CombinedError;
    mockUseCreateLeaveRequest.mockReturnValue({
      createLeave: createLeaveMockSuccess(),
      loading: false,
      error: mockError,
    });

    render(<LeaveRequestForm />);
    expect(screen.getByText("提交失败：服务器错误")).toBeInTheDocument();
  });
});
