import { superTokensAdminFunctionMiddleware, superTokensFunctionMiddleware } from "@/utils/supertokens";
import { createServerFn } from "@tanstack/react-start";
import { pbAdmin } from "@/lib/pocketbase/client";
import { tournamentInputSchema, GroupStanding } from "@/features/tournaments/types";
import { logger } from ".";
import { z } from "zod";
import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result";
import { serverFnLoggingMiddleware } from "@/utils/activities";
import { emitServerEvent } from "@/lib/events/emitter";
import brackets from "@/features/bracket/utils";
import { MatchInput } from "@/features/matches/types";
import { generateSingleEliminationBracket } from "./utils/bracket-generator";
import { orderGroupStageMatches } from "./utils/match-queue";

export const listTournaments = createServerFn()
  .middleware([superTokensFunctionMiddleware])
  .handler(async () => 
    toServerResult(pbAdmin.listTournaments)
  );

export const createTournament = createServerFn()
  .validator(tournamentInputSchema)
  .middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
  .handler(async ({ data }) =>
    toServerResult(async () => {
      const tournament = await pbAdmin.createTournament(data);
      emitServerEvent({ type: "tournament", tournamentId: tournament.id });
      return tournament;
    })
  );

export const updateTournament = createServerFn()
  .validator(z.object({
    id: z.string(),
    updates: tournamentInputSchema.partial()
  }))
  .middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
  .handler(async ({ data }) =>
    toServerResult(async () => {
      const tournament = await pbAdmin.updateTournament(data.id, data.updates);
      emitServerEvent({ type: "tournament", tournamentId: data.id });
      return tournament;
    })
  );

export const getTournament = createServerFn()
  .validator(z.string())
  .middleware([superTokensFunctionMiddleware])
  .handler(async ({ data: tournamentId, context }) => {
    const isAdmin = context.roles.includes("Admin");
    return toServerResult(() => pbAdmin.getTournament(tournamentId, isAdmin));
  });

export const getCurrentTournament = createServerFn()
  .handler(async () => 
    toServerResult(() => pbAdmin.getMostRecentTournament())
  );

export const enrollTeam = createServerFn()
  .validator(z.object({
    tournamentId: z.string(),
    teamId: z.string()
  }))
  .middleware([superTokensFunctionMiddleware, serverFnLoggingMiddleware])
  .handler(async ({ data: { tournamentId, teamId }, context }) => 
    toServerResult(async () => {
      const userId = context.userAuthId;
      const isAdmin = context.roles.includes("Admin"); 

      const team = await pbAdmin.getTeam(teamId);
      if (!team) { throw new Error('Team not found'); }

      const freeAgents = await pbAdmin.getFreeAgents(tournamentId);
      for (const player of team.players || []) {
        const isFreeAgent = freeAgents.some(fa => fa.player?.id === player.id);
        if (isFreeAgent) {
          await pbAdmin.unenrollFreeAgent(player.id, tournamentId);
        }
      }

      logger.info('Enrolling team in tournament', { tournamentId, teamId, userId });
      const tournament = await pbAdmin.enrollTeam(tournamentId, teamId);
      emitServerEvent({ type: "tournament", tournamentId });
      return tournament;
    })
  );

export const unenrollTeam = createServerFn()
  .validator(z.object({
    tournamentId: z.string(),
    teamId: z.string()
  }))
  .middleware([superTokensFunctionMiddleware, serverFnLoggingMiddleware])
  .handler(async ({ data: { tournamentId, teamId }, context }) =>
    toServerResult(async () => {
      const result = await pbAdmin.unenrollTeam(tournamentId, teamId);
      emitServerEvent({ type: "tournament", tournamentId });
      return result;
    })
  );

export const getUnenrolledTeams = createServerFn()
  .validator(z.string())
  .middleware([superTokensAdminFunctionMiddleware])
  .handler(async ({ data: tournamentId }) => 
    toServerResult(() => pbAdmin.getUnenrolledTeams(tournamentId))
  );

export const getFreeAgents = createServerFn()
  .validator(z.string())
  .middleware([superTokensFunctionMiddleware])
  .handler(async ({ data: tournamentId }) => 
    toServerResult(() => pbAdmin.getFreeAgents(tournamentId))
  );

export const enrollFreeAgent = createServerFn()
  .validator(z.object({ phone: z.string(), tournamentId: z.string() }))
  .middleware([superTokensFunctionMiddleware, serverFnLoggingMiddleware])
  .handler(async ({ context, data }) =>
    toServerResult(async () => {
      const userAuthId = context.userAuthId;
      const player = await pbAdmin.getPlayerByAuthId(userAuthId);
      if (!player) throw new Error("Player not found");

      await pbAdmin.enrollFreeAgent(player.id, data.phone, data.tournamentId);
      logger.info('Player enrolled as free agent', { playerId: player.id, phone: data.phone });
      emitServerEvent({ type: "tournament", tournamentId: data.tournamentId });
    })
  );

export const unenrollFreeAgent = createServerFn()
  .validator(z.object({ tournamentId: z.string() }))
  .middleware([superTokensFunctionMiddleware, serverFnLoggingMiddleware])
  .handler(async ({ context, data }) =>
    toServerResult(async () => {
      const userAuthId = context.userAuthId;
      const player = await pbAdmin.getPlayerByAuthId(userAuthId);
      if (!player) throw new Error("Player not found");

      await pbAdmin.unenrollFreeAgent(player.id, data.tournamentId);
      logger.info('Player unenrolled as free agent', { playerId: player.id });
      emitServerEvent({ type: "tournament", tournamentId: data.tournamentId });
    })
  );

export const generateRandomTeams = createServerFn()
  .validator(z.object({
    tournamentId: z.string(),
    seed: z.number().optional()
  }))
  .middleware([superTokensAdminFunctionMiddleware])
  .handler(async ({ data }) =>
    toServerResult(async () => {
      const freeAgents = await pbAdmin.getFreeAgents(data.tournamentId);

      if (freeAgents.length < 2) {
        throw new Error("Need at least 2 players to create teams");
      }

      if (freeAgents.length % 2 !== 0) {
        throw new Error("Need an even number of players to create teams");
      }

      const uniqueFreeAgents = Array.from(
        new Map(
          freeAgents
            .filter(fa => fa.player?.id)
            .map(fa => [fa.player!.id, fa])
        ).values()
      );

      if (uniqueFreeAgents.length !== freeAgents.length) {
        logger.warn('Duplicate free agents detected', {
          freeAgentCount: freeAgents.length,
          uniquePlayerCount: uniqueFreeAgents.length
        });
      }

      if (uniqueFreeAgents.length < 2) {
        throw new Error("Need at least 2 unique players to create teams");
      }

      if (uniqueFreeAgents.length % 2 !== 0) {
        throw new Error("Need an even number of unique players to create teams");
      }

      const playerIds = uniqueFreeAgents.map(fa => fa.player!.id);

      const allTeams = await pbAdmin.getTeamsWithFilter(
        playerIds.map(id => `players.id ?= "${id}"`).join(" || "),
        "players,tournaments"
      );

      const invalidPairings = new Set<string>();
      const mostRecentRegionalPartners = new Map<string, string>();

      let mostRecentRegionalDate: Date | null = null;

      for (const team of allTeams) {
        const teamPlayers = (team.expand?.players || []) as any[];
        if (teamPlayers.length !== 2) continue;

        const [p1, p2] = teamPlayers.map((p: any) => p.id).sort();
        const pairKey = `${p1}|${p2}`;

        const teamTournaments = (team.expand?.tournaments || []) as any[];
        const hasMainlineTournament = teamTournaments.some((t: any) => !t.regional);

        if (hasMainlineTournament) {
          invalidPairings.add(pairKey);
        } else if (team.private && teamTournaments.length > 0) {
          const regionalTournaments = teamTournaments.filter((t: any) => t.regional);
          for (const tournament of regionalTournaments) {
            const tournamentDate = new Date(tournament.start_time);
            if (!mostRecentRegionalDate || tournamentDate > mostRecentRegionalDate) {
              mostRecentRegionalDate = tournamentDate;
            }
          }
        }
      }

      if (mostRecentRegionalDate) {
        for (const team of allTeams) {
          if (!team.private) continue;

          const teamPlayers = (team.expand?.players || []) as any[];
          if (teamPlayers.length !== 2) continue;

          const teamTournaments = (team.expand?.tournaments || []) as any[];
          const regionalTournaments = teamTournaments.filter((t: any) => t.regional);

          for (const tournament of regionalTournaments) {
            const tournamentDate = new Date(tournament.start_time);
            if (tournamentDate.getTime() === mostRecentRegionalDate.getTime()) {
              const [p1Id, p2Id] = teamPlayers.map((p: any) => p.id);
              mostRecentRegionalPartners.set(p1Id, p2Id);
              mostRecentRegionalPartners.set(p2Id, p1Id);
            }
          }
        }
      }

      function canPairPlayers(p1Id: string, p2Id: string): boolean {
        const pairKey = [p1Id, p2Id].sort().join('|');
        if (invalidPairings.has(pairKey)) return false;

        const p1LastPartner = mostRecentRegionalPartners.get(p1Id);
        if (p1LastPartner === p2Id) return false;

        return true;
      }

      const seed = data.seed || Math.floor(Math.random() * 1000000);

      function seededRandom(s: number) {
        const x = Math.sin(s++) * 10000;
        return x - Math.floor(x);
      }

      let currentSeed = seed;
      const shuffled = [...uniqueFreeAgents];
      for (let i = shuffled.length - 1; i > 0; i--) {
        const j = Math.floor(seededRandom(currentSeed++) * (i + 1));
        [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
      }

      const assignments = [];
      const paired = new Set<string>();
      const MAX_ATTEMPTS = 1000;
      let attempts = 0;

      while (paired.size < shuffled.length && attempts < MAX_ATTEMPTS) {
        attempts++;

        for (let i = 0; i < shuffled.length; i++) {
          if (paired.has(shuffled[i].player!.id)) continue;

          for (let j = i + 1; j < shuffled.length; j++) {
            if (paired.has(shuffled[j].player!.id)) continue;

            const player1 = shuffled[i].player!;
            const player2 = shuffled[j].player!;

            if (canPairPlayers(player1.id, player2.id)) {
              const teamName = `${player1.first_name} And ${player2.first_name}`;

              assignments.push({
                player1,
                player2,
                teamName
              });

              paired.add(player1.id);
              paired.add(player2.id);
              break;
            }
          }
        }

        if (paired.size < shuffled.length) {
          currentSeed++;
          for (let i = shuffled.length - 1; i > 0; i--) {
            const j = Math.floor(seededRandom(currentSeed++) * (i + 1));
            [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
          }
          assignments.length = 0;
          paired.clear();
        }
      }

      if (paired.size < shuffled.length) {
        throw new Error("Unable to create valid pairings with current restrictions. Please manually adjust enrollments.");
      }

      logger.info('Generated random team assignments with restrictions', {
        tournamentId: data.tournamentId,
        teamCount: assignments.length,
        seed,
        attempts
      });

      return { assignments, seed };
    })
  );

export const confirmTeamAssignments = createServerFn()
  .validator(z.object({
    tournamentId: z.string(),
    assignments: z.array(z.object({
      player1Id: z.string(),
      player2Id: z.string(),
      teamName: z.string()
    }))
  }))
  .middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
  .handler(async ({ data }) =>
    toServerResult(async () => {
      const createdTeams = [];
      let reusedCount = 0;

      for (const assignment of data.assignments) {
        const existingTeams = await pbAdmin.getTeamsWithFilter(
          `private = true && players.id ?= "${assignment.player1Id}" && players.id ?= "${assignment.player2Id}"`,
          "players,tournaments"
        );

        let teamToUse = null;

        for (const team of existingTeams) {
          const teamPlayers = (team.expand?.players || []) as any[];

          if (teamPlayers.length !== 2) continue;

          const playerIds = teamPlayers.map((p: any) => p.id).sort();
          const assignmentIds = [assignment.player1Id, assignment.player2Id].sort();
          if (playerIds[0] !== assignmentIds[0] || playerIds[1] !== assignmentIds[1]) continue;

          const teamTournaments = (team.expand?.tournaments || []) as any[];
          const hasMainlineTournament = teamTournaments.some((t: any) => !t.regional);

          if (!hasMainlineTournament) {
            teamToUse = team;
            break;
          }
        }

        if (teamToUse) {
          await pbAdmin.enrollTeam(data.tournamentId, teamToUse.id);
          createdTeams.push(teamToUse);
          reusedCount++;
          logger.info('Reusing existing regional team', { teamId: teamToUse.id, teamName: teamToUse.name });
        } else {
          const team = await pbAdmin.createTeam({
            name: assignment.teamName,
            players: [assignment.player1Id, assignment.player2Id],
            private: true
          });

          await pbAdmin.enrollTeam(data.tournamentId, team.id);
          createdTeams.push(team);
        }
      }

      for (const assignment of data.assignments) {
        await pbAdmin.unenrollFreeAgent(assignment.player1Id, data.tournamentId);
        await pbAdmin.unenrollFreeAgent(assignment.player2Id, data.tournamentId);
      }

      logger.info('Confirmed team assignments', {
        tournamentId: data.tournamentId,
        teamCount: createdTeams.length,
        reusedCount,
        newCount: createdTeams.length - reusedCount
      });

      emitServerEvent({ type: "team" });
      emitServerEvent({ type: "tournament", tournamentId: data.tournamentId });

      return { teams: createdTeams };
    })
  );

async function calculateGroupStandings(groupId: string): Promise<GroupStanding[]> {
  const group = await pbAdmin.getGroup(groupId);
  if (!group) {
    throw new Error("Group not found");
  }

  const matches = await pbAdmin.getMatchesByGroup(groupId);
  const completedMatches = matches.filter(m => m.status === "ended");

  const standings = new Map<string, GroupStanding>();

  for (const team of group.teams || []) {
    standings.set(team.id, {
      team,
      wins: 0,
      losses: 0,
      cups_for: 0,
      cups_against: 0,
      cup_differential: 0,
      rank: 0,
    });
  }

  for (const match of completedMatches) {
    if (!match.home || !match.away) continue;

    const homeStanding = standings.get(match.home.id);
    const awayStanding = standings.get(match.away.id);

    if (homeStanding && awayStanding) {
      homeStanding.cups_for += match.home_cups;
      homeStanding.cups_against += match.away_cups;
      awayStanding.cups_for += match.away_cups;
      awayStanding.cups_against += match.home_cups;

      if (match.home_cups > match.away_cups) {
        homeStanding.wins++;
        awayStanding.losses++;
      } else {
        awayStanding.wins++;
        homeStanding.losses++;
      }
    }
  }

  for (const standing of standings.values()) {
    standing.cup_differential = standing.cups_for - standing.cups_against;
  }

  const h2hRecords = new Map<string, Map<string, { wins: number; cupDiff: number }>>();

  for (const match of completedMatches) {
    if (!match.home || !match.away) continue;

    if (!h2hRecords.has(match.home.id)) {
      h2hRecords.set(match.home.id, new Map());
    }
    if (!h2hRecords.has(match.away.id)) {
      h2hRecords.set(match.away.id, new Map());
    }

    const homeH2H = h2hRecords.get(match.home.id)!;
    const awayH2H = h2hRecords.get(match.away.id)!;

    if (!homeH2H.has(match.away.id)) {
      homeH2H.set(match.away.id, { wins: 0, cupDiff: 0 });
    }
    if (!awayH2H.has(match.home.id)) {
      awayH2H.set(match.home.id, { wins: 0, cupDiff: 0 });
    }

    const homeRecord = homeH2H.get(match.away.id)!;
    const awayRecord = awayH2H.get(match.home.id)!;

    const cupDiff = match.home_cups - match.away_cups;
    homeRecord.cupDiff += cupDiff;
    awayRecord.cupDiff -= cupDiff;

    if (match.home_cups > match.away_cups) {
      homeRecord.wins++;
    } else {
      awayRecord.wins++;
    }
  }

  const sortedStandings = Array.from(standings.values()).sort((a, b) => {
    if (b.wins !== a.wins) return b.wins - a.wins;

    if (b.cup_differential !== a.cup_differential) return b.cup_differential - a.cup_differential;

    if (b.cups_for !== a.cups_for) return b.cups_for - a.cups_for;

    const aH2H = h2hRecords.get(a.team.id);
    const bH2H = h2hRecords.get(b.team.id);

    if (aH2H && bH2H) {
      const aVsB = aH2H.get(b.team.id);
      const bVsA = bH2H.get(a.team.id);

      if (aVsB && bVsA) {
        if (aVsB.wins !== bVsA.wins) return bVsA.wins - aVsB.wins;

        if (aVsB.cupDiff !== -bVsA.cupDiff) return aVsB.cupDiff - (-bVsA.cupDiff);
      }
    }

    return a.team.id.localeCompare(b.team.id);
  });

  sortedStandings.forEach((standing, index) => {
    standing.rank = index + 1;
  });

  return sortedStandings;
}

export const getGroupStandings = createServerFn()
  .validator(z.string())
  .middleware([superTokensFunctionMiddleware])
  .handler(async ({ data: groupId }) =>
    toServerResult(() => calculateGroupStandings(groupId))
  );

export const generateKnockoutBracket = createServerFn()
  .validator(z.object({
    tournamentId: z.string(),
  }))
  .middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
  .handler(async ({ data }) =>
    toServerResult(async () => {
      logger.info('Generating knockout bracket', {
        tournamentId: data.tournamentId,
      });

      const tournament = await pbAdmin.getTournament(data.tournamentId);
      if (!tournament) {
        throw new Error("Tournament not found");
      }

      if (tournament.phase !== "group_stage") {
        throw new Error("Tournament must be in group_stage phase to generate knockout bracket");
      }

      if (!tournament.group_config) {
        throw new Error("Tournament must have group_config");
      }

      const groups = await pbAdmin.getGroupsByTournament(data.tournamentId);
      if (!groups || groups.length === 0) {
        throw new Error("No groups found for tournament");
      }

      const qualifiedTeams: { teamId: string; groupOrder: number; rank: number }[] = [];
      const allStandings: { standing: GroupStanding; groupOrder: number }[] = [];

      for (const group of groups) {
        const standings = await calculateGroupStandings(group.id);

        for (let i = 0; i < standings.length; i++) {
          allStandings.push({
            standing: standings[i],
            groupOrder: group.order,
          });
        }

        const topTeams = standings.slice(0, tournament.group_config.advance_per_group);
        for (const standing of topTeams) {
          qualifiedTeams.push({
            teamId: standing.team.id,
            groupOrder: group.order,
            rank: standing.rank,
          });
        }
      }

      const orderedTeamIds: string[] = [];
      const maxRank = tournament.group_config.advance_per_group;
      const numGroups = tournament.group_config.num_groups;

      const teamsByGroup: string[][] = [];
      for (let g = 0; g < numGroups; g++) {
        teamsByGroup[g] = [];
      }

      for (const qualified of qualifiedTeams) {
        teamsByGroup[qualified.groupOrder][qualified.rank - 1] = qualified.teamId;
      }

      const totalTeams = numGroups * maxRank;
      for (let i = 0; i < totalTeams / 2; i++) {
        const group1 = i % numGroups;
        const rankIndex1 = Math.floor(i / numGroups);

        const group2 = (i + 1) % numGroups;
        const rankIndex2 = maxRank - 1 - rankIndex1;

        const team1 = teamsByGroup[group1]?.[rankIndex1];
        const team2 = teamsByGroup[group2]?.[rankIndex2];

        if (team1) orderedTeamIds.push(team1);
        if (team2) orderedTeamIds.push(team2);
      }

      let teamCount = orderedTeamIds.length;

      const nextPowerOf2 = Math.pow(2, Math.ceil(Math.log2(teamCount)));
      const wildcardsNeeded = nextPowerOf2 - teamCount;

      if (wildcardsNeeded > 0) {
        const qualifiedTeamIds = new Set(qualifiedTeams.map(t => t.teamId));
        const wildcardCandidates = allStandings
          .filter(s => !qualifiedTeamIds.has(s.standing.team.id))
          .map(s => s.standing);

        wildcardCandidates.sort((a, b) => {
          if (b.wins !== a.wins) return b.wins - a.wins;
          if (b.cup_differential !== a.cup_differential) return b.cup_differential - a.cup_differential;
          if (b.cups_for !== a.cups_for) return b.cups_for - a.cups_for;
          return a.team.id.localeCompare(b.team.id);
        });

        const wildcardTeams = wildcardCandidates.slice(0, wildcardsNeeded);
        const wildcardTeamIds = wildcardTeams.map(t => t.team.id);

        orderedTeamIds.push(...wildcardTeamIds);
        teamCount = orderedTeamIds.length;

        logger.info('Added wildcard teams to knockout bracket', {
          tournamentId: data.tournamentId,
          wildcardsNeeded,
          wildcardTeams: wildcardTeams.map(t => ({
            id: t.team.id,
            name: t.team.name,
            wins: t.wins,
            cupDiff: t.cup_differential,
            cupsFor: t.cups_for
          }))
        });
      }

      let bracketTemplate: any;
      if (Object.keys(brackets).includes(teamCount.toString())) {
        bracketTemplate = brackets[teamCount as keyof typeof brackets];
      } else {
        bracketTemplate = generateSingleEliminationBracket(teamCount);
      }

      const seedToTeamId = new Map<number, string>();
      orderedTeamIds.forEach((teamId, index) => {
        seedToTeamId.set(index + 1, teamId);
      });

      const matchInputs: MatchInput[] = [];
      let matchLid = 1000;

      bracketTemplate.winners.forEach((round: any[]) => {
        round.forEach((match: any) => {
          const matchInput: MatchInput = {
            lid: matchLid++,
            round: match.round,
            order: match.order || 0,
            reset: false,
            bye: match.bye || false,
            home_cups: 0,
            away_cups: 0,
            ot_count: 0,
            home_from_lid: match.home_from_lid === null ? -1 : (match.home_from_lid + 1000),
            away_from_lid: match.away_from_lid === null ? -1 : (match.away_from_lid + 1000),
            home_from_loser: false,
            away_from_loser: false,
            is_losers_bracket: false,
            match_type: "knockout",
            status: "tbd",
            tournament: data.tournamentId,
          };

          if (match.home_seed) {
            const teamId = seedToTeamId.get(match.home_seed);
            if (teamId) {
              matchInput.home = teamId;
              matchInput.home_seed = match.home_seed;
            }
          }

          if (match.away_seed) {
            const teamId = seedToTeamId.get(match.away_seed);
            if (teamId) {
              matchInput.away = teamId;
              matchInput.away_seed = match.away_seed;
            }
          }

          if (matchInput.home && matchInput.away) {
            matchInput.status = "ready";
          }

          matchInputs.push(matchInput);
        });
      });

      const createdMatches = await pbAdmin.createMatches(matchInputs);

      const existingMatchIds = tournament.matches?.map(m => m.id) || [];
      const newMatchIds = createdMatches.map(m => m.id);
      await pbAdmin.updateTournamentMatches(data.tournamentId, [...existingMatchIds, ...newMatchIds]);

      await pbAdmin.updateTournament(data.tournamentId, {
        phase: "knockout"
      });

      logger.info('Knockout bracket generated', {
        tournamentId: data.tournamentId,
        matchCount: createdMatches.length,
        qualifiedTeamCount: qualifiedTeams.length
      });

      emitServerEvent({ type: "tournament", tournamentId: data.tournamentId });

      return {
        tournament,
        matchCount: createdMatches.length,
        matches: createdMatches
      };
    })
  );

export const adminEnrollPlayer = createServerFn()
  .validator(z.object({
    playerId: z.string(),
    tournamentId: z.string()
  }))
  .middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
  .handler(async ({ data }) =>
    toServerResult(async () => {
      await pbAdmin.enrollFreeAgent(data.playerId, "", data.tournamentId);
      logger.info('Admin enrolled player', { playerId: data.playerId, tournamentId: data.tournamentId });
      emitServerEvent({ type: "tournament", tournamentId: data.tournamentId });
    })
  );

export const adminUnenrollPlayer = createServerFn()
  .validator(z.object({
    playerId: z.string(),
    tournamentId: z.string()
  }))
  .middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
  .handler(async ({ data }) =>
    toServerResult(async () => {
      await pbAdmin.unenrollFreeAgent(data.playerId, data.tournamentId);
      logger.info('Admin unenrolled player', { playerId: data.playerId, tournamentId: data.tournamentId });
      emitServerEvent({ type: "tournament", tournamentId: data.tournamentId });
    })
  );

export const generateGroupStage = createServerFn()
  .validator(z.object({
    tournamentId: z.string(),
    groupConfig: z.object({
      num_groups: z.number(),
      teams_per_group: z.number(),
      advance_per_group: z.number(),
      matches_guaranteed: z.number(),
      seeding_method: z.enum(["random", "ranked"]),
    }),
    teamAssignments: z.array(z.object({
      groupIndex: z.number(),
      groupName: z.string(),
      teamIds: z.array(z.string())
    })),
    seed: z.number().optional()
  }))
  .middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
  .handler(async ({ data }) =>
    toServerResult(async () => {
      logger.info('Generating group stage', {
        tournamentId: data.tournamentId,
        numGroups: data.groupConfig.num_groups,
        seed: data.seed
      });

      const tournament = await pbAdmin.getTournament(data.tournamentId);
      if (!tournament) {
        throw new Error("Tournament not found");
      }

      if (tournament.matches && tournament.matches.length > 0) {
        throw new Error("Tournament already has matches generated");
      }

      await pbAdmin.deleteGroupsByTournament(data.tournamentId);

      const createdGroups = [];
      const groupStageMatches = [];

      for (const assignment of data.teamAssignments) {
        const group = await pbAdmin.createGroup({
          tournament: data.tournamentId,
          name: assignment.groupName,
          order: assignment.groupIndex,
          teams: assignment.teamIds
        });

        createdGroups.push(group);

        const teamIds = assignment.teamIds;

        for (let i = 0; i < teamIds.length; i++) {
          for (let j = i + 1; j < teamIds.length; j++) {
            groupStageMatches.push({
              lid: -1,
              round: -1,
              order: 0,

              reset: false,
              bye: false,
              home: teamIds[i],
              away: teamIds[j],
              home_cups: 0,
              away_cups: 0,
              ot_count: 0,
              home_from_lid: -1,
              away_from_lid: -1,
              home_from_loser: false,
              away_from_loser: false,
              is_losers_bracket: false,
              match_type: "group_stage" as const,
              group: group.id,
              status: "ready" as const,
              tournament: data.tournamentId,
            });
          }
        }
      }

      const orderedGroupMatches = orderGroupStageMatches(
        groupStageMatches,
        (m) => ({ groupId: m.group, teamA: m.home, teamB: m.away })
      );
      orderedGroupMatches.forEach((match, index) => {
        match.order = index + 1;
      });

      const knockoutTeamCount = data.groupConfig.num_groups * data.groupConfig.advance_per_group;

      const nextPowerOf2 = Math.pow(2, Math.ceil(Math.log2(knockoutTeamCount)));
      const bracketSize = nextPowerOf2;

      let bracketTemplate: any;

      if (Object.keys(brackets).includes(bracketSize.toString())) {
        bracketTemplate = brackets[bracketSize as keyof typeof brackets];
      } else {
        bracketTemplate = generateSingleEliminationBracket(bracketSize);
      }

      logger.info('Creating knockout bracket template', {
        tournamentId: data.tournamentId,
        knockoutTeamCount,
        bracketSize,
        wildcardsNeeded: bracketSize - knockoutTeamCount
      });

      const knockoutMatches: any[] = [];

      bracketTemplate.winners.forEach((round: any[]) => {
        round.forEach((match: any) => {
          knockoutMatches.push({
            lid: match.lid,
            round: match.round,
            order: match.order,
            reset: false,
            bye: match.bye || false,
            home_seed: match.home_seed,
            away_seed: match.away_seed,
            home_cups: 0,
            away_cups: 0,
            ot_count: 0,
            home_from_lid: match.home_from_lid !== null ? match.home_from_lid : -1,
            away_from_lid: match.away_from_lid !== null ? match.away_from_lid : -1,
            home_from_loser: false,
            away_from_loser: false,
            is_losers_bracket: false,
            match_type: "knockout" as const,
            status: "tbd" as const,
            tournament: data.tournamentId,
          });
        });
      });

      const allMatches = [...groupStageMatches, ...knockoutMatches];
      const createdMatches = await pbAdmin.createMatches(allMatches);

      const matchIds = createdMatches.map((match) => match.id);
      await pbAdmin.updateTournamentMatches(data.tournamentId, matchIds);

      await pbAdmin.updateTournament(data.tournamentId, {
        phase: "group_stage",
        group_config: data.groupConfig
      });

      logger.info('Group stage and knockout bracket generated', {
        tournamentId: data.tournamentId,
        groupCount: createdGroups.length,
        groupMatchCount: groupStageMatches.length,
        knockoutMatchCount: knockoutMatches.length,
        totalMatchCount: createdMatches.length
      });

      emitServerEvent({ type: "tournament", tournamentId: data.tournamentId });

      return {
        tournament,
        groups: createdGroups,
        matchCount: createdMatches.length,
        matches: createdMatches
      };
    })
  );
