import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { MlsStandingsAdapter } from "../mls";

function makeStat(name: string, value: number, displayValue?: string) {
  return { name, value, displayValue: displayValue ?? String(value) };
}

const SAMPLE_MLS_RESPONSE = {
  children: [
    {
      name: "Eastern Conference",
      standings: {
        entries: [
          {
            team: { id: "371", displayName: "Inter Miami CF", shortDisplayName: "Inter Miami", abbreviation: "MIA" },
            stats: [
              makeStat("wins", 18),
              makeStat("losses", 8),
              makeStat("ties", 8),
              makeStat("points", 62),
              makeStat("pointsFor", 55),
              makeStat("pointsAgainst", 38),
              makeStat("pointDifferential", 17),
              makeStat("playoffSeed", 1),
              { name: "Home", value: 0, displayValue: "10-3-3" },
              { name: "Road", value: 0, displayValue: "8-5-5" },
            ],
          },
          {
            team: { id: "928", displayName: "Columbus Crew", shortDisplayName: "Columbus", abbreviation: "CLB" },
            stats: [
              makeStat("wins", 15),
              makeStat("losses", 10),
              makeStat("ties", 9),
              makeStat("points", 54),
              makeStat("pointsFor", 48),
              makeStat("pointsAgainst", 42),
              makeStat("pointDifferential", 6),
              makeStat("playoffSeed", 2),
              { name: "Home", value: 0, displayValue: "9-4-4" },
              { name: "Road", value: 0, displayValue: "6-6-5" },
            ],
          },
        ],
      },
    },
    {
      name: "Western Conference",
      standings: {
        entries: [
          {
            team: { id: "339", displayName: "Los Angeles FC", shortDisplayName: "LAFC", abbreviation: "LAFC" },
            stats: [
              makeStat("wins", 20),
              makeStat("losses", 6),
              makeStat("ties", 8),
              makeStat("points", 68),
              makeStat("pointsFor", 62),
              makeStat("pointsAgainst", 30),
              makeStat("pointDifferential", 32),
              makeStat("playoffSeed", 1),
              { name: "Home", value: 0, displayValue: "12-2-3" },
              { name: "Road", value: 0, displayValue: "8-4-5" },
            ],
          },
        ],
      },
    },
  ],
};

describe("MlsStandingsAdapter", () => {
  beforeEach(() => {
    vi.stubGlobal("fetch", vi.fn());
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it("maps ESPN API response to FetchedStandingsRecord[]", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLS_RESPONSE,
    } as Response);

    const adapter = new MlsStandingsAdapter();
    const records = await adapter.fetchStandings();

    expect(records).toHaveLength(3);
  });

  it("maps soccer-specific fields (W/D/L/GF/GA/GD/PTS)", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLS_RESPONSE,
    } as Response);

    const adapter = new MlsStandingsAdapter();
    const records = await adapter.fetchStandings();

    const miami = records.find((r) => r.teamName === "Inter Miami CF");
    if (!miami) throw new Error("Inter Miami CF not found");
    expect(miami.wins).toBe(18);
    expect(miami.losses).toBe(8);
    expect(miami.ties).toBe(8);
    expect(miami.tablePoints).toBe(62);
    expect(miami.goalsFor).toBe(55);
    expect(miami.goalsAgainst).toBe(38);
    expect(miami.goalDifference).toBe(17);
    expect(miami.gamesPlayed).toBe(34); // 18+8+8
  });

  it("normalizes conference names to Eastern/Western", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLS_RESPONSE,
    } as Response);

    const adapter = new MlsStandingsAdapter();
    const records = await adapter.fetchStandings();

    const miami = records.find((r) => r.teamName === "Inter Miami CF");
    const lafc = records.find((r) => r.teamName === "Los Angeles FC");
    if (!miami || !lafc) throw new Error("Teams not found");

    expect(miami.conference).toBe("Eastern");
    expect(lafc.conference).toBe("Western");
  });

  it("assigns conferenceRank from playoffSeed stat", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLS_RESPONSE,
    } as Response);

    const adapter = new MlsStandingsAdapter();
    const records = await adapter.fetchStandings();

    const miami = records.find((r) => r.teamName === "Inter Miami CF");
    const columbus = records.find((r) => r.teamName === "Columbus Crew");
    if (!miami || !columbus) throw new Error("Teams not found");

    expect(miami.conferenceRank).toBe(1);
    expect(columbus.conferenceRank).toBe(2);
  });

  it("assigns leagueRank by points descending", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLS_RESPONSE,
    } as Response);

    const adapter = new MlsStandingsAdapter();
    const records = await adapter.fetchStandings();

    // LAFC has 68 pts, Miami 62, Columbus 54 → ranks 1, 2, 3
    const lafc = records.find((r) => r.teamName === "Los Angeles FC");
    const miami = records.find((r) => r.teamName === "Inter Miami CF");
    const columbus = records.find((r) => r.teamName === "Columbus Crew");
    if (!lafc || !miami || !columbus) throw new Error("Teams not found");

    expect(lafc.leagueRank).toBe(1);
    expect(miami.leagueRank).toBe(2);
    expect(columbus.leagueRank).toBe(3);
  });

  it("uses externalTeamId from ESPN team id", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLS_RESPONSE,
    } as Response);

    const adapter = new MlsStandingsAdapter();
    const records = await adapter.fetchStandings();

    const lafc = records.find((r) => r.teamName === "Los Angeles FC");
    if (!lafc) throw new Error("LAFC not found");
    expect(lafc.externalTeamId).toBe("339");
  });

  it("maps home and away records", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLS_RESPONSE,
    } as Response);

    const adapter = new MlsStandingsAdapter();
    const records = await adapter.fetchStandings();

    const miami = records.find((r) => r.teamName === "Inter Miami CF");
    if (!miami) throw new Error("Inter Miami CF not found");
    expect(miami.homeRecord).toBe("10-3-3");
    expect(miami.awayRecord).toBe("8-5-5");
  });

  it("throws on non-ok response", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: false,
      status: 429,
      statusText: "Too Many Requests",
    } as Response);

    const adapter = new MlsStandingsAdapter();
    await expect(adapter.fetchStandings()).rejects.toThrow("429");
  });

  it("throws when no entries returned", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => ({ children: [] }),
    } as Response);

    const adapter = new MlsStandingsAdapter();
    await expect(adapter.fetchStandings()).rejects.toThrow("no entries");
  });

  it("passes through unrecognized conference names unchanged", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => ({
        children: [
          {
            name: "Southern Division",
            standings: {
              entries: [
                {
                  team: { id: "1", displayName: "Test FC" },
                  stats: [
                    makeStat("wins", 5),
                    makeStat("losses", 3),
                    makeStat("ties", 2),
                    makeStat("points", 17),
                    makeStat("pointsFor", 12),
                    makeStat("pointsAgainst", 9),
                    makeStat("pointDifferential", 3),
                    makeStat("playoffSeed", 1),
                  ],
                },
              ],
            },
          },
        ],
      }),
    } as Response);

    const adapter = new MlsStandingsAdapter();
    const records = await adapter.fetchStandings();
    expect(records[0].conference).toBe("Southern Division");
  });

  it("returns winPct of 0 when gamesPlayed is 0", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => ({
        children: [
          {
            name: "Eastern Conference",
            standings: {
              entries: [
                {
                  team: { id: "1", displayName: "Preseason FC" },
                  stats: [
                    makeStat("wins", 0),
                    makeStat("losses", 0),
                    makeStat("ties", 0),
                    makeStat("points", 0),
                    makeStat("pointsFor", 0),
                    makeStat("pointsAgainst", 0),
                    makeStat("pointDifferential", 0),
                    makeStat("playoffSeed", 1),
                  ],
                },
              ],
            },
          },
        ],
      }),
    } as Response);

    const adapter = new MlsStandingsAdapter();
    const records = await adapter.fetchStandings();
    expect(records[0].winPct).toBe(0);
    expect(records[0].gamesPlayed).toBe(0);
  });
});
