import { describe, it, expect, vi, beforeEach } from "vitest";
import { MlbStandingsAdapter } from "../mlb";

const SAMPLE_MLB_RESPONSE = {
  records: [
    {
      // AL East division record
      teamRecords: [
        {
          team: {
            id: 110,
            name: "Baltimore Orioles",
            league: { name: "American League" },
            division: { name: "AL East" },
          },
          wins: 47,
          losses: 34,
          gamesPlayed: 81,
          winningPercentage: ".580",
          divisionRank: "1",
          leagueRank: "1",
          gamesBack: "-",
          streak: { streakCode: "W", streakNumber: 3 },
          records: {
            splitRecords: [
              { type: "home", wins: 24, losses: 15 },
              { type: "away", wins: 23, losses: 19 },
              { type: "lastTen", wins: 7, losses: 3 },
            ],
          },
        },
        {
          team: {
            id: 111,
            name: "Boston Red Sox",
            league: { name: "American League" },
            division: { name: "AL East" },
          },
          wins: 42,
          losses: 39,
          gamesPlayed: 81,
          winningPercentage: ".519",
          divisionRank: "2",
          leagueRank: "4",
          gamesBack: "5.0",
          streak: { streakCode: "L", streakNumber: 2 },
          records: {
            splitRecords: [
              { type: "home", wins: 22, losses: 18 },
              { type: "away", wins: 20, losses: 21 },
              { type: "lastTen", wins: 4, losses: 6 },
            ],
          },
        },
      ],
    },
    {
      // NL East division record
      teamRecords: [
        {
          team: {
            id: 121,
            name: "New York Mets",
            league: { name: "National League" },
            division: { name: "NL East" },
          },
          wins: 43,
          losses: 38,
          gamesPlayed: 81,
          winningPercentage: ".531",
          divisionRank: "1",
          leagueRank: "1",
          gamesBack: "-",
          streak: { streakCode: "W", streakNumber: 1 },
          records: {
            splitRecords: [
              { type: "home", wins: 22, losses: 17 },
              { type: "away", wins: 21, losses: 21 },
              { type: "lastTen", wins: 6, losses: 4 },
            ],
          },
        },
      ],
    },
  ],
};

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

  it("maps AL teams to conference 'AL'", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLB_RESPONSE,
    } as Response);

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

    const bos = records.find((r) => r.teamName === "Baltimore Orioles");
    expect(bos?.conference).toBe("AL");
  });

  it("maps NL teams to conference 'NL'", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLB_RESPONSE,
    } as Response);

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

    const mets = records.find((r) => r.teamName === "New York Mets");
    expect(mets?.conference).toBe("NL");
  });

  it("maps division names correctly", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLB_RESPONSE,
    } as Response);

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

    const bos = records.find((r) => r.teamName === "Baltimore Orioles");
    const mets = records.find((r) => r.teamName === "New York Mets");
    expect(bos?.division).toBe("AL East");
    expect(mets?.division).toBe("NL East");
  });

  it("maps wins, losses, gamesPlayed, and winPct", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLB_RESPONSE,
    } as Response);

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

    const ori = records.find((r) => r.teamName === "Baltimore Orioles");
    expect(ori?.wins).toBe(47);
    expect(ori?.losses).toBe(34);
    expect(ori?.gamesPlayed).toBe(81);
    expect(ori?.winPct).toBeCloseTo(0.58, 2);
  });

  it("formats streak as 'W3' or 'L2'", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLB_RESPONSE,
    } as Response);

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

    const ori = records.find((r) => r.teamName === "Baltimore Orioles");
    const bos = records.find((r) => r.teamName === "Boston Red Sox");
    expect(ori?.streak).toBe("W3");
    expect(bos?.streak).toBe("L2");
  });

  it("extracts lastTen from splitRecords", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLB_RESPONSE,
    } as Response);

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

    const ori = records.find((r) => r.teamName === "Baltimore Orioles");
    expect(ori?.lastTen).toBe("7-3");
  });

  it("extracts homeRecord and awayRecord from splitRecords", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLB_RESPONSE,
    } as Response);

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

    const ori = records.find((r) => r.teamName === "Baltimore Orioles");
    expect(ori?.homeRecord).toBe("24-15");
    expect(ori?.awayRecord).toBe("23-19");
  });

  it("parses gamesBack as null for the division leader ('-')", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLB_RESPONSE,
    } as Response);

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

    const ori = records.find((r) => r.teamName === "Baltimore Orioles");
    expect(ori?.gamesBack).toBeUndefined();
  });

  it("parses gamesBack as a number for non-leaders", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLB_RESPONSE,
    } as Response);

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

    const bos = records.find((r) => r.teamName === "Boston Red Sox");
    expect(bos?.gamesBack).toBeCloseTo(5.0, 1);
  });

  it("computes leagueRank sorted by wins desc across all 30 teams", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLB_RESPONSE,
    } as Response);

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

    // Baltimore (47W) should rank #1 overall, Boston (42W) should rank #2
    const ori = records.find((r) => r.teamName === "Baltimore Orioles");
    const bos = records.find((r) => r.teamName === "Boston Red Sox");
    expect(ori?.leagueRank).toBe(1);
    expect(bos?.leagueRank).toBeGreaterThan(1);
  });

  it("never sets otLosses (MLB has no OT losses)", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => SAMPLE_MLB_RESPONSE,
    } as Response);

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

    for (const record of records) {
      expect(record.otLosses).toBeUndefined();
    }
  });

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

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

    const ori = records.find((r) => r.teamName === "Baltimore Orioles");
    expect(ori?.externalTeamId).toBe("110");
  });

  it("throws on non-ok HTTP response", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: false,
      status: 503,
      statusText: "Service Unavailable",
    } as Response);

    const adapter = new MlbStandingsAdapter();
    await expect(adapter.fetchStandings()).rejects.toThrow("503");
  });

  it("throws on unexpected response shape", async () => {
    vi.mocked(fetch).mockResolvedValueOnce({
      ok: true,
      json: async () => ({ unexpected: true }),
    } as Response);

    const adapter = new MlbStandingsAdapter();
    await expect(adapter.fetchStandings()).rejects.toThrow();
  });
});
