import { describe, it, expect, vi, beforeEach } from "vitest";

vi.mock("./handlers/lobbyHandlers.js", () => ({
  handleLobbyJoin: vi.fn(),
  handleLobbyLeave: vi.fn(),
  handleLobbyStart: vi.fn(),
}));

vi.mock("./handlers/gameHandlers.js", () => ({
  handleGameAnswer: vi.fn(),
  handleGameReady: vi.fn(),
}));

import { handleMessage } from "./router.js";
import {
  handleLobbyJoin,
  handleLobbyLeave,
  handleLobbyStart,
} from "./handlers/lobbyHandlers.js";
import { handleGameAnswer, handleGameReady } from "./handlers/gameHandlers.js";

const mockHandleLobbyJoin = vi.mocked(handleLobbyJoin);
const mockHandleLobbyLeave = vi.mocked(handleLobbyLeave);
const mockHandleLobbyStart = vi.mocked(handleLobbyStart);
const mockHandleGameAnswer = vi.mocked(handleGameAnswer);
const mockHandleGameReady = vi.mocked(handleGameReady);

const fakeWs = { send: vi.fn(), readyState: 1, OPEN: 1 };

const FAKE_LOBBY_ID = "00000000-0000-4000-8000-000000000001";
const FAKE_QUESTION_ID = "00000000-0000-4000-8000-000000000002";

const fakeAuth = {
  session: {
    id: "session-1",
    userId: "user-1",
    token: "fake-token",
    expiresAt: new Date(Date.now() + 1000 * 60 * 60),
    createdAt: new Date(),
    updatedAt: new Date(),
    ipAddress: null,
    userAgent: null,
  },
  user: {
    id: "user-1",
    name: "Test User",
    email: "test@test.com",
    emailVerified: false,
    image: null,
    createdAt: new Date(),
    updatedAt: new Date(),
  },
};

beforeEach(() => {
  vi.clearAllMocks();
});

describe("handleMessage", () => {
  it("dispatches lobby:join to handleLobbyJoin", async () => {
    const msg = JSON.stringify({ type: "lobby:join", code: "ABC123" });
    await handleMessage(fakeWs as never, msg, fakeAuth);
    expect(mockHandleLobbyJoin).toHaveBeenCalledWith(
      fakeWs,
      { type: "lobby:join", code: "ABC123" },
      fakeAuth.user,
    );
  });

  it("dispatches lobby:leave to handleLobbyLeave", async () => {
    const msg = JSON.stringify({ type: "lobby:leave", lobbyId: FAKE_LOBBY_ID });
    await handleMessage(fakeWs as never, msg, fakeAuth);
    expect(mockHandleLobbyLeave).toHaveBeenCalled();
  });

  it("dispatches lobby:start to handleLobbyStart", async () => {
    const msg = JSON.stringify({ type: "lobby:start", lobbyId: FAKE_LOBBY_ID });
    await handleMessage(fakeWs as never, msg, fakeAuth);
    expect(mockHandleLobbyStart).toHaveBeenCalled();
  });

  it("dispatches game:answer to handleGameAnswer", async () => {
    const msg = JSON.stringify({
      type: "game:answer",
      lobbyId: FAKE_LOBBY_ID,
      questionId: FAKE_QUESTION_ID,
      selectedOptionId: 2,
    });
    await handleMessage(fakeWs as never, msg, fakeAuth);
    expect(mockHandleGameAnswer).toHaveBeenCalled();
  });

  it("dispatches game:ready to handleGameReady", async () => {
    const msg = JSON.stringify({ type: "game:ready", lobbyId: FAKE_LOBBY_ID });
    await handleMessage(fakeWs as never, msg, fakeAuth);
    expect(mockHandleGameReady).toHaveBeenCalled();
  });

  it("sends error message for invalid JSON", async () => {
    await handleMessage(fakeWs as never, "not json", fakeAuth);
    expect(fakeWs.send).toHaveBeenCalledWith(
      expect.stringContaining("Invalid JSON"),
    );
  });

  it("sends error message for unknown message type", async () => {
    const msg = JSON.stringify({ type: "unknown:type" });
    await handleMessage(fakeWs as never, msg, fakeAuth);
    expect(fakeWs.send).toHaveBeenCalledWith(
      expect.stringContaining("Invalid message format"),
    );
  });

  it("sends error message when handler throws AppError", async () => {
    const { AppError } = await import("../errors/AppError.js");
    mockHandleLobbyJoin.mockRejectedValueOnce(
      new AppError("Lobby not found", 404),
    );
    const msg = JSON.stringify({ type: "lobby:join", code: "ABC123" });
    await handleMessage(fakeWs as never, msg, fakeAuth);
    expect(fakeWs.send).toHaveBeenCalledWith(
      expect.stringContaining("Lobby not found"),
    );
  });
});
