/**
 * Tests for draft-state-sync event handling on reconnection.
 *
 * Validates that after a mobile browser goes to background and returns,
 * the draft room properly syncs all state: picks, current pick number,
 * pause status, timers, and available participants.
 *
 * These tests verify the contract between useDraftSocket and the draft
 * room component's event handlers.
 */

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

// ---------------------------------------------------------------------------
// socket.io-client mock
// ---------------------------------------------------------------------------

type EventHandler = (...args: any[]) => void;

function makeSocketMock() {
  const socketHandlers: Record<string, EventHandler[]> = {};
  const managerHandlers: Record<string, EventHandler[]> = {};

  const socketMock = {
    connected: false,
    id: "socket-test-id",
    on: vi.fn((event: string, handler: EventHandler) => {
      socketHandlers[event] ??= [];
      socketHandlers[event].push(handler);
    }),
    off: vi.fn((event: string, handler?: EventHandler) => {
      if (!handler) {
        socketHandlers[event] = [];
      } else {
        socketHandlers[event] = (socketHandlers[event] ?? []).filter(
          (h) => h !== handler
        );
      }
    }),
    emit: vi.fn(),
    connect: vi.fn(() => {
      socketMock.connected = true;
      setTimeout(() => {
        socketMock._trigger("connect");
      }, 0);
    }),
    disconnect: vi.fn(() => {
      socketMock.connected = false;
    }),
    io: {
      on: vi.fn((event: string, handler: EventHandler) => {
        managerHandlers[event] ??= [];
        managerHandlers[event].push(handler);
      }),
      off: vi.fn((event: string, handler?: EventHandler) => {
        if (!handler) {
          managerHandlers[event] = [];
        } else {
          managerHandlers[event] = (managerHandlers[event] ?? []).filter(
            (h) => h !== handler
          );
        }
      }),
    },

    _trigger(event: string, ...args: any[]) {
      (socketHandlers[event] ?? []).forEach((h) => h(...args));
    },
    _triggerManager(event: string, ...args: any[]) {
      (managerHandlers[event] ?? []).forEach((h) => h(...args));
    },
  };

  return socketMock;
}

type SocketMock = ReturnType<typeof makeSocketMock>;
let socketMock: SocketMock;

vi.mock("socket.io-client", () => ({
  io: vi.fn(() => socketMock),
}));

import { useDraftSocket } from "../useDraftSocket";

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

function renderDraftSocket(seasonId = "season-1", teamId?: string) {
  return renderHook(() => useDraftSocket(seasonId, teamId));
}

/** Simulate the full connect → disconnect → reconnect cycle */
function doReconnectCycle(mock: SocketMock) {
  // Initial connect
  act(() => {
    mock.connected = true;
    mock._trigger("connect");
  });
  // Disconnect (mobile background / network loss)
  act(() => {
    mock.connected = false;
    mock._trigger("disconnect", "transport close");
  });
  // Reconnect
  act(() => {
    mock.connected = true;
    mock._trigger("connect");
  });
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("useDraftSocket – draft-state-sync integration", () => {
  beforeEach(() => {
    socketMock = makeSocketMock();
    vi.useFakeTimers();
  });

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

  it("re-emits join-draft on reconnect, which triggers server to send draft-state-sync", () => {
    renderDraftSocket("season-42", "team-7");

    doReconnectCycle(socketMock);

    // join-draft should have been emitted on both initial connect and reconnect
    const joinCalls = socketMock.emit.mock.calls.filter(
      ([ev]) => ev === "join-draft"
    );
    expect(joinCalls.length).toBe(2);
    expect(joinCalls[1]).toEqual(["join-draft", "season-42", "team-7"]);
  });

  it("increments reconnectCount on reconnect so draft room can trigger revalidation", () => {
    const { result } = renderDraftSocket("season-1", "team-1");

    doReconnectCycle(socketMock);

    expect(result.current.reconnectCount).toBe(1);
  });

  it("allows consumer to register draft-state-sync handler via on()", () => {
    const { result } = renderDraftSocket("season-1", "team-1");

    // Initial connect
    act(() => {
      socketMock.connected = true;
      socketMock._trigger("connect");
    });

    // Consumer registers a handler for draft-state-sync
    const syncHandler = vi.fn();
    act(() => {
      result.current.on("draft-state-sync", syncHandler);
    });

    // Server sends draft-state-sync
    const syncData = {
      currentPickNumber: 15,
      isPaused: false,
      status: "draft",
      picks: [
        { id: "pick-1", pickNumber: 1, team: { id: "team-1" } },
        { id: "pick-2", pickNumber: 2, team: { id: "team-2" } },
      ],
      timers: [
        { teamId: "team-1", timeRemaining: 90 },
        { teamId: "team-2", timeRemaining: 120 },
      ],
    };

    act(() => {
      socketMock._trigger("draft-state-sync", syncData);
    });

    expect(syncHandler).toHaveBeenCalledWith(syncData);
  });

  it("allows consumer to register timer-update handler that receives currentPickNumber", () => {
    const { result } = renderDraftSocket("season-1");

    act(() => {
      socketMock.connected = true;
      socketMock._trigger("connect");
    });

    const timerHandler = vi.fn();
    act(() => {
      result.current.on("timer-update", timerHandler);
    });

    const timerData = {
      seasonId: "season-1",
      teamId: "team-2",
      timeRemaining: 85,
      currentPickNumber: 12,
    };

    act(() => {
      socketMock._trigger("timer-update", timerData);
    });

    expect(timerHandler).toHaveBeenCalledWith(timerData);
    // The handler receives currentPickNumber which the draft room uses to sync
    expect(timerHandler.mock.calls[0][0].currentPickNumber).toBe(12);
  });

  it("handles visibilitychange-based reconnect when socket is alive (zombie detection)", () => {
    const { result } = renderDraftSocket("season-1", "team-1");

    // Initial connect
    act(() => {
      socketMock.connected = true;
      socketMock._trigger("connect");
    });
    expect(result.current.reconnectCount).toBe(0);

    // Simulate mobile background return with still-alive socket
    act(() => {
      Object.defineProperty(document, "visibilityState", {
        value: "visible",
        configurable: true,
      });
      document.dispatchEvent(new Event("visibilitychange"));
    });

    // Should increment reconnectCount even though socket didn't disconnect
    // This is critical for triggering revalidation
    expect(result.current.reconnectCount).toBe(1);

    // join-draft re-emitted
    const joinCalls = socketMock.emit.mock.calls.filter(
      ([ev]) => ev === "join-draft"
    );
    expect(joinCalls.length).toBe(2);
  });

  it("handles long background period: disconnect → many seconds → visibilitychange → reconnect", () => {
    const { result } = renderDraftSocket("season-1", "team-1");

    // Initial connect
    act(() => {
      socketMock.connected = true;
      socketMock._trigger("connect");
    });

    // Disconnect (OS suspends the app)
    act(() => {
      socketMock.connected = false;
      socketMock._trigger("disconnect", "ping timeout");
    });
    expect(result.current.isConnected).toBe(false);
    expect(result.current.isReconnecting).toBe(true);

    // Much later: tab becomes visible (user opens phone)
    act(() => {
      Object.defineProperty(document, "visibilityState", {
        value: "visible",
        configurable: true,
      });
      document.dispatchEvent(new Event("visibilitychange"));
    });

    // Socket was dead → connect() should have been called
    expect(socketMock.connect).toHaveBeenCalled();

    // Simulate the delayed connect event
    act(() => {
      vi.runAllTimers();
    });

    // After connect: reconnectCount incremented
    expect(result.current.reconnectCount).toBe(1);
    expect(result.current.isConnected).toBe(true);
  });
});
