// useRealtimeNotifications Hook 单测
// 依据：02-architecture-design.md §5 实时推送
// 覆盖：各 WebSocketEvent 类型 → CustomEvent 分发 / status 透传

import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { renderHook, cleanup } from "@testing-library/react";
import type { WebSocketEvent } from "@/types";

// ===== Mock useWebSocket =====
// 捕获 onEvent 回调，便于在测试中手动触发各类事件
let capturedOnEvent: ((event: WebSocketEvent) => void) | null = null;
let mockWsStatus = "disconnected";

vi.mock("./useWebSocket", () => ({
  useWebSocket: vi.fn(
    (options: {
      onEvent?: (event: WebSocketEvent) => void;
      enabled?: boolean;
    }) => {
      capturedOnEvent = options.onEvent ?? null;
      return { status: mockWsStatus, reconnect: vi.fn() };
    },
  ),
}));

import { useRealtimeNotifications } from "./useRealtimeNotifications";
import type { NotificationItem, ChildInfo } from "@/types";

beforeEach(() => {
  capturedOnEvent = null;
  mockWsStatus = "disconnected";
});

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

describe("useRealtimeNotifications", () => {
  it("透传 useWebSocket 的 status", () => {
    mockWsStatus = "connected";
    const { result } = renderHook(() => useRealtimeNotifications());
    expect(result.current.status).toBe("connected");
  });

  it("初始 status 为 disconnected（默认）", () => {
    const { result } = renderHook(() => useRealtimeNotifications());
    expect(result.current.status).toBe("disconnected");
  });

  it("NotificationRequested 事件分发 realtime-notification CustomEvent", () => {
    const listener = vi.fn();
    window.addEventListener("realtime-notification", listener);

    renderHook(() => useRealtimeNotifications());
    expect(capturedOnEvent).not.toBeNull();

    const notification: NotificationItem = {
      id: "notif-001",
      childId: "student-001",
      eventType: "grade_recorded",
      title: "成绩发布",
      body: "数学成绩已发布",
      read: false,
      createdAt: new Date().toISOString(),
    };

    capturedOnEvent!({ type: "NotificationRequested", notification });

    expect(listener).toHaveBeenCalledTimes(1);
    const event = listener.mock.calls[0]?.[0] as CustomEvent;
    expect(event.detail).toEqual(notification);

    window.removeEventListener("realtime-notification", listener);
  });

  it("GradeRecorded 事件分发 realtime-grade CustomEvent", () => {
    const listener = vi.fn();
    window.addEventListener("realtime-grade", listener);

    renderHook(() => useRealtimeNotifications());
    expect(capturedOnEvent).not.toBeNull();

    capturedOnEvent!({
      type: "GradeRecorded",
      childId: "student-001",
      examId: "exam-001",
      examName: "期中数学",
      score: 95,
    });

    expect(listener).toHaveBeenCalledTimes(1);
    const event = listener.mock.calls[0]?.[0] as CustomEvent;
    expect(event.detail).toEqual({
      childId: "student-001",
      examId: "exam-001",
    });

    window.removeEventListener("realtime-grade", listener);
  });

  it("SchoolAnnouncement 事件分发 realtime-announcement CustomEvent", () => {
    const listener = vi.fn();
    window.addEventListener("realtime-announcement", listener);

    renderHook(() => useRealtimeNotifications());
    expect(capturedOnEvent).not.toBeNull();

    capturedOnEvent!({
      type: "SchoolAnnouncement",
      title: "放假通知",
      body: "暑假从7月15日开始",
    });

    expect(listener).toHaveBeenCalledTimes(1);
    const event = listener.mock.calls[0]?.[0] as CustomEvent;
    expect(event.detail).toEqual({
      title: "放假通知",
      body: "暑假从7月15日开始",
    });

    window.removeEventListener("realtime-announcement", listener);
  });

  it("ChildBound 事件分发 realtime-child-bound CustomEvent（无 detail）", () => {
    const listener = vi.fn();
    window.addEventListener("realtime-child-bound", listener);

    renderHook(() => useRealtimeNotifications());
    expect(capturedOnEvent).not.toBeNull();

    const child: ChildInfo = {
      id: "student-003",
      name: "张小刚",
      grade: "grade.3",
      schoolName: "实验小学",
      classId: "class-3-1",
    };

    capturedOnEvent!({ type: "ChildBound", child });

    expect(listener).toHaveBeenCalledTimes(1);

    window.removeEventListener("realtime-child-bound", listener);
  });

  it("ChildUnbound 事件分发 realtime-child-unbound CustomEvent", () => {
    const listener = vi.fn();
    window.addEventListener("realtime-child-unbound", listener);

    renderHook(() => useRealtimeNotifications());
    expect(capturedOnEvent).not.toBeNull();

    capturedOnEvent!({ type: "ChildUnbound", childId: "student-001" });

    expect(listener).toHaveBeenCalledTimes(1);
    const event = listener.mock.calls[0]?.[0] as CustomEvent;
    expect(event.detail).toEqual({ childId: "student-001" });

    window.removeEventListener("realtime-child-unbound", listener);
  });

  it("handleEvent 使用 useCallback 保持引用稳定", () => {
    const { rerender } = renderHook(() => useRealtimeNotifications());
    const firstOnEvent = capturedOnEvent;

    rerender();

    // useCallback deps=[]，引用应保持不变
    expect(capturedOnEvent).toBe(firstOnEvent);
  });

  it("向 useWebSocket 传递 enabled=true", async () => {
    const { useWebSocket } = await import("./useWebSocket");
    const mockedUseWebSocket = vi.mocked(useWebSocket);

    renderHook(() => useRealtimeNotifications());

    expect(mockedUseWebSocket).toHaveBeenCalled();
    const callArgs = mockedUseWebSocket.mock.calls[0]?.[0];
    expect(callArgs?.enabled).toBe(true);
  });

  it("多个不同类型事件依次分发各自 CustomEvent", () => {
    const notifListener = vi.fn();
    const gradeListener = vi.fn();
    window.addEventListener("realtime-notification", notifListener);
    window.addEventListener("realtime-grade", gradeListener);

    renderHook(() => useRealtimeNotifications());
    expect(capturedOnEvent).not.toBeNull();

    const notification: NotificationItem = {
      id: "notif-002",
      childId: null,
      eventType: "school_announcement",
      title: "test",
      body: "test body",
      read: false,
      createdAt: new Date().toISOString(),
    };

    capturedOnEvent!({ type: "NotificationRequested", notification });
    capturedOnEvent!({
      type: "GradeRecorded",
      childId: "student-002",
      examId: "exam-005",
      examName: "期末",
      score: 88,
    });

    expect(notifListener).toHaveBeenCalledTimes(1);
    expect(gradeListener).toHaveBeenCalledTimes(1);

    window.removeEventListener("realtime-notification", notifListener);
    window.removeEventListener("realtime-grade", gradeListener);
  });

  it("卸载后不再分发事件（组件已清理）", () => {
    const listener = vi.fn();
    window.addEventListener("realtime-notification", listener);

    const { unmount } = renderHook(() => useRealtimeNotifications());
    expect(capturedOnEvent).not.toBeNull();

    unmount();

    // capturedOnEvent 仍持有旧引用，但组件已卸载
    // window 事件监听器是在 handleEvent 中即时 addEventListener 的
    // 这里验证卸载不抛出
    expect(() => unmount()).not.toThrow();

    window.removeEventListener("realtime-notification", listener);
  });
});
