import { msg } from "@lingui/core/macro";
import type { I18n } from "@lingui/core";
import { Match } from "@/features/matches/types";
import { Team, TeamInfo } from "@/features/teams/types";
import { Tournament } from "@/features/tournaments/types";
import { PicksMap } from "./types";

const teamId = (team?: TeamInfo | Team | string): string | undefined =>
  typeof team === "string" ? team : team?.id;

const asTeamInfo = (team?: TeamInfo | Team | string): TeamInfo | undefined =>
  typeof team === "string" ? undefined : team;

export interface ResolvedMatch {
  lid: number;
  home?: TeamInfo;
  homeId?: string;
  away?: TeamInfo;
  awayId?: string;
  pickedWinner?: TeamInfo;
  pickedWinnerId?: string;
  pickedLoser?: TeamInfo;
  pickedLoserId?: string;
  resetNecessary?: boolean;
}

const isBracketMatch = (match: Match) => match.round !== -1 && !match.bye;

export const getPickableMatches = (
  matches: Match[],
  picks: PicksMap
): Match[] => {
  const resolved = resolvePredictedBracket(matches, picks);
  return matches
    .filter(
      (match) =>
        isBracketMatch(match) &&
        (!match.reset || resolved.get(match.lid)?.resetNecessary)
    )
    .sort((a, b) => a.lid - b.lid);
};

export const isPredictionLocked = (matches: Match[]): boolean =>
  matches.some(
    (match) => match.status === "started" || match.status === "ended"
  );

export const isTournamentPredictable = (tournament: Tournament): boolean => {
  const matches = tournament.matches || [];
  return (
    !tournament.regional &&
    matches.length > 0 &&
    !matches.some((match) => match.round === -1)
  );
};

const resolveInternal = (
  matches: Match[],
  picks: PicksMap,
  prune: boolean
): { resolved: Map<number, ResolvedMatch>; picks: PicksMap } => {
  const resolved = new Map<number, ResolvedMatch>();
  const nextPicks: PicksMap = { ...picks };

  const bracketMatches = matches
    .filter(isBracketMatch)
    .sort((a, b) => a.lid - b.lid);

  for (const match of bracketMatches) {
    const entry: ResolvedMatch = { lid: match.lid };

    if (match.home_from_lid === -1) {
      entry.home = asTeamInfo(match.home);
      entry.homeId = teamId(match.home);
    } else {
      const source = resolved.get(match.home_from_lid);
      entry.home = match.home_from_loser
        ? source?.pickedLoser
        : source?.pickedWinner;
      entry.homeId = match.home_from_loser
        ? source?.pickedLoserId
        : source?.pickedWinnerId;
    }

    if (match.away_from_lid === -1) {
      entry.away = asTeamInfo(match.away);
      entry.awayId = teamId(match.away);
    } else {
      const source = resolved.get(match.away_from_lid);
      entry.away = match.away_from_loser
        ? source?.pickedLoser
        : source?.pickedWinner;
      entry.awayId = match.away_from_loser
        ? source?.pickedLoserId
        : source?.pickedWinnerId;
    }

    let pickEligible = true;
    if (match.reset) {
      const grandFinal = resolved.get(match.home_from_lid);
      entry.resetNecessary =
        !!grandFinal?.pickedWinnerId &&
        grandFinal.pickedWinnerId === grandFinal.awayId;
      pickEligible = entry.resetNecessary;
    }

    const pickId = nextPicks[String(match.lid)];
    if (pickEligible && pickId && pickId === entry.homeId) {
      entry.pickedWinner = entry.home;
      entry.pickedWinnerId = entry.homeId;
      entry.pickedLoser = entry.away;
      entry.pickedLoserId = entry.awayId;
    } else if (pickEligible && pickId && pickId === entry.awayId) {
      entry.pickedWinner = entry.away;
      entry.pickedWinnerId = entry.awayId;
      entry.pickedLoser = entry.home;
      entry.pickedLoserId = entry.homeId;
    } else if (pickId && prune) {
      delete nextPicks[String(match.lid)];
    }

    resolved.set(match.lid, entry);
  }

  return { resolved, picks: nextPicks };
};

export const resolvePredictedBracket = (
  matches: Match[],
  picks: PicksMap
): Map<number, ResolvedMatch> => resolveInternal(matches, picks, false).resolved;

export const setPick = (
  matches: Match[],
  picks: PicksMap,
  lid: number,
  pickedTeamId: string
): PicksMap =>
  resolveInternal(
    matches,
    { ...picks, [String(lid)]: pickedTeamId },
    true
  ).picks;

export const isPredictionComplete = (
  matches: Match[],
  picks: PicksMap
): boolean => {
  const resolved = resolvePredictedBracket(matches, picks);
  return getPickableMatches(matches, picks).every(
    (match) => resolved.get(match.lid)?.pickedWinnerId
  );
};

export const getMatchLabel = (
  i18n: I18n,
  matches: Match[],
  match: Match
): string => {
  if (match.reset) return i18n._(msg`Bracket Reset`);

  const winners = matches.filter(
    (m) => isBracketMatch(m) && !m.reset && !m.is_losers_bracket
  );
  const grandFinal = winners.reduce(
    (highest: Match | undefined, current) =>
      !highest || current.lid > highest.lid ? current : highest,
    undefined
  );
  if (!grandFinal) return i18n._({ ...msg`Match {order}`, values: { order: match.order } });

  const hasLosersBracket = matches.some(
    (m) => isBracketMatch(m) && m.is_losers_bracket
  );

  if (match.lid === grandFinal.lid) return i18n._(msg`Final`);
  if (
    hasLosersBracket &&
    !match.is_losers_bracket &&
    grandFinal.home_from_lid === match.lid
  ) {
    return i18n._(msg`Winners Bracket Final`);
  }
  if (match.is_losers_bracket && grandFinal.away_from_lid === match.lid) {
    return i18n._(msg`Losers Bracket Final`);
  }
  return i18n._({ ...msg`Match {order}`, values: { order: match.order } });
};

export type PickResult = "correct" | "incorrect" | "pending";

export interface PredictionScore {
  points: number;
  correct: number;
  total: number;
  perMatch: Map<number, PickResult>;
  predictedChampionId?: string;
}

export const getMatchPoints = (match: Match): number =>
  (match.is_losers_bracket ? 5 : 10) * 2 ** match.round;

export const computePredictionScore = (
  matches: Match[],
  picks: PicksMap
): PredictionScore => {
  const pickable = getPickableMatches(matches, picks);
  const perMatch = new Map<number, PickResult>();

  let points = 0;
  let correct = 0;

  for (const match of pickable) {
    const pickId = picks[String(match.lid)];

    if (match.status !== "ended") {
      perMatch.set(match.lid, "pending");
      continue;
    }

    const actualWinnerId =
      match.home_cups > match.away_cups ? teamId(match.home) : teamId(match.away);

    if (pickId && actualWinnerId && pickId === actualWinnerId) {
      perMatch.set(match.lid, "correct");
      points += getMatchPoints(match);
      correct += 1;
    } else {
      perMatch.set(match.lid, "incorrect");
    }
  }

  const grandFinal = pickable
    .filter((match) => !match.is_losers_bracket)
    .at(-1);

  return {
    points,
    correct,
    total: pickable.length,
    perMatch,
    predictedChampionId: grandFinal
      ? picks[String(grandFinal.lid)]
      : undefined,
  };
};
