import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import type { DrawnIdea } from "@/components/BlindboxDrawnHistory";

const {
  mockRequestJson,
  mockConfetti,
  mockConfettiReset,
  mockBoxStart,
} = vi.hoisted(() => ({
  mockRequestJson: vi.fn(),
  mockConfetti: vi.fn(),
  mockConfettiReset: vi.fn(),
  mockBoxStart: vi.fn().mockResolvedValue(undefined),
}));

vi.mock("@/lib/fetcher", () => ({
  requestJson: mockRequestJson,
}));

vi.mock("canvas-confetti", () => {
  Object.assign(mockConfetti, { reset: mockConfettiReset });
  return { default: mockConfetti };
});

vi.mock("framer-motion", () => ({
  useAnimation: () => ({ start: mockBoxStart }),
}));

import { useBlindboxDraw } from "./useBlindboxDraw";

const room = {
  id: "room-1",
  code: "ABC123",
  name: "周末盲盒",
  creatorId: "user-1",
  city: "上海",
  address: "人民广场",
  lat: 31.23,
  lng: 121.47,
  poolCount: 2,
  members: [{ id: "user-1", username: "alice", avatar: "🐱" }],
};

const profile = { id: "user-1", username: "alice", avatar: "🐱" };

describe("useBlindboxDraw", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.useFakeTimers();
  });

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

  it("returns error immediately when pool is empty", async () => {
    const setPoolCount = vi.fn();
    const setDrawnHistory = vi.fn();
    const setError = vi.fn();
    const setPhase = vi.fn();

    const { result } = renderHook(() =>
      useBlindboxDraw(null, profile, 0, setPoolCount, setDrawnHistory, setError, setPhase),
    );

    await act(async () => {
      await result.current.handleDraw();
    });

    expect(setError).toHaveBeenCalledWith("盒子是空的，先往里面塞点想法吧！");
    expect(setPhase).not.toHaveBeenCalled();
    expect(mockRequestJson).not.toHaveBeenCalled();
  });

  it("updates phase and state when draw succeeds", async () => {
    const setPoolCount = vi.fn();
    const setDrawnHistory = vi.fn();
    const setError = vi.fn();
    const setPhase = vi.fn();
    const idea: DrawnIdea = {
      id: "idea-1",
      content: "去看展",
      createdAt: new Date().toISOString(),
      user: { id: "u2", username: "bob", avatar: "🐶" },
      drawnBy: { id: "user-1", username: "alice", avatar: "🐱" },
    };
    mockRequestJson.mockResolvedValueOnce(idea);

    const { result } = renderHook(() =>
      useBlindboxDraw(room, profile, 2, setPoolCount, setDrawnHistory, setError, setPhase),
    );

    await act(async () => {
      await result.current.handleDraw();
    });

    expect(mockRequestJson).toHaveBeenCalledWith("/api/blindbox/draw", {
      method: "POST",
      body: { roomId: "room-1" },
    });
    expect(setPhase).toHaveBeenNthCalledWith(1, "shaking");
    expect(setPhase).toHaveBeenNthCalledWith(2, "reveal");
    expect(result.current.revealedIdea).toEqual(idea);
    expect(mockConfetti).toHaveBeenCalled();

    const poolUpdater = setPoolCount.mock.calls[0][0] as (value: number) => number;
    expect(poolUpdater(2)).toBe(1);

    const historyUpdater = setDrawnHistory.mock.calls[0][0] as (value: DrawnIdea[]) => DrawnIdea[];
    expect(historyUpdater([])).toEqual([idea]);
  });

  it("falls back to pool phase and sets error when draw fails", async () => {
    const setPoolCount = vi.fn();
    const setDrawnHistory = vi.fn();
    const setError = vi.fn();
    const setPhase = vi.fn();
    mockRequestJson.mockRejectedValueOnce(new Error("网络异常"));

    const { result } = renderHook(() =>
      useBlindboxDraw(room, profile, 2, setPoolCount, setDrawnHistory, setError, setPhase),
    );

    await act(async () => {
      await result.current.handleDraw();
    });

    expect(setPhase).toHaveBeenNthCalledWith(1, "shaking");
    expect(setPhase).toHaveBeenNthCalledWith(2, "pool");
    expect(setError).toHaveBeenCalledWith("网络异常");
    expect(result.current.revealedIdea).toBeNull();
  });
});
