// apiFootballAdapter — PRIMARY soccer source. Tests the auth header
// shape (x-apisports-key, NOT RapidAPI), the rate-limit bookkeeping,
// the graceful-degradation paths, and the per-endpoint projection.

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

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

const adapter = require('../../src/services/adapters/apiFootballAdapter');

beforeEach(async () => {
  mockAxiosGet.mockReset();
  mockCacheStore.clear();
  await adapter.__internals.resetCounterForTests();
  // Re-clear so the counter set above doesn't persist into the next test.
  mockCacheStore.clear();
});

describe('apiFootballAdapter', () => {
  describe('graceful degradation when API_FOOTBALL_KEY missing', () => {
    const original = process.env.API_FOOTBALL_KEY;
    beforeAll(() => { delete process.env.API_FOOTBALL_KEY; });
    afterAll(() => { if (original !== undefined) process.env.API_FOOTBALL_KEY = original; });

    test('hasApiKey reports false', () => {
      expect(adapter.hasApiKey()).toBe(false);
    });

    test('all endpoints return null without touching axios', async () => {
      expect(await adapter.getFixtures({ league: 1, season: 2026 })).toBeNull();
      expect(await adapter.getFixtureLineups(42)).toBeNull();
      expect(await adapter.getFixturePlayerStats(42)).toBeNull();
      expect(await adapter.getFixtureEvents(42)).toBeNull();
      expect(await adapter.getPlayerSeasonStats(100, 2026)).toBeNull();
      expect(await adapter.getStandings(1, 2026)).toBeNull();
      expect(mockAxiosGet).not.toHaveBeenCalled();
    });
  });

  describe('with key configured', () => {
    beforeAll(() => { process.env.API_FOOTBALL_KEY = 'test-apisports-key'; });

    test('auth header is x-apisports-key — NOT RapidAPI', async () => {
      mockAxiosGet.mockResolvedValueOnce({ data: { response: [] } });
      await adapter.getFixtures({ league: 1, season: 2026 });
      const [, opts] = mockAxiosGet.mock.calls[0];
      expect(opts.headers['x-apisports-key']).toBe('test-apisports-key');
      // RapidAPI headers must NOT be present.
      expect(opts.headers['x-rapidapi-key']).toBeUndefined();
      expect(opts.headers['x-rapidapi-host']).toBeUndefined();
    });

    test('getFixtures projects to the unified shape', async () => {
      mockAxiosGet.mockResolvedValueOnce({
        data: {
          response: [
            {
              fixture: { id: 9001, date: '2026-06-11T20:00:00+00:00', status: { short: 'NS' }, venue: { name: 'Estadio Azteca' }, referee: 'Daniele Orsato' },
              league: { name: 'World Cup', season: 2026, round: 'Group Stage - 1' },
              teams: { home: { id: 26, name: 'Mexico' }, away: { id: 6, name: 'USA' } },
              score: { fulltime: { home: null, away: null } },
            },
          ],
        },
      });
      const fixtures = await adapter.getFixtures({ league: 1, season: 2026, date: '2026-06-11' });
      expect(fixtures).toHaveLength(1);
      expect(fixtures[0]).toMatchObject({
        id: 9001,
        homeTeam: 'Mexico',
        awayTeam: 'USA',
        venue: 'Estadio Azteca',
        referee: 'Daniele Orsato',
        league: 'World Cup',
      });
    });

    test('getFixtures with no date works (whole season)', async () => {
      mockAxiosGet.mockResolvedValueOnce({ data: { response: [{ fixture: { id: 1 }, teams: { home: { name: 'A' }, away: { name: 'B' } } }] } });
      const fixtures = await adapter.getFixtures({ league: 1, season: 2026 });
      expect(fixtures).toHaveLength(1);
      const [url] = mockAxiosGet.mock.calls[0];
      expect(url).not.toMatch(/date=/);
    });

    test('null params bounce without touching axios', async () => {
      expect(await adapter.getFixtures({})).toBeNull();
      expect(await adapter.getFixtureLineups(null)).toBeNull();
      expect(await adapter.getPlayerSeasonStats(null, 2026)).toBeNull();
      expect(await adapter.getStandings(1, null)).toBeNull();
      expect(mockAxiosGet).not.toHaveBeenCalled();
    });

    test('getFixturePlayerStats flattens per-team rosters into one list', async () => {
      mockAxiosGet.mockResolvedValueOnce({
        data: {
          response: [
            {
              team: { name: 'Argentina' },
              players: [
                {
                  player: { id: 1, name: 'Messi' },
                  statistics: [{
                    games: { minutes: 88, position: 'F', rating: '8.4', substitute: false },
                    goals: { total: 1, assists: 1, saves: null },
                    shots: { total: 5, on: 3 },
                    passes: { total: 47, accuracy: 89 },
                    tackles: { total: 1, blocks: 0, interceptions: 2 },
                    cards: { yellow: 0, red: 0 },
                  }],
                },
              ],
            },
            {
              team: { name: 'France' },
              players: [
                {
                  player: { id: 2, name: 'Mbappe' },
                  statistics: [{
                    games: { minutes: 90, position: 'F', rating: '7.9', substitute: false },
                    goals: { total: 2, assists: 0, saves: null },
                    shots: { total: 7, on: 4 },
                    passes: { total: 28, accuracy: 71 },
                    tackles: { total: 0 },
                    cards: { yellow: 1, red: 0 },
                  }],
                },
              ],
            },
          ],
        },
      });
      const stats = await adapter.getFixturePlayerStats(9001);
      expect(stats).toHaveLength(2);
      const messi = stats.find((p) => p.name === 'Messi');
      expect(messi.team).toBe('Argentina');
      expect(messi.goals).toBe(1);
      expect(messi.shots_on).toBe(3);
      const mbappe = stats.find((p) => p.name === 'Mbappe');
      expect(mbappe.team).toBe('France');
      expect(mbappe.goals).toBe(2);
      expect(mbappe.yellow).toBe(1);
    });

    test('getFixtureLineups projects formation + startXI + bench', async () => {
      mockAxiosGet.mockResolvedValueOnce({
        data: {
          response: [
            {
              team: { id: 26, name: 'Mexico' },
              coach: { name: 'Some Coach' },
              formation: '4-3-3',
              startXI: [
                { player: { id: 10, name: 'GK', number: 1, pos: 'G', grid: '1:1' } },
              ],
              substitutes: [{ player: { id: 11, name: 'Sub', number: 22, pos: 'M' } }],
            },
          ],
        },
      });
      const lineups = await adapter.getFixtureLineups(9001);
      expect(lineups[0].formation).toBe('4-3-3');
      expect(lineups[0].startXI[0].name).toBe('GK');
      expect(lineups[0].substitutes[0].name).toBe('Sub');
    });

    test('getStandings flattens group standings into a flat list', async () => {
      mockAxiosGet.mockResolvedValueOnce({
        data: {
          response: [{
            league: {
              standings: [
                [ // Group A
                  { rank: 1, team: { id: 26, name: 'Mexico' }, all: { played: 3, win: 2, draw: 1, lose: 0, goals: { for: 5, against: 2 } }, points: 7, group: 'Group A' },
                ],
                [ // Group B
                  { rank: 1, team: { id: 5, name: 'Argentina' }, all: { played: 3, win: 3, draw: 0, lose: 0, goals: { for: 6, against: 1 } }, points: 9, group: 'Group B' },
                ],
              ],
            },
          }],
        },
      });
      const standings = await adapter.getStandings(1, 2026);
      expect(standings).toHaveLength(2);
      expect(standings.find((s) => s.team === 'Mexico')?.points).toBe(7);
      expect(standings.find((s) => s.team === 'Argentina')?.points).toBe(9);
    });

    test('cache hit on repeat call (axios not re-invoked)', async () => {
      mockAxiosGet.mockResolvedValueOnce({ data: { response: [{ fixture: { id: 1 }, teams: { home: { name: 'A' }, away: { name: 'B' } } }] } });
      await adapter.getFixtures({ league: 1, season: 2026, date: '2026-06-11' });
      await adapter.getFixtures({ league: 1, season: 2026, date: '2026-06-11' });
      expect(mockAxiosGet).toHaveBeenCalledTimes(1);
    });

    test('axios throw → null + stale-while-revalidate', async () => {
      mockCacheStore.set('apifootball:fixtures:1:2026:2026-06-11:stale', { response: [{ fixture: { id: 999 }, teams: { home: { name: 'X' }, away: { name: 'Y' } } }] });
      mockAxiosGet.mockRejectedValueOnce(new Error('upstream 500'));
      const fixtures = await adapter.getFixtures({ league: 1, season: 2026, date: '2026-06-11' });
      expect(fixtures[0].id).toBe(999);
    });

    test('axios throw with no stale → returns null', async () => {
      mockAxiosGet.mockRejectedValueOnce(new Error('network down'));
      const fixtures = await adapter.getFixtures({ league: 1, season: 2026, date: '2026-06-11' });
      expect(fixtures).toBeNull();
    });
  });

  describe('daily rate-limit accounting', () => {
    beforeAll(() => { process.env.API_FOOTBALL_KEY = 'test-apisports-key'; });

    test('bumps the counter on a successful network call', async () => {
      mockAxiosGet.mockResolvedValueOnce({ data: { response: [] } });
      await adapter.getFixtures({ league: 1, season: 2026 });
      const count = await adapter.__internals.readDailyCount();
      expect(count).toBe(1);
    });

    test('does NOT bump on cache hit', async () => {
      mockAxiosGet.mockResolvedValueOnce({ data: { response: [{ fixture: { id: 1 }, teams: { home: { name: 'A' }, away: { name: 'B' } } }] } });
      await adapter.getFixtures({ league: 1, season: 2026 });
      await adapter.getFixtures({ league: 1, season: 2026 }); // cached
      const count = await adapter.__internals.readDailyCount();
      expect(count).toBe(1);
    });

    test('at SOFT_LIMIT, refuses network + serves stale if present', async () => {
      // Prime the counter to SOFT_LIMIT (90).
      const { cacheSet } = require('../../src/utils/redis');
      await cacheSet(adapter.__internals.DAILY_COUNTER_KEY, adapter.__internals.SOFT_LIMIT);
      mockCacheStore.set('apifootball:fixtures:1:2026:stale', { response: [{ fixture: { id: 7 }, teams: { home: { name: 'X' }, away: { name: 'Y' } } }] });

      const fixtures = await adapter.getFixtures({ league: 1, season: 2026 });
      expect(fixtures[0].id).toBe(7);
      // Network NOT called — the bucket stopped us.
      expect(mockAxiosGet).not.toHaveBeenCalled();
    });

    test('at SOFT_LIMIT with no stale → null', async () => {
      const { cacheSet } = require('../../src/utils/redis');
      await cacheSet(adapter.__internals.DAILY_COUNTER_KEY, adapter.__internals.SOFT_LIMIT);
      const fixtures = await adapter.getFixtures({ league: 1, season: 2026 });
      expect(fixtures).toBeNull();
      expect(mockAxiosGet).not.toHaveBeenCalled();
    });
  });
});
