// footApiAdapter — BACKUP soccer source via RapidAPI. Tests the
// RapidAPI auth header shape, the per-endpoint projection, and the
// graceful-degradation paths.

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/footApiAdapter');

beforeEach(async () => {
  mockAxiosGet.mockReset();
  mockCacheStore.clear();
});

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

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

    test('all endpoints return null without touching axios', async () => {
      expect(await adapter.getMatchLineups(123)).toBeNull();
      expect(await adapter.getMatchIncidents(123)).toBeNull();
      expect(await adapter.getRefereeStatistics(7)).toBeNull();
      expect(await adapter.getWorldCupSchedule(11, 6, 2026)).toBeNull();
      expect(mockAxiosGet).not.toHaveBeenCalled();
    });
  });

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

    test('auth headers are RapidAPI shape — NOT x-apisports-key', async () => {
      mockAxiosGet.mockResolvedValueOnce({ data: {} });
      await adapter.getMatchLineups(42);
      const [url, opts] = mockAxiosGet.mock.calls[0];
      expect(url).toMatch(/^https:\/\/footapi7\.p\.rapidapi\.com/);
      expect(opts.headers['x-rapidapi-key']).toBe('test-rapid-key');
      expect(opts.headers['x-rapidapi-host']).toBe('footapi7.p.rapidapi.com');
      // Primary adapter's header MUST NOT appear.
      expect(opts.headers['x-apisports-key']).toBeUndefined();
    });

    test('FOOTAPI_HOST override is honored', async () => {
      const originalHost = process.env.FOOTAPI_HOST;
      process.env.FOOTAPI_HOST = 'mirror.rapidapi.com';
      mockAxiosGet.mockResolvedValueOnce({ data: {} });
      await adapter.getMatchLineups(7);
      const [url, opts] = mockAxiosGet.mock.calls[0];
      expect(url).toMatch(/^https:\/\/mirror\.rapidapi\.com/);
      expect(opts.headers['x-rapidapi-host']).toBe('mirror.rapidapi.com');
      if (originalHost !== undefined) process.env.FOOTAPI_HOST = originalHost;
      else delete process.env.FOOTAPI_HOST;
    });

    test('getMatchLineups flattens home + away into one player list', async () => {
      mockAxiosGet.mockResolvedValueOnce({
        data: {
          home: {
            formation: '4-3-3',
            players: [
              {
                player: { id: 1, name: 'Saka' },
                position: 'F',
                shirtNumber: 7,
                statistics: { minutesPlayed: 88, rating: 8.1, goals: 1, goalAssist: 1, totalShots: 4, shotOnTarget: 2, totalPass: 35, accuratePass: 30, totalTackle: 2, yellowCards: 0, redCards: 0, keyPass: 3 },
              },
            ],
          },
          away: {
            formation: '4-2-3-1',
            players: [
              {
                player: { id: 2, name: 'Mbappe' },
                position: 'F',
                substitute: false,
                statistics: { minutesPlayed: 90, rating: 7.7, goals: 0, totalShots: 5, shotOnTarget: 1, totalPass: 22, accuratePass: 18, yellowCards: 1 },
              },
            ],
          },
        },
      });
      const lineups = await adapter.getMatchLineups(101);
      expect(lineups).toHaveLength(2);
      const saka = lineups.find((p) => p.name === 'Saka');
      expect(saka.side).toBe('home');
      expect(saka.goals).toBe(1);
      expect(saka.shotsOnTarget).toBe(2);
      expect(saka.assists).toBe(1);
      const mbappe = lineups.find((p) => p.name === 'Mbappe');
      expect(mbappe.side).toBe('away');
      expect(mbappe.yellow).toBe(1);
    });

    test('getMatchIncidents projects time + addedTime + player + type', async () => {
      mockAxiosGet.mockResolvedValueOnce({
        data: {
          incidents: [
            { incidentType: 'goal', time: 43, addedTime: 0, isHome: true, player: { name: 'A' }, assist1: { name: 'B' }, text: '1-0' },
            { incidentType: 'card', incidentClass: 'yellow', time: 90, addedTime: 4, player: { name: 'C' } },
          ],
        },
      });
      const events = await adapter.getMatchIncidents(101);
      expect(events).toHaveLength(2);
      expect(events[0]).toMatchObject({ type: 'goal', minute: 43, player: 'A', assist: 'B' });
      expect(events[1]).toMatchObject({ type: 'card', minute: 90, addedTime: 4 });
    });

    test('getRefereeStatistics computes per-game rates', async () => {
      mockAxiosGet.mockResolvedValueOnce({
        data: {
          statistics: [
            { tournament: { id: 16, name: 'World Cup' }, season: { year: 2022 }, appearances: 6, yellowCards: 24, redCards: 1 },
          ],
        },
      });
      const refs = await adapter.getRefereeStatistics(99);
      expect(refs).toHaveLength(1);
      // 24/6 = 4.00 cards/game; 1/6 = ~0.167 red/game.
      expect(refs[0].yellowCardsPerGame).toBeCloseTo(4.0);
      expect(refs[0].redCardsPerGame).toBeCloseTo(0.167, 2);
    });

    test('getRefereeStatistics handles zero appearances gracefully', async () => {
      mockAxiosGet.mockResolvedValueOnce({
        data: { statistics: [{ tournament: { id: 16 }, appearances: 0, yellowCards: 0, redCards: 0 }] },
      });
      const refs = await adapter.getRefereeStatistics(99);
      expect(refs[0].yellowCardsPerGame).toBeNull();
      expect(refs[0].redCardsPerGame).toBeNull();
    });

    test('getWorldCupSchedule maps events with venue + referee', async () => {
      mockAxiosGet.mockResolvedValueOnce({
        data: {
          events: [
            {
              id: 5, startTimestamp: 1749672000, status: { type: 'notstarted' },
              homeTeam: { id: 26, name: 'Mexico' }, awayTeam: { id: 6, name: 'USA' },
              homeScore: { current: 0 }, awayScore: { current: 0 },
              venue: { name: 'Estadio Azteca' }, referee: { name: 'Daniele Orsato' },
            },
          ],
        },
      });
      const matches = await adapter.getWorldCupSchedule(11, 6, 2026);
      expect(matches[0]).toMatchObject({
        id: 5, homeTeam: 'Mexico', awayTeam: 'USA', venue: 'Estadio Azteca', referee: 'Daniele Orsato',
      });
    });

    test('cache hit on repeat call (axios not re-invoked)', async () => {
      mockAxiosGet.mockResolvedValueOnce({ data: { home: { players: [] }, away: { players: [] } } });
      await adapter.getMatchLineups(101);
      await adapter.getMatchLineups(101);
      expect(mockAxiosGet).toHaveBeenCalledTimes(1);
    });

    test('null IDs bounce without touching axios', async () => {
      expect(await adapter.getMatchLineups(null)).toBeNull();
      expect(await adapter.getMatchIncidents(null)).toBeNull();
      expect(await adapter.getRefereeStatistics(null)).toBeNull();
      expect(await adapter.getWorldCupSchedule(null, 6, 2026)).toBeNull();
      expect(mockAxiosGet).not.toHaveBeenCalled();
    });
  });

  describe('daily rate-limit (50 budget, soft 45)', () => {
    beforeAll(() => { process.env.RAPID_API_KEY = 'test-rapid-key'; });
    beforeEach(async () => { await adapter.__internals.resetCounterForTests(); mockCacheStore.clear(); });

    test('bumps counter on successful network call', async () => {
      mockAxiosGet.mockResolvedValueOnce({ data: {} });
      await adapter.getMatchLineups(42);
      expect(await adapter.__internals.readDailyCount()).toBe(1);
    });

    test('at SOFT_LIMIT refuses network + serves stale', async () => {
      const { cacheSet } = require('../../src/utils/redis');
      await cacheSet(adapter.__internals.DAILY_COUNTER_KEY, adapter.__internals.SOFT_LIMIT);
      mockCacheStore.set('footapi:match:42:lineups:stale', { home: { players: [{ player: { id: 1, name: 'Stale' }, statistics: {} }] }, away: { players: [] } });

      const lineups = await adapter.getMatchLineups(42);
      expect(lineups[0].name).toBe('Stale');
      expect(mockAxiosGet).not.toHaveBeenCalled();
    });
  });
});
