// AttendanceCalendar 组件单测
// 依据：02-architecture-design.md §15.5 AttendanceCalendar 设计
// 覆盖：日历网格渲染 / 状态颜色标记 / 统计 / 空数据 / 月份标题 / 月份导航

import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { AttendanceCalendar } from "./AttendanceCalendar";
import type { AttendanceRecord } from "@/types";

// 固定当前时间为 2026-06-15（6 月有 30 天，1 日是周一）
const FIXED_DATE = new Date("2026-06-15T12:00:00Z");

beforeEach(() => {
  vi.useFakeTimers();
  vi.setSystemTime(FIXED_DATE);
});

afterEach(() => {
  vi.useRealTimers();
});

// 生成 2026-06 的考勤记录
function makeJuneRecords(
  overrides: Partial<AttendanceRecord>[] = [],
): AttendanceRecord[] {
  const records: AttendanceRecord[] = [];
  // 6/1 ~ 6/5 出勤
  for (let d = 1; d <= 5; d++) {
    records.push({
      id: `att-${d}`,
      date: `2026-06-${String(d).padStart(2, "0")}`,
      status: "present",
    });
  }
  // 6/8 迟到
  records.push({
    id: "att-late",
    date: "2026-06-08",
    status: "late",
  });
  // 6/10 缺勤
  records.push({
    id: "att-absent",
    date: "2026-06-10",
    status: "absent",
  });
  // 6/12 请假
  records.push({
    id: "att-leave",
    date: "2026-06-12",
    status: "leave",
  });
  // 应用覆盖
  for (const o of overrides) {
    const idx = records.findIndex((r) => r.date === o.date);
    if (idx >= 0) {
      records[idx] = { ...records[idx]!, ...o };
    } else {
      records.push(o as AttendanceRecord);
    }
  }
  return records;
}

describe("AttendanceCalendar 标题与图例", () => {
  it("渲染当前年月标题（2026年6月）", () => {
    render(<AttendanceCalendar records={[]} />);
    expect(screen.getByText(/2026年6月考勤/)).toBeInTheDocument();
  });

  it("渲染 4 种状态图例标签", () => {
    render(<AttendanceCalendar records={[]} />);
    // 图例 + 统计行均含这些标签，使用 getAllByText 验证至少存在
    expect(screen.getAllByText("出勤").length).toBeGreaterThanOrEqual(2);
    expect(screen.getAllByText("迟到").length).toBeGreaterThanOrEqual(2);
    expect(screen.getAllByText("缺勤").length).toBeGreaterThanOrEqual(2);
    expect(screen.getAllByText("请假").length).toBeGreaterThanOrEqual(2);
  });

  it("渲染星期表头（日一二三四五六）", () => {
    render(<AttendanceCalendar records={[]} />);
    for (const w of ["日", "一", "二", "三", "四", "五", "六"]) {
      expect(screen.getByText(w)).toBeInTheDocument();
    }
  });
});

describe("AttendanceCalendar 日历网格", () => {
  it("渲染 6 月所有日期 1-30", () => {
    render(<AttendanceCalendar records={[]} />);
    for (let d = 1; d <= 30; d++) {
      expect(screen.getByText(String(d))).toBeInTheDocument();
    }
  });

  it("空记录数组时所有日期无状态标记点", () => {
    const { container } = render(<AttendanceCalendar records={[]} />);
    // 有记录时日期单元格内会有一个小圆点 span（mt-0.5 + rounded-full）
    const dots = container.querySelectorAll(".mt-0\\.5");
    expect(dots.length).toBe(0);
  });

  it("有记录的日期显示状态标记点", () => {
    const records = makeJuneRecords();
    const { container } = render(<AttendanceCalendar records={records} />);
    const dots = container.querySelectorAll(".mt-0\\.5");
    // 5(出勤) + 1(迟到) + 1(缺勤) + 1(请假) = 8 个标记点
    expect(dots.length).toBe(8);
  });

  it("有记录的日期单元格 title 属性显示状态标签", () => {
    const records: AttendanceRecord[] = [
      { id: "a", date: "2026-06-01", status: "present" },
      { id: "b", date: "2026-06-02", status: "late" },
    ];
    const { container } = render(<AttendanceCalendar records={records} />);
    // 直接通过 title 属性定位日期单元格（避免与统计数字冲突）
    const presentCell = container.querySelector('[title="出勤"]');
    expect(presentCell).not.toBeNull();
    expect(presentCell).toHaveTextContent("1");
    const lateCell = container.querySelector('[title="迟到"]');
    expect(lateCell).not.toBeNull();
    expect(lateCell).toHaveTextContent("2");
  });
});

describe("AttendanceCalendar 统计", () => {
  it("统计各状态数量", () => {
    const records = makeJuneRecords();
    render(<AttendanceCalendar records={records} />);
    // 出勤 5 / 迟到 1 / 缺勤 1 / 请假 1
    const presentStat = screen.getByText("5", { selector: ".text-success" });
    expect(presentStat).toBeInTheDocument();
    const lateStat = screen.getByText("1", { selector: ".text-warning" });
    expect(lateStat).toBeInTheDocument();
    const absentStat = screen.getByText("1", { selector: ".text-danger" });
    expect(absentStat).toBeInTheDocument();
  });

  it("空记录时所有统计为 0", () => {
    render(<AttendanceCalendar records={[]} />);
    const stats = screen.getAllByText("0");
    expect(stats.length).toBeGreaterThanOrEqual(3);
  });

  it("渲染统计标签文案", () => {
    render(<AttendanceCalendar records={[]} />);
    // 统计行：出勤 / 迟到 / 缺勤 / 请假
    const labels = screen.getAllByText(/出勤|迟到|缺勤|请假/);
    // 至少 4 个统计标签（图例也有这些标签，所以会更多）
    expect(labels.length).toBeGreaterThanOrEqual(4);
  });
});

describe("AttendanceCalendar 空数据", () => {
  it("空记录数组时仍渲染日历骨架", () => {
    render(<AttendanceCalendar records={[]} />);
    expect(screen.getByText(/2026年6月考勤/)).toBeInTheDocument();
    // 日期 1-30 仍然渲染
    expect(screen.getByText("15")).toBeInTheDocument();
  });
});

describe("AttendanceCalendar 月份导航", () => {
  it("未传 onMonthChange 时不渲染导航按钮（向后兼容）", () => {
    render(<AttendanceCalendar records={[]} />);
    expect(screen.queryByRole("button", { name: "上一月" })).not.toBeInTheDocument();
    expect(screen.queryByRole("button", { name: "下一月" })).not.toBeInTheDocument();
  });

  it("传入 onMonthChange 时渲染上一月/下一月按钮", () => {
    render(
      <AttendanceCalendar
        records={[]}
        month="2026-06"
        onMonthChange={vi.fn()}
      />,
    );
    expect(screen.getByRole("button", { name: "上一月" })).toBeInTheDocument();
    expect(screen.getByRole("button", { name: "下一月" })).toBeInTheDocument();
  });

  it("传入 month prop 时渲染对应月份标题", () => {
    render(
      <AttendanceCalendar
        records={[]}
        month="2026-05"
        onMonthChange={vi.fn()}
      />,
    );
    expect(screen.getByText(/2026年5月考勤/)).toBeInTheDocument();
  });

  it("点击上一月按钮调用 onMonthChange 传 2026-05", () => {
    const onMonthChange = vi.fn();
    render(
      <AttendanceCalendar
        records={[]}
        month="2026-06"
        onMonthChange={onMonthChange}
      />,
    );
    fireEvent.click(screen.getByRole("button", { name: "上一月" }));
    expect(onMonthChange).toHaveBeenCalledWith("2026-05");
  });

  it("点击下一月按钮调用 onMonthChange 传 2026-07", () => {
    const onMonthChange = vi.fn();
    render(
      <AttendanceCalendar
        records={[]}
        month="2026-06"
        onMonthChange={onMonthChange}
      />,
    );
    fireEvent.click(screen.getByRole("button", { name: "下一月" }));
    expect(onMonthChange).toHaveBeenCalledWith("2026-07");
  });

  it("跨年导航：2026-01 上一月为 2025-12", () => {
    const onMonthChange = vi.fn();
    render(
      <AttendanceCalendar
        records={[]}
        month="2026-01"
        onMonthChange={onMonthChange}
      />,
    );
    fireEvent.click(screen.getByRole("button", { name: "上一月" }));
    expect(onMonthChange).toHaveBeenCalledWith("2025-12");
  });

  it("跨年导航：2026-12 下一月为 2027-01", () => {
    const onMonthChange = vi.fn();
    render(
      <AttendanceCalendar
        records={[]}
        month="2026-12"
        onMonthChange={onMonthChange}
      />,
    );
    fireEvent.click(screen.getByRole("button", { name: "下一月" }));
    expect(onMonthChange).toHaveBeenCalledWith("2027-01");
  });
});
