import { db } from "@lila/db";
import { lobbies, lobby_players } from "@lila/db/schema";
import { eq, and, sql } from "drizzle-orm";

import type { LobbyStatus } from "@lila/shared";

export type Lobby = typeof lobbies.$inferSelect;
export type LobbyPlayer = typeof lobby_players.$inferSelect;
export type LobbyWithPlayers = Lobby & {
  players: (LobbyPlayer & { user: { id: string; name: string } })[];
};

export const createLobby = async (
  code: string,
  hostUserId: string,
): Promise<Lobby> => {
  const [newLobby] = await db
    .insert(lobbies)
    .values({ code, hostUserId, status: "waiting" })
    .returning();

  if (!newLobby) {
    throw new Error("Failed to create lobby");
  }

  return newLobby;
};

export const getLobbyByCodeWithPlayers = async (
  code: string,
): Promise<LobbyWithPlayers | undefined> => {
  return db.query.lobbies.findFirst({
    where: eq(lobbies.code, code),
    with: {
      players: { with: { user: { columns: { id: true, name: true } } } },
    },
  });
};

export const getLobbyByIdWithPlayers = async (
  lobbyId: string,
): Promise<LobbyWithPlayers | undefined> => {
  return db.query.lobbies.findFirst({
    where: eq(lobbies.id, lobbyId),
    with: {
      players: { with: { user: { columns: { id: true, name: true } } } },
    },
  });
};

export const updateLobbyStatus = async (
  lobbyId: string,
  status: LobbyStatus,
): Promise<void> => {
  await db.update(lobbies).set({ status }).where(eq(lobbies.id, lobbyId));
};

export const deleteLobby = async (lobbyId: string): Promise<void> => {
  await db.delete(lobbies).where(eq(lobbies.id, lobbyId));
};

/**
 * Atomically inserts a player into a lobby. Returns the new player row,
 * or undefined if the insert was skipped because:
 *   - the lobby is at capacity, or
 *   - the lobby is not in 'waiting' status, or
 *   - the user is already in the lobby (PK conflict).
 *
 * Callers are expected to pre-check these conditions against a hydrated
 * lobby state to produce specific error messages; the undefined return
 * is a safety net for concurrent races.
 */
export const addPlayer = async (
  lobbyId: string,
  userId: string,
  maxPlayers: number,
): Promise<LobbyPlayer | undefined> => {
  const result = await db.execute(sql`
     INSERT INTO lobby_players (lobby_id, user_id)
     SELECT ${lobbyId}::uuid, ${userId}
     WHERE (
       SELECT COUNT(*) FROM lobby_players WHERE lobby_id = ${lobbyId}::uuid
     ) < ${maxPlayers}
     AND EXISTS (
       SELECT 1 FROM lobbies WHERE id = ${lobbyId}::uuid AND status = 'waiting'
     )
     ON CONFLICT (lobby_id, user_id) DO NOTHING
   `);

  if (!result.rowCount) return undefined;
  const [player] = await db
    .select()
    .from(lobby_players)
    .where(
      and(eq(lobby_players.lobbyId, lobbyId), eq(lobby_players.userId, userId)),
    );

  return player;
};

export const removePlayer = async (
  lobbyId: string,
  userId: string,
): Promise<void> => {
  await db
    .delete(lobby_players)
    .where(
      and(eq(lobby_players.lobbyId, lobbyId), eq(lobby_players.userId, userId)),
    );
};

export const finishGame = async (
  lobbyId: string,
  scoresByUser: Map<string, number>,
): Promise<void> => {
  await db.transaction(async (tx) => {
    for (const [userId, score] of scoresByUser) {
      await tx
        .update(lobby_players)
        .set({ score })
        .where(
          and(
            eq(lobby_players.lobbyId, lobbyId),
            eq(lobby_players.userId, userId),
          ),
        );
    }
    await tx
      .update(lobbies)
      .set({ status: "finished" })
      .where(eq(lobbies.id, lobbyId));
  });
};
