import { randomInt } from "crypto";
import {
  createLobby as createLobbyModel,
  getLobbyByCodeWithPlayers,
  addPlayer,
} from "@lila/db";
import type { Lobby, LobbyWithPlayers } from "@lila/db";
import { MAX_LOBBY_PLAYERS } from "@lila/shared";
import { NotFoundError, ConflictError, AppError } from "../errors/AppError.js";

const CODE_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // Crockford Base32
const CODE_LENGTH = 6;
const MAX_CODE_ATTEMPTS = 5;

const generateLobbyCode = (): string => {
  let code = "";
  for (let i = 0; i < CODE_LENGTH; i++) {
    code += CODE_ALPHABET[randomInt(CODE_ALPHABET.length)];
  }
  return code;
};

const isUniqueViolation = (err: unknown): boolean => {
  return (err as { code?: string })?.code === "23505";
};

export const createLobby = async (hostUserId: string): Promise<Lobby> => {
  for (let i = 0; i < MAX_CODE_ATTEMPTS; i++) {
    const code = generateLobbyCode();
    try {
      const lobby = await createLobbyModel(code, hostUserId);
      await addPlayer(lobby.id, hostUserId, MAX_LOBBY_PLAYERS);
      return lobby;
    } catch (err) {
      if (isUniqueViolation(err)) continue;
      throw err;
    }
  }
  throw new AppError("Could not generate a unique lobby code", 500);
};

export const joinLobby = async (
  code: string,
  userId: string,
): Promise<LobbyWithPlayers> => {
  const lobby = await getLobbyByCodeWithPlayers(code);
  if (!lobby) {
    throw new NotFoundError(`Lobby not found: ${code}`);
  }
  if (lobby.status !== "waiting") {
    throw new ConflictError("Game has already started");
  }
  if (lobby.players.some((p) => p.userId === userId)) {
    return lobby; // idempotent: already in lobby
  }
  if (lobby.players.length >= MAX_LOBBY_PLAYERS) {
    throw new ConflictError("Lobby is full");
  }

  const player = await addPlayer(lobby.id, userId, MAX_LOBBY_PLAYERS);
  if (!player) {
    // Race fallback: another request filled the last slot, started the game,
    // or the user joined concurrently. Pre-checks above handle the common cases.
    throw new ConflictError("Lobby is no longer available");
  }

  const fresh = await getLobbyByCodeWithPlayers(code);
  if (!fresh) {
    throw new AppError("Lobby disappeared during join", 500);
  }
  return fresh;
};
