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

// ---------------------------------------------------------------------------
// Mocks
// ---------------------------------------------------------------------------

// vi.hoisted garante que as funções existem antes do hoisting do vi.mock.
const { mockDestroy, mockResize, mockPhaserGame } = vi.hoisted(() => {
  const mockDestroy = vi.fn();
  const mockResize = vi.fn();
  // Precisa ser uma função construtora (não arrow) para suportar `new Phaser.Game()`
  const mockPhaserGame = vi.fn(function () {
    this.destroy = mockDestroy;
    this.scale = { resize: mockResize };
  });
  return { mockDestroy, mockResize, mockPhaserGame };
});

vi.mock("phaser", () => ({
  default: { Game: mockPhaserGame },
}));

// useIsMobile controla se estamos em modo mobile.
let mockIsMobile = false;
vi.mock("../useIsMobile", () => ({
  useIsMobile: () => mockIsMobile,
}));

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

const PHASE_CONFIG = { maxBlocks: 5 };
const GAME_CONFIG = { gameId: "test-game", fases: [PHASE_CONFIG] };

function buildOptions(overrides = {}) {
  return {
    gameFactory: vi.fn(() => ({ type: Phaser.AUTO })),
    phaseConfig: PHASE_CONFIG,
    currentPhase: 1,
    customFailureHandler: undefined,
    gameConfig: GAME_CONFIG,
    ...overrides,
  };
}

// ---------------------------------------------------------------------------
describe("usePhaser — retorno", () => {
  beforeEach(() => {
    vi.useFakeTimers();
    mockPhaserGame.mockClear();
    mockDestroy.mockClear();
    mockResize.mockClear();
  });

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

  it("retorna gameContainerRef como objeto ref", () => {
    const { result } = renderHook(() => usePhaser(buildOptions()));

    expect(result.current).toHaveProperty("gameContainerRef");
    expect(result.current.gameContainerRef).toHaveProperty("current");
  });
});

// ---------------------------------------------------------------------------
describe("usePhaser — inicialização do Phaser", () => {
  let container;

  beforeEach(() => {
    vi.useFakeTimers();
    mockPhaserGame.mockClear();
    mockDestroy.mockClear();
    // Cria um elemento DOM real para annexar ao ref
    container = document.createElement("div");
    document.body.appendChild(container);
  });

  afterEach(() => {
    document.body.removeChild(container);
    vi.useRealTimers();
  });

  it("chama gameFactory com os parâmetros corretos após o delay", () => {
    const gameFactory = vi.fn(() => ({}));
    const options = buildOptions({ gameFactory, customFailureHandler: vi.fn() });

    const { result } = renderHook(() => usePhaser(options));

    // Injeta o elemento DOM no ref antes do timer disparar
    result.current.gameContainerRef.current = container;

    // gameFactory NÃO deve ser chamado antes do setTimeout(10ms)
    expect(gameFactory).not.toHaveBeenCalled();

    act(() => {
      vi.advanceTimersByTime(10);
    });

    expect(gameFactory).toHaveBeenCalledWith(
      container,
      options.phaseConfig,
      options.customFailureHandler,
      options.currentPhase,
      options.gameConfig,
    );
  });

  it("não instancia Phaser.Game quando gameContainerRef.current é null", () => {
    renderHook(() => usePhaser(buildOptions()));

    act(() => {
      vi.advanceTimersByTime(10);
    });

    // Container null → not called
    expect(mockPhaserGame).not.toHaveBeenCalled();
  });

  it("reinicializa quando currentPhase muda", () => {
    const gameFactory = vi.fn(() => ({}));
    const { result, rerender } = renderHook(
      (props) => usePhaser(props),
      { initialProps: buildOptions({ gameFactory, currentPhase: 1 }) },
    );

    result.current.gameContainerRef.current = container;
    act(() => vi.advanceTimersByTime(10));
    const callsAfterFirst = gameFactory.mock.calls.length;

    rerender(buildOptions({ gameFactory, currentPhase: 2 }));
    act(() => vi.advanceTimersByTime(10));

    expect(gameFactory.mock.calls.length).toBeGreaterThan(callsAfterFirst);
  });

  it("reinicializa quando gameFactory muda", () => {
    const factory1 = vi.fn(() => ({}));
    const factory2 = vi.fn(() => ({}));

    const { result, rerender } = renderHook(
      (props) => usePhaser(props),
      { initialProps: buildOptions({ gameFactory: factory1 }) },
    );

    result.current.gameContainerRef.current = container;
    act(() => vi.advanceTimersByTime(10));

    rerender(buildOptions({ gameFactory: factory2 }));
    act(() => vi.advanceTimersByTime(10));

    expect(factory2).toHaveBeenCalled();
  });
});

// ---------------------------------------------------------------------------
describe("usePhaser — cleanup", () => {
  beforeEach(() => {
    vi.useFakeTimers();
    mockPhaserGame.mockClear();
    mockDestroy.mockClear();
  });

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

  it("cancela o timeout pendente ao desmontar antes dos 10ms", () => {
    const gameFactory = vi.fn(() => ({}));
    const { unmount } = renderHook(() =>
      usePhaser(buildOptions({ gameFactory })),
    );

    // Desmonta antes do timeout disparar
    unmount();
    act(() => vi.advanceTimersByTime(10));

    // gameFactory não deve ter sido chamado porque o timer foi cancelado
    expect(gameFactory).not.toHaveBeenCalled();
  });
});

// ---------------------------------------------------------------------------
describe("usePhaser — resize ao alternar mobile/desktop", () => {
  beforeEach(() => {
    vi.useFakeTimers();
    mockResize.mockClear();
    mockPhaserGame.mockClear();
    mockIsMobile = false;
  });

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

  it("não chama scale.resize antes de existir instância Phaser", () => {
    renderHook(() => usePhaser(buildOptions()));
    // Sem instância Phaser (container null), resize não é chamado
    expect(mockResize).not.toHaveBeenCalled();
  });
});

// ---------------------------------------------------------------------------
describe("usePhaser — resize ao alternar mobile/desktop COM instância", () => {
  let container;

  beforeEach(() => {
    vi.useFakeTimers();
    mockPhaserGame.mockClear();
    mockResize.mockClear();
    mockIsMobile = false;
    container = document.createElement("div");
    document.body.appendChild(container);
  });

  afterEach(() => {
    document.body.removeChild(container);
    vi.useRealTimers();
    mockIsMobile = false;
  });

  it("chama scale.resize quando isMobile muda e instância Phaser existe", () => {
    // Usa factory estável para não mudar deps do segundo effect no rerender
    const stableFactory = vi.fn(() => ({}));
    const baseOptions = buildOptions({ gameFactory: stableFactory });

    const { result, rerender } = renderHook(
      ({ isMobile: _isMobile, ...opts }) => {
        mockIsMobile = _isMobile;
        return usePhaser(opts);
      },
      { initialProps: { ...baseOptions, isMobile: false } },
    );

    // Anexa container e inicializa Phaser
    result.current.gameContainerRef.current = container;
    act(() => vi.advanceTimersByTime(10));
    expect(mockPhaserGame).toHaveBeenCalled();

    // Alterna para mobile → deve chamar scale.resize
    mockResize.mockClear();
    act(() => {
      rerender({ ...baseOptions, isMobile: true });
    });

    expect(mockResize).toHaveBeenCalled();
  });
});

// ---------------------------------------------------------------------------
describe("usePhaser — erros na inicialização", () => {
  let container;

  beforeEach(() => {
    vi.useFakeTimers();
    mockPhaserGame.mockClear();
    mockDestroy.mockClear();
    container = document.createElement("div");
    document.body.appendChild(container);
  });

  afterEach(() => {
    document.body.removeChild(container);
    vi.useRealTimers();
  });

  it("loga console.error quando gameFactory lança exceção no timeout", () => {
    const throwingFactory = vi.fn(() => { throw new Error("factory error"); });
    const spy = vi.spyOn(console, "error").mockImplementation(() => {});

    const { result } = renderHook(() =>
      usePhaser(buildOptions({ gameFactory: throwingFactory })),
    );
    result.current.gameContainerRef.current = container;

    act(() => vi.advanceTimersByTime(10));

    expect(spy).toHaveBeenCalledWith(
      expect.stringContaining("Erro ao inicializar"),
      expect.any(Error),
    );
    spy.mockRestore();
  });
});

// ---------------------------------------------------------------------------
describe("usePhaser — cleanup com destroy lançando erro", () => {
  let container;

  beforeEach(() => {
    vi.useFakeTimers();
    mockPhaserGame.mockClear();
    mockDestroy.mockClear();
    container = document.createElement("div");
    document.body.appendChild(container);
  });

  afterEach(() => {
    document.body.removeChild(container);
    vi.useRealTimers();
  });

  it("loga console.warn quando destroy() lança exceção durante cleanup", async () => {
    mockDestroy.mockImplementation(() => { throw new Error("destroy error"); });
    const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
    const stableFactory = vi.fn(() => ({}));

    const { result, unmount } = renderHook(() =>
      usePhaser(buildOptions({ gameFactory: stableFactory })),
    );
    result.current.gameContainerRef.current = container;
    await act(async () => vi.advanceTimersByTime(10));
    expect(mockPhaserGame).toHaveBeenCalled();

    // Desmonta → cleanup roda → destroy lança → console.warn chamado
    await act(async () => unmount());

    expect(spy).toHaveBeenCalledWith(
      expect.stringContaining("Erro durante cleanup"),
      expect.any(Error),
    );
    spy.mockRestore();
  });
});
