/**
 * useAntiCheat Hook 测试
 *
 * 测试防作弊 hook 的核心逻辑：
 * - 事件监听（copy / paste / contextmenu / visibilitychange）
 * - 违规记录
 * - 多次切屏后显示警告
 */

import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { useAntiCheat } from "./use-anti-cheat";

// ClipboardEvent 在 jsdom 中未实现，需要 polyfill
if (typeof globalThis.ClipboardEvent === "undefined") {
  class ClipboardEventPolyfill extends Event {
    clipboardData: DataTransfer | null;
    constructor(type: string, eventInit?: EventInit & { clipboardData?: DataTransfer | null }) {
      super(type, eventInit);
      this.clipboardData = eventInit?.clipboardData ?? null;
    }
  }
  globalThis.ClipboardEvent = ClipboardEventPolyfill as unknown as typeof ClipboardEvent;
}

describe("useAntiCheat", () => {
  const examId = "exam-001";
  let fetchMock: ReturnType<typeof vi.fn>;

  beforeEach(() => {
    fetchMock = vi.fn().mockResolvedValue({ ok: true });
    vi.stubGlobal("fetch", fetchMock);
  });

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

  describe("初始状态", () => {
    it("violations 初始应为空数组", () => {
      const { result } = renderHook(() => useAntiCheat(examId));
      expect(result.current.violations).toEqual([]);
    });

    it("isFullscreen 初始应为 false", () => {
      const { result } = renderHook(() => useAntiCheat(examId));
      expect(result.current.isFullscreen).toBe(false);
    });

    it("showWarning 初始应为 false", () => {
      const { result } = renderHook(() => useAntiCheat(examId));
      expect(result.current.showWarning).toBe(false);
    });

    it("warningMessage 初始应为 null", () => {
      const { result } = renderHook(() => useAntiCheat(examId));
      expect(result.current.warningMessage).toBeNull();
    });
  });

  describe("copy 事件监听", () => {
    it("copy 事件应记录 copy 违规", () => {
      const { result } = renderHook(() => useAntiCheat(examId));

      act(() => {
        document.dispatchEvent(
          new ClipboardEvent("copy", { cancelable: true }),
        );
      });

      expect(result.current.violations).toHaveLength(1);
      expect(result.current.violations[0]?.type).toBe("copy");
    });

    it("copy 事件应调用 preventDefault", () => {
      renderHook(() => useAntiCheat(examId));

      const event = new ClipboardEvent("copy", { cancelable: true });
      const preventSpy = vi.spyOn(event, "preventDefault");

      act(() => {
        document.dispatchEvent(event);
      });

      expect(preventSpy).toHaveBeenCalled();
    });
  });

  describe("paste 事件监听", () => {
    it("paste 事件应记录 paste 违规", () => {
      const { result } = renderHook(() => useAntiCheat(examId));

      act(() => {
        document.dispatchEvent(
          new ClipboardEvent("paste", { cancelable: true }),
        );
      });

      expect(result.current.violations).toHaveLength(1);
      expect(result.current.violations[0]?.type).toBe("paste");
    });

    it("paste 事件应调用 preventDefault", () => {
      renderHook(() => useAntiCheat(examId));

      const event = new ClipboardEvent("paste", { cancelable: true });
      const preventSpy = vi.spyOn(event, "preventDefault");

      act(() => {
        document.dispatchEvent(event);
      });

      expect(preventSpy).toHaveBeenCalled();
    });
  });

  describe("contextmenu 事件监听", () => {
    it("contextmenu 事件应记录 contextmenu 违规", () => {
      const { result } = renderHook(() => useAntiCheat(examId));

      act(() => {
        document.dispatchEvent(
          new MouseEvent("contextmenu", { cancelable: true }),
        );
      });

      expect(result.current.violations).toHaveLength(1);
      expect(result.current.violations[0]?.type).toBe("contextmenu");
    });

    it("contextmenu 事件应调用 preventDefault", () => {
      renderHook(() => useAntiCheat(examId));

      const event = new MouseEvent("contextmenu", { cancelable: true });
      const preventSpy = vi.spyOn(event, "preventDefault");

      act(() => {
        document.dispatchEvent(event);
      });

      expect(preventSpy).toHaveBeenCalled();
    });
  });

  describe("visibilitychange 事件监听", () => {
    function setVisibility(state: "visible" | "hidden"): void {
      Object.defineProperty(document, "visibilityState", {
        configurable: true,
        get: () => state,
      });
    }

    afterEach(() => {
      // 恢复默认
      Object.defineProperty(document, "visibilityState", {
        configurable: true,
        get: () => "visible",
      });
    });

    it("切到 hidden 应记录 tab-switch 违规", () => {
      const { result } = renderHook(() => useAntiCheat(examId));

      setVisibility("hidden");
      act(() => {
        document.dispatchEvent(new Event("visibilitychange"));
      });

      expect(result.current.violations).toHaveLength(1);
      expect(result.current.violations[0]?.type).toBe("tab-switch");
    });

    it("切到 visible 不应记录违规", () => {
      const { result } = renderHook(() => useAntiCheat(examId));

      setVisibility("visible");
      act(() => {
        document.dispatchEvent(new Event("visibilitychange"));
      });

      expect(result.current.violations).toHaveLength(0);
    });

    it("3 次切屏后应显示警告", () => {
      const { result } = renderHook(() => useAntiCheat(examId));

      for (let i = 0; i < 3; i++) {
        setVisibility("hidden");
        act(() => {
          document.dispatchEvent(new Event("visibilitychange"));
        });
      }

      expect(result.current.violations).toHaveLength(3);
      expect(result.current.showWarning).toBe(true);
      expect(result.current.warningMessage).toContain("切屏");
    });

    it("2 次切屏不应显示警告", () => {
      const { result } = renderHook(() => useAntiCheat(examId));

      for (let i = 0; i < 2; i++) {
        setVisibility("hidden");
        act(() => {
          document.dispatchEvent(new Event("visibilitychange"));
        });
      }

      expect(result.current.violations).toHaveLength(2);
      expect(result.current.showWarning).toBe(false);
    });
  });

  describe("dismissWarning", () => {
    it("dismissWarning 应清除警告状态", () => {
      const { result } = renderHook(() => useAntiCheat(examId));

      // 触发 3 次切屏显示警告
      Object.defineProperty(document, "visibilityState", {
        configurable: true,
        get: () => "hidden",
      });
      for (let i = 0; i < 3; i++) {
        act(() => {
          document.dispatchEvent(new Event("visibilitychange"));
        });
      }
      expect(result.current.showWarning).toBe(true);

      // 恢复 visibility
      Object.defineProperty(document, "visibilityState", {
        configurable: true,
        get: () => "visible",
      });

      act(() => {
        result.current.dismissWarning();
      });

      expect(result.current.showWarning).toBe(false);
      expect(result.current.warningMessage).toBeNull();
    });
  });

  describe("违规记录结构", () => {
    it("每条违规应包含 type / timestamp / details", () => {
      const { result } = renderHook(() => useAntiCheat(examId));

      act(() => {
        document.dispatchEvent(
          new ClipboardEvent("copy", { cancelable: true }),
        );
      });

      const violation = result.current.violations[0];
      expect(violation).toBeDefined();
      expect(violation?.type).toBe("copy");
      expect(typeof violation?.timestamp).toBe("number");
      expect(typeof violation?.details).toBe("string");
    });

    it("多次违规应累积记录", () => {
      const { result } = renderHook(() => useAntiCheat(examId));

      act(() => {
        document.dispatchEvent(new ClipboardEvent("copy", { cancelable: true }));
        document.dispatchEvent(new ClipboardEvent("paste", { cancelable: true }));
        document.dispatchEvent(new MouseEvent("contextmenu", { cancelable: true }));
      });

      expect(result.current.violations).toHaveLength(3);
      expect(result.current.violations[0]?.type).toBe("copy");
      expect(result.current.violations[1]?.type).toBe("paste");
      expect(result.current.violations[2]?.type).toBe("contextmenu");
    });
  });

  describe("批量上报", () => {
    it("违规满 10 条后应调用 fetch 上报", () => {
      renderHook(() => useAntiCheat(examId));

      act(() => {
        for (let i = 0; i < 10; i++) {
          document.dispatchEvent(
            new ClipboardEvent("copy", { cancelable: true }),
          );
        }
      });

      expect(fetchMock).toHaveBeenCalledTimes(1);
      expect(fetchMock).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({
          method: "POST",
          headers: { "Content-Type": "application/json" },
        }),
      );
    });

    it("不满 10 条时不应调用 fetch", () => {
      renderHook(() => useAntiCheat(examId));

      act(() => {
        for (let i = 0; i < 5; i++) {
          document.dispatchEvent(
            new ClipboardEvent("copy", { cancelable: true }),
          );
        }
      });

      expect(fetchMock).not.toHaveBeenCalled();
    });
  });

  describe("卸载清理", () => {
    it("卸载时应移除事件监听", () => {
      const { unmount } = renderHook(() => useAntiCheat(examId));

      unmount();

      // 卸载后触发事件不应报错
      expect(() => {
        document.dispatchEvent(new ClipboardEvent("copy", { cancelable: true }));
      }).not.toThrow();
    });

    it("卸载时有未上报违规应 flush", () => {
      const { result, unmount } = renderHook(() => useAntiCheat(examId));

      act(() => {
        for (let i = 0; i < 3; i++) {
          document.dispatchEvent(
            new ClipboardEvent("copy", { cancelable: true }),
          );
        }
      });
      expect(result.current.violations).toHaveLength(3);
      expect(fetchMock).not.toHaveBeenCalled();

      unmount();

      expect(fetchMock).toHaveBeenCalledTimes(1);
    });
  });
});
