import type { WebSocket } from "ws";
import type { User } from "better-auth";
import type { WsLobbyJoin, WsLobbyLeave, WsLobbyStart } from "@lila/shared";
import {
  getLobbyByCodeWithPlayers,
  deleteLobby,
  removePlayer,
  updateLobbyStatus,
  getLobbyByIdWithPlayers,
} from "@lila/db";
import {
  addConnection,
  getConnections,
  removeConnection,
  broadcastToLobby,
} from "../connections.js";
import { NotFoundError, ConflictError } from "../../errors/AppError.js";
import { generateMultiplayerQuestions } from "../../services/multiplayerGameService.js";
import { lobbyGameStore, timers } from "../gameState.js";
import { resolveRound } from "./gameHandlers.js";

export const handleLobbyJoin = async (
  ws: WebSocket,
  msg: WsLobbyJoin,
  user: User,
): Promise<void> => {
  // Load lobby and validate membership
  const lobby = await getLobbyByCodeWithPlayers(msg.code);
  if (!lobby) {
    throw new NotFoundError("Lobby not found");
  }

  if (lobby.status !== "waiting") {
    throw new ConflictError("Lobby is not in waiting state");
  }

  if (!lobby.players.some((p) => p.userId === user.id)) {
    throw new ConflictError("You are not a member of this lobby");
  }

  // Register connection and tag the socket with lobbyId
  addConnection(lobby.id, user.id, ws);
  ws.lobbyId = lobby.id;

  // Broadcast updated lobby state to all players
  broadcastToLobby(lobby.id, { type: "lobby:state", lobby });
};

export const handleLobbyLeave = async (
  ws: WebSocket,
  msg: WsLobbyLeave,
  user: User,
): Promise<void> => {
  const lobby = await getLobbyByCodeWithPlayers(msg.lobbyId);
  if (!lobby) return;

  removeConnection(msg.lobbyId, user.id);
  ws.lobbyId = undefined;

  if (lobby.hostUserId === user.id) {
    await deleteLobby(msg.lobbyId);
    broadcastToLobby(msg.lobbyId, {
      type: "error",
      code: "LOBBY_CLOSED",
      message: "Host left the lobby",
    });
    for (const player of lobby.players) {
      removeConnection(msg.lobbyId, player.userId);
    }
  } else {
    await removePlayer(msg.lobbyId, user.id);
    const updated = await getLobbyByCodeWithPlayers(lobby.code);
    if (!updated) return;
    broadcastToLobby(msg.lobbyId, { type: "lobby:state", lobby: updated });

    // TODO(reconnection-slice): if lobby.status === 'in_progress', the game
    // continues with remaining players. If only one player remains after this
    // leave, end the game immediately and declare them winner. Currently we
    // broadcast updated lobby state and let the game resolve naturally via
    // timeouts — the disconnected player's answers will be null each round.
    // When reconnection handling is added, this is the place to change.
  }
};

export const handleLobbyStart = async (
  _ws: WebSocket,
  msg: WsLobbyStart,
  user: User,
): Promise<void> => {
  // Load lobby and validate
  const lobby = await getLobbyByIdWithPlayers(msg.lobbyId);
  if (!lobby) {
    throw new NotFoundError("Lobby not found");
  }
  if (lobby.hostUserId !== user.id) {
    throw new ConflictError("Only the host can start the game");
  }
  if (lobby.status !== "waiting") {
    throw new ConflictError("Game has already started");
  }

  // Check connected players, not DB players
  const connected = getConnections(msg.lobbyId);
  if (connected.size < 2) {
    throw new ConflictError("At least 2 players must be connected to start");
  }

  // Generate questions
  const questions = await generateMultiplayerQuestions();

  // Initialize scores for all connected players
  const scores = new Map<string, number>();
  for (const userId of connected.keys()) {
    scores.set(userId, 0);
  }

  // Initialize game state
  await lobbyGameStore.create(msg.lobbyId, {
    code: lobby.code,
    questions,
    currentIndex: 0,
    playerAnswers: new Map(),
    scores,
  });

  // Update lobby status in DB
  await updateLobbyStatus(msg.lobbyId, "in_progress");

  // Broadcast first question
  const firstQuestion = questions[0]!;
  broadcastToLobby(msg.lobbyId, {
    type: "game:question",
    question: {
      questionId: firstQuestion.questionId,
      prompt: firstQuestion.prompt,
      gloss: firstQuestion.gloss,
      options: firstQuestion.options,
    },
    questionNumber: 1,
    totalQuestions: questions.length,
  });

  // Start 15s timer
  startRoundTimer(msg.lobbyId, 0, questions.length);
};

const startRoundTimer = (
  lobbyId: string,
  questionIndex: number,
  totalQuestions: number,
): void => {
  const timer = setTimeout(() => {
    void resolveRound(lobbyId, questionIndex, totalQuestions).catch((err) => {
      console.error("Error resolving round after timeout:", err);
    });
  }, 15000);
  timers.set(lobbyId, timer);
};
