// Soccer daily prefetch — tests the data transforms + Redis writes via
// the cache-write spy. The football-data adapter is mocked at the
// module boundary so no network is touched.

const mockGetLeagueStandings = jest.fn();
const mockGetLeagueScorers = jest.fn();
const mockHasApiKey = jest.fn(() => true);
jest.mock('../../src/services/adapters/footballDataAdapter', () => ({
  getLeagueStandings: (...a) => mockGetLeagueStandings(...a),
  getLeagueScorers: (...a) => mockGetLeagueScorers(...a),
  hasApiKey: (...a) => mockHasApiKey(...a),
}));

const mockCacheSets = new Map();
jest.mock('../../src/utils/redis', () => ({
  cacheGet: async () => null,
  cacheSet: async (k, v, ttl) => { mockCacheSets.set(k, { value: v, ttl }); return true; },
  cacheDel: async () => true,
  isDegraded: () => false,
}));

const { normalizeName } = require('../../src/utils/normalize');
const prefetch = require('../../scripts/soccer-data-prefetch');

beforeEach(() => {
  mockGetLeagueStandings.mockReset();
  mockGetLeagueScorers.mockReset();
  mockHasApiKey.mockReset().mockReturnValue(true);
  mockCacheSets.clear();
});

describe('soccer-data-prefetch', () => {
  describe('parseArgs', () => {
    test('default leagues=[WC]', () => {
      const a = prefetch.__internals.parseArgs(['node', 'script']);
      expect(a.leagues).toEqual(['WC']);
      expect(a.dryRun).toBe(false);
    });
    test('--leagues=WC,PL,PD parsed and uppercased', () => {
      const a = prefetch.__internals.parseArgs(['node', 'script', '--leagues=wc,pl,pd']);
      expect(a.leagues).toEqual(['WC', 'PL', 'PD']);
    });
    test('--dry-run flag', () => {
      const a = prefetch.__internals.parseArgs(['node', 'script', '--dry-run']);
      expect(a.dryRun).toBe(true);
    });
  });

  describe('aggregateTeamDefense', () => {
    const { aggregateTeamDefense } = prefetch.__internals;

    test('computes goals_conceded_per_game and rank from full table', () => {
      const allRows = [
        { teamName: 'Italy',   goalsAgainst: 3, playedGames: 10 }, // 0.30 — best
        { teamName: 'England', goalsAgainst: 6, playedGames: 10 }, // 0.60
        { teamName: 'France',  goalsAgainst: 9, playedGames: 10 }, // 0.90 — worst
      ];
      const italy   = aggregateTeamDefense(allRows[0], allRows);
      const england = aggregateTeamDefense(allRows[1], allRows);
      const france  = aggregateTeamDefense(allRows[2], allRows);

      expect(italy.goals_conceded_per_game).toBeCloseTo(0.3);
      expect(italy.defensive_rank).toBe(1);
      expect(italy.defensive_rank_norm).toBeCloseTo(0);

      expect(england.defensive_rank).toBe(2);
      expect(england.defensive_rank_norm).toBeCloseTo(0.5);

      expect(france.defensive_rank).toBe(3);
      expect(france.defensive_rank_norm).toBeCloseTo(1);
    });

    test('returns null when row has no played games', () => {
      const result = aggregateTeamDefense({ goalsAgainst: 0, playedGames: 0 }, []);
      expect(result).toBeNull();
    });

    test('clean_sheet_rate null when API does not provide it', () => {
      const allRows = [{ goalsAgainst: 2, playedGames: 4 }];
      const r = aggregateTeamDefense(allRows[0], allRows);
      expect(r.clean_sheet_rate).toBeNull();
    });
  });

  describe('aggregatePlayerFromScorer', () => {
    const { aggregatePlayerFromScorer } = prefetch.__internals;

    test('per-90 rates computed from minutes when present', () => {
      const r = aggregatePlayerFromScorer({
        name: 'Kane', team: 'England', goals: 3, assists: 1, playedMatches: 4, minutesPlayed: 360,
      });
      // 3 goals / (360/90) = 0.75 per 90.
      expect(r.goals_per_90).toBeCloseTo(0.75);
      expect(r.assists_per_90).toBeCloseTo(0.25);
      expect(r.minutes_per_game).toBe(90);
    });

    test('per-90 falls back to per-match when minutes are missing', () => {
      const r = aggregatePlayerFromScorer({
        name: 'X', team: 'Y', goals: 4, assists: 2, playedMatches: 4, minutesPlayed: null,
      });
      // No minutes data → use goals/played as a rough proxy.
      expect(r.goals_per_90).toBeCloseTo(1.0);
      expect(r.minutes_per_game).toBeNull();
    });

    test('xG fields are explicitly null on Day 1', () => {
      const r = aggregatePlayerFromScorer({ name: 'X', goals: 1, assists: 0, playedMatches: 2 });
      expect(r.xg_per_90).toBeNull();
      expect(r.xg_delta).toBeNull();
    });
  });

  describe('processLeague — Redis writes', () => {
    test('writes one teamdefense key per table row + one player key per scorer', async () => {
      mockGetLeagueStandings.mockResolvedValueOnce([
        {
          type: 'TOTAL',
          table: [
            { team: { name: 'Italy' }, goalsAgainst: 2, playedGames: 5 },
            { team: { name: 'France' }, goalsAgainst: 8, playedGames: 5 },
          ],
        },
      ]);
      mockGetLeagueScorers.mockResolvedValueOnce([
        { name: 'Harry Kane', team: 'England', goals: 5, assists: 1, playedMatches: 4, minutesPlayed: 360 },
        { name: 'Vinicius Junior', team: 'Brazil', goals: 3, assists: 2, playedMatches: 4, minutesPlayed: 340 },
      ]);

      const summary = await prefetch.__internals.processLeague('WC', { dryRun: false });

      expect(summary.standings).toBe(2);
      expect(summary.scorers).toBe(2);
      expect(summary.players).toBe(2);
      expect(summary.teamDefense).toBe(2);

      // Cache keys present at the right path.
      expect(mockCacheSets.has('soccer:teamdefense:wc:Italy')).toBe(true);
      expect(mockCacheSets.has('soccer:teamdefense:wc:France')).toBe(true);
      expect(mockCacheSets.has(`soccer:player:${normalizeName('Harry Kane')}`)).toBe(true);
      expect(mockCacheSets.has(`soccer:player:${normalizeName('Vinicius Junior')}`)).toBe(true);
      expect(mockCacheSets.has('soccer:wc:standings')).toBe(true);
      expect(mockCacheSets.has('soccer:wc:scorers')).toBe(true);

      // TTLs match the constants.
      const kaneEntry = mockCacheSets.get(`soccer:player:${normalizeName('Harry Kane')}`);
      expect(kaneEntry.ttl).toBe(prefetch.__internals.PLAYER_TTL_SEC);
      const italyEntry = mockCacheSets.get('soccer:teamdefense:wc:Italy');
      expect(italyEntry.ttl).toBe(prefetch.__internals.DEFENSE_TTL_SEC);
    });

    test('dry-run computes summary but writes nothing', async () => {
      mockGetLeagueStandings.mockResolvedValueOnce([
        { type: 'TOTAL', table: [{ team: { name: 'X' }, goalsAgainst: 1, playedGames: 1 }] },
      ]);
      mockGetLeagueScorers.mockResolvedValueOnce([
        { name: 'X', team: 'X', goals: 1, assists: 0, playedMatches: 1, minutesPlayed: 90 },
      ]);
      const summary = await prefetch.__internals.processLeague('WC', { dryRun: true });
      expect(summary.players).toBeGreaterThan(0);
      expect(mockCacheSets.size).toBe(0);
    });

    test('both API calls null → skipped flag', async () => {
      mockGetLeagueStandings.mockResolvedValueOnce(null);
      mockGetLeagueScorers.mockResolvedValueOnce(null);
      const summary = await prefetch.__internals.processLeague('WC', { dryRun: false });
      expect(summary.skipped).toBe(true);
      expect(mockCacheSets.size).toBe(0);
    });
  });

  describe('main — top-level entry', () => {
    test('graceful skip when API key missing', async () => {
      mockHasApiKey.mockReturnValueOnce(false);
      const result = await prefetch.main(['node', 'script', '--leagues=WC']);
      expect(result.skipped).toBe(true);
      // Critically: adapter methods never invoked.
      expect(mockGetLeagueStandings).not.toHaveBeenCalled();
    });

    test('processes each --leagues arg in turn', async () => {
      mockGetLeagueStandings.mockResolvedValue([]);
      mockGetLeagueScorers.mockResolvedValue([]);
      await prefetch.main(['node', 'script', '--leagues=WC,PL', '--dry-run']);
      expect(mockGetLeagueStandings).toHaveBeenCalledWith('WC');
      expect(mockGetLeagueStandings).toHaveBeenCalledWith('PL');
    });
  });
});
