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

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

// WNBA standings response (13 active teams — flat conference structure, no divisions).
const SAMPLE_STANDINGS_RESPONSE = {
  children: [
    {
      name: "Eastern Conference",
      standings: {
        entries: [
          {
            team: { id: "5", displayName: "New York Liberty", abbreviation: "NY" },
            stats: [
              makeStat("wins", 28),
              makeStat("losses", 12),
              makeStat("winPercent", 0.7),
              makeStat("gamesBehind", 0),
              makeStat("playoffSeed", 1),
              { name: "streak", value: 3, displayValue: "W3" },
              { name: "Last Ten Games", value: 0, displayValue: "8-2" },
              { name: "Home", value: 0, displayValue: "15-5" },
              { name: "Road", value: 0, displayValue: "13-7" },
              makeStat("avgPointsFor", 89.5),
              makeStat("avgPointsAgainst", 82.1),
            ],
          },
          {
            team: { id: "3", displayName: "Connecticut Sun", abbreviation: "CONN" },
            stats: [
              makeStat("wins", 20),
              makeStat("losses", 20),
              makeStat("winPercent", 0.5),
              makeStat("gamesBehind", 8),
              makeStat("playoffSeed", 4),
              { name: "streak", value: 1, displayValue: "L1" },
              { name: "Last Ten Games", value: 0, displayValue: "5-5" },
              { name: "Home", value: 0, displayValue: "11-9" },
              { name: "Road", value: 0, displayValue: "9-11" },
              makeStat("avgPointsFor", 78.0),
              makeStat("avgPointsAgainst", 78.0),
            ],
          },
        ],
      },
    },
    {
      name: "Western Conference",
      standings: {
        entries: [
          {
            team: { id: "20", displayName: "Las Vegas Aces", abbreviation: "LV" },
            stats: [
              makeStat("wins", 26),
              makeStat("losses", 14),
              makeStat("winPercent", 0.65),
              makeStat("gamesBehind", 2),
              makeStat("playoffSeed", 1),
              { name: "streak", value: 2, displayValue: "W2" },
              { name: "Last Ten Games", value: 0, displayValue: "7-3" },
              { name: "Home", value: 0, displayValue: "14-6" },
              { name: "Road", value: 0, displayValue: "12-8" },
              makeStat("avgPointsFor", 91.2),
              makeStat("avgPointsAgainst", 85.0),
            ],
          },
        ],
      },
    },
  ],
};

// Teams endpoint response — includes 2 expansion teams not yet in standings.
const SAMPLE_TEAMS_RESPONSE = {
  sports: [
    {
      leagues: [
        {
          teams: [
            { team: { id: "5",      displayName: "New York Liberty",  isActive: true } },
            { team: { id: "3",      displayName: "Connecticut Sun",   isActive: true } },
            { team: { id: "20",     displayName: "Las Vegas Aces",    isActive: true } },
            { team: { id: "99001",  displayName: "Portland Fire",     isActive: true } },
            { team: { id: "99002",  displayName: "Toronto Tempo",     isActive: true } },
          ],
        },
      ],
    },
  ],
};

// Teams response with no expansion teams beyond what's in standings.
const TEAMS_RESPONSE_NO_EXTRAS = {
  sports: [{ leagues: [{ teams: [
    { team: { id: "5",  displayName: "New York Liberty", isActive: true } },
    { team: { id: "3",  displayName: "Connecticut Sun",  isActive: true } },
    { team: { id: "20", displayName: "Las Vegas Aces",   isActive: true } },
  ] }] }],
};

function mockFetch(standingsBody: unknown, teamsBody: unknown) {
  vi.mocked(fetch)
    .mockResolvedValueOnce({ ok: true, json: async () => standingsBody } as Response)
    .mockResolvedValueOnce({ ok: true, json: async () => teamsBody } as Response);
}

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

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

  it("returns standings teams + zero-record expansion teams", async () => {
    mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE);

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

    // 3 from standings + 2 expansion
    expect(records).toHaveLength(5);
  });

  it("expansion teams have wins=0, losses=0, gamesPlayed=0, srs=null", async () => {
    mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE);

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

    const portland = records.find((r) => r.teamName === "Portland Fire");
    if (!portland) throw new Error("Portland Fire not found");
    expect(portland.wins).toBe(0);
    expect(portland.losses).toBe(0);
    expect(portland.gamesPlayed).toBe(0);
    expect(portland.srs).toBeNull();

    const toronto = records.find((r) => r.teamName === "Toronto Tempo");
    if (!toronto) throw new Error("Toronto Tempo not found");
    expect(toronto.wins).toBe(0);
  });

  it("expansion teams rank below all active teams", async () => {
    mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE);

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

    const maxActiveRank = Math.max(
      ...records
        .filter((r) => r.wins > 0 || r.gamesPlayed > 0)
        .map((r) => r.leagueRank)
    );
    const portland = records.find((r) => r.teamName === "Portland Fire");
    const toronto  = records.find((r) => r.teamName === "Toronto Tempo");
    if (!portland) throw new Error("Portland Fire not found");
    if (!toronto) throw new Error("Toronto Tempo not found");
    expect(portland.leagueRank).toBeGreaterThan(maxActiveRank);
    expect(toronto.leagueRank).toBeGreaterThan(maxActiveRank);
  });

  it("parses wins, losses, and gamesPlayed correctly", async () => {
    mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE);

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

    const ny = records.find((r) => r.teamName === "New York Liberty");
    if (!ny) throw new Error("New York Liberty not found");
    expect(ny.wins).toBe(28);
    expect(ny.losses).toBe(12);
    expect(ny.gamesPlayed).toBe(40);
  });

  it("assigns leagueRank 1 to the team with most wins", async () => {
    mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE);

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

    const rank1 = records.find((r) => r.leagueRank === 1);
    expect(rank1?.teamName).toBe("New York Liberty");
  });

  it("assigns conference from the standings response", async () => {
    mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE);

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

    const ny = records.find((r) => r.teamName === "New York Liberty");
    expect(ny?.conference).toBe("Eastern Conference");

    const lv = records.find((r) => r.teamName === "Las Vegas Aces");
    expect(lv?.conference).toBe("Western Conference");
  });

  it("expansion teams have no division (WNBA has no divisions)", async () => {
    mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE);

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

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

  it("computes srs as net rating (avgPointsFor - avgPointsAgainst)", async () => {
    mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE);

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

    const ny = records.find((r) => r.teamName === "New York Liberty");
    expect(ny?.srs).toBeCloseTo(7.4, 1); // 89.5 - 82.1

    const conn = records.find((r) => r.teamName === "Connecticut Sun");
    expect(conn?.srs).toBeCloseTo(0, 1); // 78.0 - 78.0
  });

  it("does not duplicate teams that appear in both standings and teams list", async () => {
    mockFetch(SAMPLE_STANDINGS_RESPONSE, TEAMS_RESPONSE_NO_EXTRAS);

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

    expect(records).toHaveLength(3);
    const names = records.map((r) => r.teamName);
    expect(new Set(names).size).toBe(3);
  });

  it("extracts streak and lastTen from standings", async () => {
    mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE);

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

    const ny = records.find((r) => r.teamName === "New York Liberty");
    expect(ny?.streak).toBe("W3");
    expect(ny?.lastTen).toBe("8-2");
  });

  it("extracts home and away records", async () => {
    mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE);

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

    const ny = records.find((r) => r.teamName === "New York Liberty");
    expect(ny?.homeRecord).toBe("15-5");
    expect(ny?.awayRecord).toBe("13-7");
  });

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

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

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

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