// Soccer poller — tests the tick() function (the unit of work). Run
// loop intentionally not exercised: it's a sleep+repeat shape that
// would only test setTimeout.

const mockAxiosGet = jest.fn();
jest.mock('axios', () => ({ get: (...args) => mockAxiosGet(...args) }));

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,
}));

// Stub the football-data adapter so soccer poller's "non-WC league"
// branch is exercised without hitting the real API.
const mockFbdGetLeagueFixtures = jest.fn();
const mockFbdGetWorldCupFixtures = jest.fn();
jest.mock('../../src/services/adapters/footballDataAdapter', () => ({
  getLeagueFixtures: (...a) => mockFbdGetLeagueFixtures(...a),
  getWorldCupFixtures: (...a) => mockFbdGetWorldCupFixtures(...a),
  hasApiKey: () => false,
}));

const soccerPoller = require('../../poller/soccer');

beforeEach(() => {
  mockAxiosGet.mockReset();
  mockFbdGetLeagueFixtures.mockReset();
  mockFbdGetWorldCupFixtures.mockReset();
  mockCacheSets.clear();
});

describe('soccer poller', () => {
  describe('parseLeagues', () => {
    test('defaults to WC when env var unset', () => {
      const original = process.env.SOCCER_LEAGUES;
      delete process.env.SOCCER_LEAGUES;
      expect(soccerPoller.__internals.parseLeagues()).toEqual(['WC']);
      if (original !== undefined) process.env.SOCCER_LEAGUES = original;
    });

    test('parses comma-separated list, uppercases, trims', () => {
      const original = process.env.SOCCER_LEAGUES;
      process.env.SOCCER_LEAGUES = 'wc, pl ,PD,bl1';
      expect(soccerPoller.__internals.parseLeagues()).toEqual(['WC', 'PL', 'PD', 'BL1']);
      if (original !== undefined) process.env.SOCCER_LEAGUES = original;
      else delete process.env.SOCCER_LEAGUES;
    });
  });

  describe('classifyStatus', () => {
    const { classifyStatus } = soccerPoller.__internals;
    test.each([
      ['IN_PLAY',   'live'],
      ['PAUSED',    'live'],
      ['LIVE',      'live'],
      ['FINISHED',  'finished'],
      ['FINAL',     'finished'],
      ['COMPLETED', 'finished'],
      ['SCHEDULED', 'scheduled'],
      ['TIMED',     'scheduled'],
      ['',          'scheduled'],
      [null,        'scheduled'],
    ])('classifies %s → %s', (input, expected) => {
      expect(classifyStatus(input)).toBe(expected);
    });
  });

  describe('fetchWorldCupFixtures via OSS API', () => {
    test('projects the OSS API response to the unified shape', async () => {
      mockAxiosGet.mockResolvedValueOnce({
        data: [
          {
            id: 1, home_team: 'England', away_team: 'Brazil',
            utc_date: '2026-06-15T20:00:00Z', status: 'SCHEDULED',
            matchday: 1, venue: 'MetLife Stadium',
          },
        ],
      });
      const fixtures = await soccerPoller.__internals.fetchWorldCupFixtures();
      expect(Array.isArray(fixtures)).toBe(true);
      expect(fixtures[0]).toMatchObject({
        id: 1, homeTeam: 'England', awayTeam: 'Brazil',
        venue: 'MetLife Stadium', competition: 'WC',
      });
    });

    test('axios throw → returns null (graceful)', async () => {
      mockAxiosGet.mockRejectedValueOnce(new Error('OSS down'));
      const fixtures = await soccerPoller.__internals.fetchWorldCupFixtures();
      expect(fixtures).toBeNull();
    });

    test('handles both top-level array and {matches: [...]} envelopes', async () => {
      mockAxiosGet.mockResolvedValueOnce({ data: { matches: [{ id: 9, homeTeam: 'X', awayTeam: 'Y' }] } });
      const fixtures = await soccerPoller.__internals.fetchWorldCupFixtures();
      expect(fixtures).toHaveLength(1);
      expect(fixtures[0].id).toBe(9);
    });
  });

  describe('fetchLeagueFixtures dispatch', () => {
    test('WC prefers OSS API, falls back to football-data when OSS dies', async () => {
      mockAxiosGet.mockRejectedValueOnce(new Error('OSS unreachable'));
      mockFbdGetWorldCupFixtures.mockResolvedValueOnce([{ id: 1, homeTeam: 'A', awayTeam: 'B', competition: 'WC' }]);
      const fixtures = await soccerPoller.__internals.fetchLeagueFixtures('WC');
      expect(fixtures).toHaveLength(1);
      expect(mockFbdGetWorldCupFixtures).toHaveBeenCalledTimes(1);
    });

    test('non-WC leagues use the football-data adapter', async () => {
      mockFbdGetLeagueFixtures.mockResolvedValueOnce([{ id: 7, homeTeam: 'X', awayTeam: 'Y', competition: 'PL' }]);
      const fixtures = await soccerPoller.__internals.fetchLeagueFixtures('PL');
      expect(fixtures).toHaveLength(1);
      expect(mockFbdGetLeagueFixtures).toHaveBeenCalledWith('PL');
    });
  });

  describe('indexFixturesForLeague', () => {
    test('writes per-team nextmatch + lastfixture keys', async () => {
      const inFuture = new Date(Date.now() + 5 * 86_400_000).toISOString();
      const inPast   = new Date(Date.now() - 2 * 86_400_000).toISOString();
      const counts = await soccerPoller.__internals.indexFixturesForLeague('WC', [
        { homeTeam: 'England', awayTeam: 'Brazil', utcDate: inFuture, status: 'SCHEDULED', venue: 'MetLife Stadium' },
        { homeTeam: 'USA',     awayTeam: 'Mexico', utcDate: inPast,   status: 'FINISHED',  venue: 'AT&T Stadium', score: { fullTime: { home: 2, away: 1 } } },
      ]);
      expect(counts.scheduled).toBe(1);
      expect(counts.finished).toBe(1);

      // Future fixture → next match for both teams.
      expect(mockCacheSets.has('soccer:nextmatch:England')).toBe(true);
      expect(mockCacheSets.has('soccer:nextmatch:Brazil')).toBe(true);
      expect(mockCacheSets.get('soccer:nextmatch:England').value).toMatchObject({
        opponent: 'Brazil', isHome: true, venue: 'MetLife Stadium',
      });
      expect(mockCacheSets.get('soccer:nextmatch:Brazil').value).toMatchObject({
        opponent: 'England', isHome: false,
      });

      // Past finished → last fixture for both teams.
      expect(mockCacheSets.has('soccer:lastfixture:USA')).toBe(true);
      expect(mockCacheSets.has('soccer:lastfixture:Mexico')).toBe(true);
    });

    test('returns zero counts on empty input', async () => {
      const counts = await soccerPoller.__internals.indexFixturesForLeague('WC', []);
      expect(counts).toEqual({ scheduled: 0, live: 0, finished: 0 });
      expect(mockCacheSets.size).toBe(0);
    });

    test('returns zero counts on null input (graceful)', async () => {
      const counts = await soccerPoller.__internals.indexFixturesForLeague('WC', null);
      expect(counts).toEqual({ scheduled: 0, live: 0, finished: 0 });
    });
  });

  describe('tick', () => {
    test('tick polls each configured league and reports live status', async () => {
      const original = process.env.SOCCER_LEAGUES;
      process.env.SOCCER_LEAGUES = 'WC,PL';
      const inFuture = new Date(Date.now() + 1 * 86_400_000).toISOString();

      // WC: OSS returns one live match.
      mockAxiosGet.mockResolvedValueOnce({
        data: [{ id: 1, home_team: 'A', away_team: 'B', utc_date: inFuture, status: 'IN_PLAY' }],
      });
      // PL: football-data adapter returns one scheduled.
      mockFbdGetLeagueFixtures.mockResolvedValueOnce([
        { id: 2, homeTeam: 'X', awayTeam: 'Y', utcDate: inFuture, status: 'SCHEDULED' },
      ]);

      const result = await soccerPoller.tick();
      expect(result.liveSeen).toBe(true);
      expect(result.summary.some((s) => s.startsWith('WC:'))).toBe(true);
      expect(result.summary.some((s) => s.startsWith('PL:'))).toBe(true);

      if (original !== undefined) process.env.SOCCER_LEAGUES = original;
      else delete process.env.SOCCER_LEAGUES;
    });

    test('tick survives a league with no_data', async () => {
      const original = process.env.SOCCER_LEAGUES;
      process.env.SOCCER_LEAGUES = 'WC';
      mockAxiosGet.mockRejectedValueOnce(new Error('OSS down'));
      mockFbdGetWorldCupFixtures.mockResolvedValueOnce(null);

      const result = await soccerPoller.tick();
      expect(result.summary[0]).toMatch(/WC: no_data/);

      if (original !== undefined) process.env.SOCCER_LEAGUES = original;
      else delete process.env.SOCCER_LEAGUES;
    });
  });
});
