import type { FetchedStandingsRecord, StandingsSyncAdapter } from "./types";

const MLS_STANDINGS_URL =
  "https://site.api.espn.com/apis/v2/sports/soccer/usa.1/standings";

interface EspnStat {
  name: string;
  displayName?: string;
  abbreviation?: string;
  value?: number;
  displayValue?: string;
}

interface EspnTeam {
  id: string;
  displayName: string;
  shortDisplayName?: string;
  abbreviation?: string;
}

interface EspnStandingsEntry {
  team: EspnTeam;
  stats: EspnStat[];
}

interface EspnStandingsGroup {
  name?: string;
  standings?: { entries?: EspnStandingsEntry[] };
  children?: EspnStandingsGroup[];
}

interface EspnStandingsResponse {
  children?: EspnStandingsGroup[];
}

function statsMap(stats: EspnStat[]): Map<string, EspnStat> {
  const map = new Map<string, EspnStat>();
  for (const stat of stats) {
    map.set(stat.name, stat);
  }
  return map;
}

function flattenEspnStandings(
  response: EspnStandingsResponse
): Array<{ entry: EspnStandingsEntry; conference: string }> {
  const results: Array<{ entry: EspnStandingsEntry; conference: string }> = [];

  for (const confGroup of response.children ?? []) {
    const conferenceName = confGroup.name ?? "";

    if (confGroup.children && confGroup.children.length > 0) {
      for (const subGroup of confGroup.children) {
        for (const entry of subGroup.standings?.entries ?? []) {
          results.push({ entry, conference: conferenceName });
        }
      }
    } else {
      for (const entry of confGroup.standings?.entries ?? []) {
        results.push({ entry, conference: conferenceName });
      }
    }
  }

  return results;
}

/**
 * Normalize ESPN conference group names to "Eastern" or "Western".
 * ESPN returns names like "Eastern Conference" or "Western Conference".
 */
function normalizeConference(raw: string): string {
  const upper = raw.toUpperCase();
  if (upper === "EASTERN" || upper === "EASTERN CONFERENCE") return "Eastern";
  if (upper === "WESTERN" || upper === "WESTERN CONFERENCE") return "Western";
  return raw;
}

/**
 * MLS standings adapter backed by ESPN's free soccer standings API.
 *
 * Endpoint: GET /apis/v2/sports/soccer/usa.1/standings
 * No API key required.
 */
export class MlsStandingsAdapter implements StandingsSyncAdapter {
  async fetchStandings(): Promise<FetchedStandingsRecord[]> {
    const response = await fetch(MLS_STANDINGS_URL);
    if (!response.ok) {
      throw new Error(
        `MLS standings API returned ${response.status}: ${response.statusText}`
      );
    }

    const json = (await response.json()) as EspnStandingsResponse;
    const flattened = flattenEspnStandings(json);

    if (flattened.length === 0) {
      throw new Error(
        "MLS standings API returned no entries — response shape may have changed"
      );
    }

    // Pre-parse each entry's stats once, then sort by points desc for overall league rank
    const parsed = flattened.map(({ entry, conference }) => ({
      entry,
      conference,
      sm: statsMap(entry.stats),
    }));

    const sorted = [...parsed].toSorted((a, b) => {
      const ptsA = a.sm.get("points")?.value ?? 0;
      const ptsB = b.sm.get("points")?.value ?? 0;
      if (ptsB !== ptsA) return ptsB - ptsA;
      const gdA = a.sm.get("pointDifferential")?.value ?? 0;
      const gdB = b.sm.get("pointDifferential")?.value ?? 0;
      return gdB - gdA;
    });

    return sorted.map(({ entry, conference, sm }, leagueIdx): FetchedStandingsRecord => {
      const wins = sm.get("wins")?.value ?? 0;
      const losses = sm.get("losses")?.value ?? 0;
      const ties = sm.get("ties")?.value ?? sm.get("draws")?.value ?? 0;
      const gamesPlayed = Math.round(
        sm.get("gamesPlayed")?.value ?? wins + losses + ties
      );

      const tablePoints = sm.get("points")?.value;
      const goalsFor = sm.get("pointsFor")?.value ?? sm.get("gf")?.value;
      const goalsAgainst = sm.get("pointsAgainst")?.value ?? sm.get("ga")?.value;
      const goalDifference = sm.get("pointDifferential")?.value ?? sm.get("gd")?.value;

      const playoffSeedStat = sm.get("playoffSeed");
      const conferenceRank =
        playoffSeedStat?.value !== null && playoffSeedStat?.value !== undefined
          ? Math.round(playoffSeedStat.value)
          : playoffSeedStat?.displayValue
          ? parseInt(playoffSeedStat.displayValue, 10) || undefined
          : undefined;

      const homeRecord = sm.get("Home")?.displayValue ?? undefined;
      const awayRecord = sm.get("Road")?.displayValue ?? undefined;

      return {
        teamName: entry.team.displayName,
        externalTeamId: entry.team.id,
        wins: Math.round(wins),
        losses: Math.round(losses),
        ties: Math.round(ties),
        tablePoints: tablePoints !== null && tablePoints !== undefined ? Math.round(tablePoints) : undefined,
        goalsFor: goalsFor !== null && goalsFor !== undefined ? Math.round(goalsFor) : undefined,
        goalsAgainst: goalsAgainst !== null && goalsAgainst !== undefined ? Math.round(goalsAgainst) : undefined,
        goalDifference: goalDifference !== null && goalDifference !== undefined ? Math.round(goalDifference) : undefined,
        winPct: gamesPlayed > 0 ? wins / gamesPlayed : 0,
        gamesPlayed,
        conference: normalizeConference(conference),
        conferenceRank,
        leagueRank: leagueIdx + 1,
        homeRecord,
        awayRecord,
      };
    });
  }
}
