// Session 10 — prefetch's new api-football enrichment pass and the
// CLI flags (--source, --max-players). The existing soccerDataPrefetch
// test suite covers the legacy football-data path; this suite verifies:
//   - parseArgs handles the new flags
//   - shouldRunSource respects the source filter + defaults to 'all'
//   - enrichFromApiFootball walks finished fixtures, aggregates per-90
//     rates, and writes apifootball:player_by_name:{normalizedName}
//   - graceful skip when API_FOOTBALL_KEY is unset

const mockApifGetFixtures = jest.fn();
const mockApifGetFixturePlayerStats = jest.fn();
const mockApifHasApiKey = jest.fn(() => true);
jest.mock('../../src/services/adapters/apiFootballAdapter', () => ({
  getFixtures: (...a) => mockApifGetFixtures(...a),
  getFixturePlayerStats: (...a) => mockApifGetFixturePlayerStats(...a),
  hasApiKey: (...a) => mockApifHasApiKey(...a),
}));

const mockFootapiHasApiKey = jest.fn(() => false);
const mockFootapiGetRefereeStatistics = jest.fn();
jest.mock('../../src/services/adapters/footApiAdapter', () => ({
  hasApiKey: (...a) => mockFootapiHasApiKey(...a),
  getRefereeStatistics: (...a) => mockFootapiGetRefereeStatistics(...a),
}));

const mockFbdHasApiKey = jest.fn(() => true);
const mockFbdGetLeagueStandings = jest.fn(async () => []);
const mockFbdGetLeagueScorers = jest.fn(async () => []);
jest.mock('../../src/services/adapters/footballDataAdapter', () => ({
  hasApiKey: (...a) => mockFbdHasApiKey(...a),
  getLeagueStandings: (...a) => mockFbdGetLeagueStandings(...a),
  getLeagueScorers: (...a) => mockFbdGetLeagueScorers(...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(() => {
  mockApifGetFixtures.mockReset();
  mockApifGetFixturePlayerStats.mockReset();
  mockApifHasApiKey.mockReset().mockReturnValue(true);
  mockFbdHasApiKey.mockReset().mockReturnValue(true);
  mockFbdGetLeagueStandings.mockReset().mockResolvedValue([]);
  mockFbdGetLeagueScorers.mockReset().mockResolvedValue([]);
  mockFootapiHasApiKey.mockReset().mockReturnValue(false);
  mockFootapiGetRefereeStatistics.mockReset();
  mockCacheSets.clear();
});

describe('soccer-data-prefetch — Session 10 cascade enrichment', () => {
  describe('parseArgs', () => {
    test('parses --source flag with valid value', () => {
      const a = prefetch.__internals.parseArgs(['node', 'script', '--source=api-football']);
      expect(a.source).toBe('api-football');
    });
    test('invalid --source falls back to "all"', () => {
      const a = prefetch.__internals.parseArgs(['node', 'script', '--source=bogus']);
      expect(a.source).toBe('all');
    });
    test('parses --max-players', () => {
      const a = prefetch.__internals.parseArgs(['node', 'script', '--max-players=25']);
      expect(a.maxPlayers).toBe(25);
    });
    test('--max-players ignores non-numeric and zero/negative', () => {
      expect(prefetch.__internals.parseArgs(['node', 'script', '--max-players=foo']).maxPlayers).toBe(80);
      expect(prefetch.__internals.parseArgs(['node', 'script', '--max-players=0']).maxPlayers).toBe(80);
      expect(prefetch.__internals.parseArgs(['node', 'script', '--max-players=-5']).maxPlayers).toBe(80);
    });
    test('parses --season', () => {
      expect(prefetch.__internals.parseArgs(['node', 'script', '--season=2025']).season).toBe(2025);
    });
    test('defaults', () => {
      const a = prefetch.__internals.parseArgs(['node', 'script']);
      expect(a.source).toBe('all');
      expect(a.maxPlayers).toBe(80);
      expect(a.season).toBe(2026);
    });
  });

  describe('shouldRunSource', () => {
    const { shouldRunSource } = prefetch.__internals;
    test('"all" matches everything', () => {
      expect(shouldRunSource({ source: 'all' }, 'api-football')).toBe(true);
      expect(shouldRunSource({ source: 'all' }, 'football-data')).toBe(true);
      expect(shouldRunSource({ source: 'all' }, 'footapi')).toBe(true);
    });
    test('explicit source matches only itself', () => {
      expect(shouldRunSource({ source: 'api-football' }, 'api-football')).toBe(true);
      expect(shouldRunSource({ source: 'api-football' }, 'football-data')).toBe(false);
    });
    test('missing args.source defaults to "all" (backwards compat)', () => {
      expect(shouldRunSource({}, 'football-data')).toBe(true);
      expect(shouldRunSource(undefined, 'api-football')).toBe(true);
    });
  });

  describe('enrichFromApiFootball', () => {
    test('graceful skip when API_FOOTBALL_KEY is unset', async () => {
      mockApifHasApiKey.mockReturnValueOnce(false);
      const r = await prefetch.__internals.enrichFromApiFootball('WC', {
        season: 2026, maxPlayers: 80, dryRun: false,
      });
      expect(r.skipped).toBe('no_key');
      expect(r.players).toBe(0);
      expect(mockApifGetFixtures).not.toHaveBeenCalled();
    });

    test('skips an unmapped league code (no api-football league ID)', async () => {
      const r = await prefetch.__internals.enrichFromApiFootball('UNKNOWN', {
        season: 2026, maxPlayers: 80, dryRun: false,
      });
      expect(r.skipped).toBe('unmapped_league');
      expect(mockApifGetFixtures).not.toHaveBeenCalled();
    });

    test('skips when no fixtures returned', async () => {
      mockApifGetFixtures.mockResolvedValueOnce([]);
      const r = await prefetch.__internals.enrichFromApiFootball('WC', {
        season: 2026, maxPlayers: 80, dryRun: false,
      });
      expect(r.skipped).toBe('no_fixtures');
    });

    test('aggregates per-90 stats across finished fixtures, writes cascade keys', async () => {
      mockApifGetFixtures.mockResolvedValueOnce([
        { id: 9001, status: 'FT' },
        { id: 9002, status: 'NS' },  // not yet played — skip
        { id: 9003, status: 'FT' },
      ]);
      // Fixture 9001: Messi 90min, 1G, 2A, 5 shots.
      mockApifGetFixturePlayerStats.mockResolvedValueOnce([
        { name: 'Lionel Messi', team: 'Argentina', position: 'F', minutes: 90, goals: 1, assists: 2, shots_total: 5, shots_on: 3, substitute: false, rating: '8.4' },
      ]);
      // Fixture 9003: Messi 88min, 0G, 1A.
      mockApifGetFixturePlayerStats.mockResolvedValueOnce([
        { name: 'Lionel Messi', team: 'Argentina', position: 'F', minutes: 88, goals: 0, assists: 1, shots_total: 3, shots_on: 1, substitute: false, rating: '7.5' },
      ]);

      const r = await prefetch.__internals.enrichFromApiFootball('WC', {
        season: 2026, maxPlayers: 80, dryRun: false,
      });
      expect(r.players).toBe(1);

      const key = `apifootball:player_by_name:${normalizeName('Lionel Messi')}`;
      const entry = mockCacheSets.get(key);
      expect(entry).toBeDefined();
      expect(entry.value).toMatchObject({
        name: 'Lionel Messi', team: 'Argentina',
        appearances: 2,
        minutes: 178, // 90 + 88
        goals: 1, assists: 3,
      });
      // 1 goal over 178 minutes = 0.506 per 90 (3dp).
      expect(entry.value.goals_per_90).toBeCloseTo(0.506, 2);
      // 3 assists over 178 min = 1.517 per 90.
      expect(entry.value.assists_per_90).toBeCloseTo(1.517, 2);
      // Average rating across two appearances.
      expect(entry.value.avg_rating).toBeCloseTo(7.95, 1);
      expect(entry.ttl).toBe(prefetch.__internals.PLAYER_TTL_SEC);
    });

    test('honors maxPlayers cap (limits writes per run)', async () => {
      mockApifGetFixtures.mockResolvedValueOnce([{ id: 1, status: 'FT' }]);
      mockApifGetFixturePlayerStats.mockResolvedValueOnce([
        { name: 'A', team: 'X', minutes: 90, goals: 0, assists: 0 },
        { name: 'B', team: 'X', minutes: 90, goals: 0, assists: 0 },
        { name: 'C', team: 'X', minutes: 90, goals: 0, assists: 0 },
      ]);
      const r = await prefetch.__internals.enrichFromApiFootball('WC', {
        season: 2026, maxPlayers: 2, dryRun: false,
      });
      expect(r.players).toBe(2);
    });

    test('dry-run skips cacheSet but still reports a count', async () => {
      mockApifGetFixtures.mockResolvedValueOnce([{ id: 1, status: 'FT' }]);
      mockApifGetFixturePlayerStats.mockResolvedValueOnce([
        { name: 'A', team: 'X', minutes: 90, goals: 1, assists: 0 },
      ]);
      const r = await prefetch.__internals.enrichFromApiFootball('WC', {
        season: 2026, maxPlayers: 80, dryRun: true,
      });
      expect(r.players).toBe(1);
      expect(mockCacheSets.size).toBe(0);
    });
  });

  describe('enrichRefereesFromFootApi', () => {
    test('graceful skip when RAPID_API_KEY is unset', async () => {
      mockFootapiHasApiKey.mockReturnValueOnce(false);
      const r = await prefetch.__internals.enrichRefereesFromFootApi(
        [{ id: 1, name: 'X' }], { dryRun: false },
      );
      expect(r.skipped).toBe('no_key');
      expect(mockFootapiGetRefereeStatistics).not.toHaveBeenCalled();
    });

    test('writes footapi:referee_by_name:{name} keys when stats exist', async () => {
      mockFootapiHasApiKey.mockReturnValue(true);
      mockFootapiGetRefereeStatistics.mockResolvedValueOnce([
        { tournamentId: 16, appearances: 6, yellowCards: 24, redCards: 1, yellowCardsPerGame: 4 },
      ]);
      const r = await prefetch.__internals.enrichRefereesFromFootApi(
        [{ id: 99, name: 'Anthony Taylor' }], { dryRun: false },
      );
      expect(r.referees).toBe(1);
      const entry = mockCacheSets.get('footapi:referee_by_name:Anthony Taylor');
      expect(entry.value).toMatchObject({
        name: 'Anthony Taylor', cards_per_game: 4, appearances: 6,
      });
      expect(entry.ttl).toBe(prefetch.__internals.REFEREE_TTL_SEC);
    });

    test('handles missing IDs in the referee list', async () => {
      mockFootapiHasApiKey.mockReturnValue(true);
      const r = await prefetch.__internals.enrichRefereesFromFootApi(
        [{ id: null, name: 'X' }, { id: 1, name: null }], { dryRun: false },
      );
      expect(r.referees).toBe(0);
      expect(mockFootapiGetRefereeStatistics).not.toHaveBeenCalled();
    });
  });

  describe('main — graceful skip when no source keys configured', () => {
    test('logs skip + returns {skipped: true} when nothing is available', async () => {
      mockFbdHasApiKey.mockReturnValue(false);
      mockApifHasApiKey.mockReturnValue(false);
      mockFootapiHasApiKey.mockReturnValue(false);
      const r = await prefetch.main(['node', 'script']);
      expect(r.skipped).toBe(true);
    });

    test('proceeds when ANY source is available (api-football only)', async () => {
      mockFbdHasApiKey.mockReturnValue(false);
      mockApifHasApiKey.mockReturnValue(true);
      mockApifGetFixtures.mockResolvedValue([]);
      const r = await prefetch.main(['node', 'script', '--leagues=WC', '--dry-run']);
      // Not skipped — we have at least one configured source.
      expect(r.skipped).toBeUndefined();
    });
  });
});
