import { superTokensAdminFunctionMiddleware } from "@/utils/supertokens";
import { createServerFn } from "@tanstack/react-start";
import { pbAdmin } from "@/lib/pocketbase/client";
import { logger } from "@/lib/logger";
import { z } from "zod";
import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result";
import brackets from "@/features/bracket/utils";
import { Match, MatchInput } from "@/features/matches/types";
import { serverEvents } from "@/lib/events/emitter";
import { superTokensFunctionMiddleware } from "@/utils/supertokens";
import { PlayerInfo } from "../players/types";
import { serverFnLoggingMiddleware } from "@/utils/activities";

const orderedTeamsSchema = z.object({
  tournamentId: z.string(),
  orderedTeamIds: z.array(z.string()),
});

export const generateTournamentBracket = createServerFn()
  .inputValidator(orderedTeamsSchema)
  .middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
  .handler(async ({ data: { tournamentId, orderedTeamIds } }) =>
    toServerResult(async () => {
      logger.info("Generating tournament bracket", {
        tournamentId,
        teamCount: orderedTeamIds.length,
      });

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

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

      const teamCount = orderedTeamIds.length;
      if (!Object.keys(brackets).includes(teamCount.toString())) {
        throw new Error(`Bracket not available for ${teamCount} teams`);
      }

      const bracketTemplate = brackets[
        teamCount as keyof typeof brackets
      ] as any;

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

      const matchInputs: MatchInput[] = [];

      bracketTemplate.winners.forEach((round: any[]) => {
        round.forEach((match: any) => {
          const matchInput: MatchInput = {
            lid: match.lid,
            round: match.round,
            order: match.order || 0,
            reset: match.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,
            away_from_lid: match.away_from_lid === null ? -1 : match.away_from_lid,
            home_from_loser: match.home_from_loser || false,
            away_from_loser: match.away_from_loser || false,
            is_losers_bracket: false,
            status: "tbd",
            tournament: 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);
        });
      });

      bracketTemplate.losers.forEach((round: any[]) => {
        round.forEach((match: any) => {
          const matchInput: MatchInput = {
            lid: match.lid,
            round: match.round,
            order: match.order || 0,
            reset: match.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,
            away_from_lid: match.away_from_lid === null ? -1 : match.away_from_lid,
            home_from_loser: match.home_from_loser || false,
            away_from_loser: match.away_from_loser || false,
            is_losers_bracket: true,
            status: "tbd",
            tournament: tournamentId,
          };

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

      const createdMatches = await pbAdmin.createMatches(matchInputs);

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

      logger.info("Tournament bracket generated", {
        tournamentId,
        matchCount: createdMatches.length,
      });

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

export const startMatch = createServerFn()
  .inputValidator(z.string())
  .middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
  .handler(async ({ data }) =>
    toServerResult(async () => {
      logger.info("Starting match", data);

      let match = await pbAdmin.getMatch(data);
      if (!match) {
        throw new Error("Match not found");
      }

      match = await pbAdmin.updateMatch(data, {
        start_time: new Date().toISOString(),
        status: "started",
      });

      serverEvents.emit("match", {
        type: "match",
        matchId: match.id,
        tournamentId: match.tournament.id
      });

      return match;
    })
  );

export const populateKnockoutBracket = createServerFn()
  .inputValidator(z.object({
    tournamentId: z.string(),
  }))
  .middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
  .handler(async ({ data: { tournamentId } }) =>
    toServerResult(async () => {
      const tournament = await pbAdmin.getTournament(tournamentId);
      if (!tournament) {
        throw new Error("Tournament not found");
      }

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

      return await populateKnockoutBracketInternal(tournamentId, tournament.group_config);
    })
  );

async function populateKnockoutBracketInternal(tournamentId: string, groupConfig: { num_groups: number; advance_per_group: number }) {
  logger.info('Populating knockout bracket', { tournamentId });

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

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

  for (const group of groups) {
    logger.info('Processing group', {
      groupId: group.id,
      groupOrder: group.order,
      teamsCount: group.teams?.length,
      teams: group.teams
    });

    const groupMatches = await pbAdmin.getMatchesByGroup(group.id);
    const completedMatches = groupMatches.filter(m => m.status === "ended");

    const standings = new Map<string, { teamId: string; wins: number; losses: number; cups_for: number; cups_against: number; cup_differential: number }>();

    for (const team of group.teams || []) {
      // group.teams can be either team objects or just team ID strings
      const teamId = typeof team === 'string' ? team : team.id;
      standings.set(teamId, {
        teamId,
        wins: 0,
        losses: 0,
        cups_for: 0,
        cups_against: 0,
        cup_differential: 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++;
        }

        homeStanding.cup_differential = homeStanding.cups_for - homeStanding.cups_against;
        awayStanding.cup_differential = awayStanding.cups_for - awayStanding.cups_against;
      }
    }

    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;
      return b.cups_for - a.cups_for;
    });

    const topTeams = sortedStandings.slice(0, groupConfig.advance_per_group);
    logger.info('Top teams from group', {
      groupId: group.id,
      topTeams: topTeams.map(t => ({ teamId: t.teamId, wins: t.wins, cupDiff: t.cup_differential }))
    });

    topTeams.forEach((standing, index) => {
      qualifiedTeams.push({
        teamId: standing.teamId,
        groupOrder: group.order,
        rank: index + 1,
      });
    });
  }

  logger.info('Qualified teams', { qualifiedTeams });

  const orderedTeamIds: string[] = [];
  const maxRank = groupConfig.advance_per_group;

  for (let rank = 1; rank <= maxRank; rank++) {
    const teamsAtRank = qualifiedTeams
      .filter(t => t.rank === rank)
      .sort((a, b) => a.groupOrder - b.groupOrder);
    orderedTeamIds.push(...teamsAtRank.map(t => t.teamId));
  }

  logger.info('Ordered team IDs', { orderedTeamIds });

  const tournament = await pbAdmin.getTournament(tournamentId);
  const knockoutMatches = (tournament.matches || [])
    .filter((m: Match) => m.round >= 0 && m.lid >= 0)
    .sort((a: Match, b: Match) => a.lid - b.lid);

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

  logger.info('Seed to team mapping', {
    seedToTeamId: Array.from(seedToTeamId.entries()),
    orderedTeamIds
  });

  let updatedCount = 0;
  for (const match of knockoutMatches) {
    if (match.round === 0) {
      const updates: any = {};

      if (match.home_seed) {
        const teamId = seedToTeamId.get(match.home_seed);
        logger.info('Looking up home seed', {
          matchId: match.id,
          home_seed: match.home_seed,
          teamId
        });
        if (teamId) {
          updates.home = teamId;
        }
      }

      if (match.away_seed) {
        const teamId = seedToTeamId.get(match.away_seed);
        logger.info('Looking up away seed', {
          matchId: match.id,
          away_seed: match.away_seed,
          teamId
        });
        if (teamId) {
          updates.away = teamId;
        }
      }

      if (updates.home && updates.away) {
        updates.status = "ready";
      } else if (updates.home || updates.away) {
        updates.status = "tbd";
      }

      if (Object.keys(updates).length > 0) {
        logger.info('Updating match', { matchId: match.id, updates });
        await pbAdmin.updateMatch(match.id, updates);
        updatedCount++;
      }
    }
  }

  logger.info('Updated matches', { updatedCount, totalKnockoutMatches: knockoutMatches.length });

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

  logger.info('Knockout bracket populated successfully', { tournamentId });
}

const endMatchSchema = z.object({
  matchId: z.string(),
  home_cups: z.number(),
  away_cups: z.number(),
  ot_count: z.number(),
});
export const endMatch = createServerFn()
  .inputValidator(endMatchSchema)
  .middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
  .handler(async ({ data: { matchId, home_cups, away_cups, ot_count } }) =>
    toServerResult(async () => {
      logger.info("Ending match", matchId);

      let match = await pbAdmin.getMatch(matchId);
      if (!match) {
        throw new Error("Match not found");
      }

      match = await pbAdmin.updateMatch(matchId, {
        end_time: new Date().toISOString(),
        status: "ended",
        home_cups,
        away_cups,
        ot_count,
      });

      if (match.lid === -1) {
        serverEvents.emit("match", {
          type: "match",
          matchId: match.id,
          tournamentId: match.tournament.id
        });
        return match;
      }

      const matchWinner = home_cups > away_cups ? match.home : match.away;
      const matchLoser = home_cups < away_cups ? match.home : match.away;
      if (!matchWinner || !matchLoser) throw new Error("Something went wrong");

      const { winner, loser } = await pbAdmin.getChildMatches(matchId);

      if (winner && winner.reset) {
        const awayTeamWon = match.away === matchWinner;

        if (!awayTeamWon) {
          logger.info("Deleting reset match", {
            resetMatchId: winner.id,
            currentMatchId: match.id,
            reason: "not necessary",
          });

          await pbAdmin.deleteMatch(winner.id);
          return match;
        }
      }

      if (winner) {
        await pbAdmin.updateMatch(winner.id, {
          [winner.home_from_lid === match.lid ? "home" : "away"]: matchWinner.id,
          status:
            (winner.home_from_lid === match.lid && winner.away) ||
            (winner.away_from_lid === match.lid && winner.home)
              ? "ready"
              : "tbd",
        });
      }

      if (loser) {
        await pbAdmin.updateMatch(loser.id, {
          [loser.home_from_lid === match.lid ? "home" : "away"]: matchLoser.id,
          status:
            (loser.home_from_lid === match.lid && loser.away) ||
            (loser.away_from_lid === match.lid && loser.home)
              ? "ready"
              : "tbd",
        });
      }

      serverEvents.emit("match", {
        type: "match",
        matchId: match.id,
        tournamentId: match.tournament.id
      });

      return match;
    })
  );

const toggleReactionSchema = z.object({
  matchId: z.string(),
  emoji: z.string(),
});

export const toggleMatchReaction = createServerFn()
  .inputValidator(toggleReactionSchema)
  .middleware([superTokensFunctionMiddleware])
  .handler(async ({ data: { matchId, emoji }, context }) =>
    toServerResult(async () => {
      const user = await pbAdmin.getPlayerByAuthId(context.userAuthId);
      const userId = user?.id;
      if (!userId) return;

      const match = await pbAdmin.getMatch(matchId);
      if (!match) {
        throw new Error("Match not found");
      }

      const existingReaction = await pbAdmin.getUserReaction(matchId, userId, emoji);
      if (existingReaction) {
        await pbAdmin.deleteReaction(existingReaction.id);
        logger.info("Removed reaction", { matchId, emoji, userId });
      } else {
        await pbAdmin.createReaction(matchId, userId, emoji);
        logger.info("Added reaction", { matchId, emoji, userId });
      }

      const all = await pbAdmin.getReactionsForMatch(matchId);

      const reactionsByEmoji = all.reduce((acc, reaction) => {
        const emoji = reaction.emoji;
        if (!acc[emoji]) {
          acc[emoji] = {
            emoji,
            count: 0,
            players: [],
          };
        }
        acc[emoji].count++;
        acc[emoji].players.push({
          id: reaction?.player?.id,
          first_name: reaction.player?.first_name,
          last_name: reaction.player?.last_name,
        });
        return acc;
      }, {} as Record<string, any>);

      const reactions = Object.values(reactionsByEmoji);

      serverEvents.emit("reaction", {
        type: "reaction",
        matchId,
        reactions,
      });

      return reactions as Reaction[]
    })
  );

export interface Reaction {
  emoji: string;
  count: number;
  players: PlayerInfo[];
}
export const getMatchReactions = createServerFn()
  .inputValidator(z.string())
  .middleware([superTokensFunctionMiddleware])
  .handler(async ({ data: matchId, context }) =>
    toServerResult(async () => {
      const match = await pbAdmin.getMatch(matchId);
      if (!match) {
        throw new Error("Match not found");
      }

      const all = await pbAdmin.getReactionsForMatch(matchId);

      const reactionsByEmoji = all.reduce((acc, reaction) => {
        const emoji = reaction.emoji;
        if (!acc[emoji]) {
          acc[emoji] = {
            emoji,
            count: 0,
            players: [],
          };
        }
        acc[emoji].count++;
        acc[emoji].players.push({
          id: reaction?.player?.id,
          first_name: reaction.player?.first_name,
          last_name: reaction.player?.last_name,
        });
        return acc;
      }, {} as Record<string, any>);

      const reactions = Object.values(reactionsByEmoji);

      return reactions as Reaction[]
    })
  );

const matchesBetweenPlayersSchema = z.object({
  player1Id: z.string(),
  player2Id: z.string(),
});

export const getMatchesBetweenPlayers = createServerFn()
  .inputValidator(matchesBetweenPlayersSchema)
  .middleware([superTokensFunctionMiddleware])
  .handler(async ({ data: { player1Id, player2Id } }) =>
    toServerResult(async () => {
      logger.info("Getting matches between players", { player1Id, player2Id });
      const matches = await pbAdmin.getMatchesBetweenPlayers(player1Id, player2Id);
      return matches;
    })
  );

const matchesBetweenTeamsSchema = z.object({
  team1Id: z.string(),
  team2Id: z.string(),
});

export const getMatchesBetweenTeams = createServerFn()
  .inputValidator(matchesBetweenTeamsSchema)
  .middleware([superTokensFunctionMiddleware])
  .handler(async ({ data: { team1Id, team2Id } }) =>
    toServerResult(async () => {
      logger.info("Getting matches between teams", { team1Id, team2Id });
      const matches = await pbAdmin.getMatchesBetweenTeams(team1Id, team2Id);
      return matches;
    })
  );
