// Mock Redis cache — populate per-test to simulate prefetched data.
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 { normalizeName } = require('../../src/utils/normalize');
const extractor = require('../../src/services/intelligence/soccerFeatureExtractor');

beforeEach(() => {
  mockCacheStore.clear();
});

function primePlayer(name, profile) {
  mockCacheStore.set(`soccer:player:${normalizeName(name)}`, profile);
}

describe('soccerFeatureExtractor', () => {
  describe('isSoccerSport', () => {
    test('accepts soccer + football, rejects nba/mlb/random', () => {
      expect(extractor.isSoccerSport('soccer')).toBe(true);
      expect(extractor.isSoccerSport('SOCCER')).toBe(true);
      expect(extractor.isSoccerSport('football')).toBe(true);
      expect(extractor.isSoccerSport('nba')).toBe(false);
      expect(extractor.isSoccerSport('mlb')).toBe(false);
      expect(extractor.isSoccerSport(null)).toBe(false);
      expect(extractor.isSoccerSport(undefined)).toBe(false);
    });
  });

  describe('cache-miss path (everything null)', () => {
    test('returns engine1-shaped result with errors flagged, no throw', async () => {
      const result = await extractor.extractSoccerFeatures({
        player: 'Unknown Player', stat_type: 'goals', line: 0.5, direction: 'over',
      });
      expect(result).toHaveProperty('features');
      expect(result).toHaveProperty('trap');
      expect(result).toHaveProperty('consistency');
      expect(result).toHaveProperty('prop');
      expect(result).toHaveProperty('meta');
      // Critical: numeric features default to null (NOT 0 — 0 would
      // confuse engine1 into thinking we have a real signal).
      expect(result.features.goals_per_90).toBeNull();
      expect(result.features.xg_delta).toBeNull();
      expect(result.features.minutes_per_game).toBeNull();
      // Errors surface the misses.
      expect(result.meta.errors).toContain('player_not_found_in_cache');
      expect(result.meta.errors).toContain('team_not_resolved');
    });

    test('does not throw on null player input — surfaces error', async () => {
      const result = await extractor.extractSoccerFeatures({
        stat_type: 'goals', line: 0.5,
      });
      expect(result.meta.errors).toEqual(
        expect.arrayContaining([expect.stringMatching(/missing required fields/)])
      );
    });
  });

  describe('player profile resolution', () => {
    test('reads cached profile by normalized name', async () => {
      primePlayer('Harry Kane', {
        team: 'England', goals_per_90: 0.85, assists_per_90: 0.15,
        minutes_per_game: 84, start_rate: 0.95, season_per_90: 0.78,
        recent_form_per_90: 1.05,
      });

      const result = await extractor.extractSoccerFeatures({
        player: 'Harry Kane', stat_type: 'goals', line: 0.5, direction: 'over',
      });
      expect(result.features.goals_per_90).toBe(0.85);
      expect(result.features.minutes_per_game).toBe(84);
      expect(result.features.start_rate).toBe(0.95);
      // engine1-canonical mapping: l5_avg from recent_form_per_90,
      // l20_avg from season_per_90.
      expect(result.features.l5_avg).toBe(1.05);
      expect(result.features.l20_avg).toBe(0.78);
      expect(result.meta.teamAbbr).toBe('England');
      expect(result.meta.errors).not.toContain('player_not_found_in_cache');
    });
  });

  describe('xG regression risk', () => {
    test('fires when actual goals significantly outpace expected', async () => {
      primePlayer('Striker A', {
        team: 'France', goals_per_90: 1.2, xg_per_90: 0.7, xg_delta: 0.71,
      });
      const r = await extractor.extractSoccerFeatures({
        player: 'Striker A', stat_type: 'goals', line: 1.5, direction: 'over',
      });
      expect(r.features.xg_regression_risk).toBe(true);
      expect(r.features.xg_delta).toBeCloseTo(0.71);
    });

    test('does NOT fire when xG and goals track each other', async () => {
      primePlayer('Striker B', { team: 'France', goals_per_90: 0.8, xg_per_90: 0.78, xg_delta: 0.025 });
      const r = await extractor.extractSoccerFeatures({
        player: 'Striker B', stat_type: 'goals', line: 0.5, direction: 'over',
      });
      expect(r.features.xg_regression_risk).toBe(false);
    });

    test('does NOT fire when xG data is missing', async () => {
      primePlayer('Striker C', { team: 'France' });
      const r = await extractor.extractSoccerFeatures({
        player: 'Striker C', stat_type: 'goals', line: 0.5, direction: 'over',
      });
      expect(r.features.xg_regression_risk).toBe(false);
    });
  });

  describe('venue + altitude + home-continent overlay', () => {
    test('Estadio Azteca venue surfaces high altitude impact', async () => {
      primePlayer('Player X', { team: 'England', goals_per_90: 0.5 });
      mockCacheStore.set('soccer:nextmatch:England', {
        opponent: 'USA', venue: 'Estadio Azteca', isHome: false, referee: 'Bjorn Kuipers',
      });

      const r = await extractor.extractSoccerFeatures({
        player: 'Player X', stat_type: 'goals', line: 0.5, direction: 'over',
      });
      expect(r.features.venue_altitude_ft).toBeGreaterThan(7000);
      expect(r.features.altitude_impact).toBe('high');
      expect(r.features.home_continent).toBe(false); // England isn't CONCACAF
      expect(r.features.home_away).toBe(0.0);        // away match
    });

    test('USA at MetLife Stadium → home-continent true, altitude none', async () => {
      primePlayer('Christian Pulisic', { team: 'USA', goals_per_90: 0.3 });
      mockCacheStore.set('soccer:nextmatch:USA', {
        opponent: 'Brazil', venue: 'MetLife Stadium', isHome: true, referee: null,
      });
      const r = await extractor.extractSoccerFeatures({
        player: 'Christian Pulisic', stat_type: 'goals', line: 0.5, direction: 'over',
      });
      expect(r.features.home_continent).toBe(true);
      expect(r.features.altitude_impact).toBe('none');
      expect(r.features.home_away).toBe(1.0);
      // Static-data lookup catches the penalty/corner role.
      expect(r.features.is_penalty_taker).toBe(true);
    });
  });

  describe('referee profile overlay', () => {
    test('reads cards/penalties per game for upcoming referee', async () => {
      primePlayer('Card Heavy', { team: 'Argentina', goals_per_90: 0.1 });
      mockCacheStore.set('soccer:nextmatch:Argentina', {
        opponent: 'Brazil', venue: 'MetLife Stadium', isHome: true, referee: 'Anthony Taylor',
      });
      mockCacheStore.set('soccer:referee:Anthony Taylor', {
        cards_per_game: 5.4, penalties_per_game: 0.6,
      });
      const r = await extractor.extractSoccerFeatures({
        player: 'Card Heavy', stat_type: 'cards', line: 0.5, direction: 'over',
      });
      expect(r.features.referee_cards_per_game).toBeCloseTo(5.4);
      expect(r.features.referee_penalties_per_game).toBeCloseTo(0.6);
      expect(r.features.referee_name).toBe('Anthony Taylor');
    });
  });

  describe('rest_days from last fixture', () => {
    test('computes days since last finished fixture', async () => {
      primePlayer('Worn Out', { team: 'Brazil', goals_per_90: 0.6 });
      const threeDaysAgo = new Date(Date.now() - 3 * 24 * 3600 * 1000).toISOString();
      mockCacheStore.set('soccer:lastfixture:Brazil', { utcDate: threeDaysAgo });
      const r = await extractor.extractSoccerFeatures({
        player: 'Worn Out', stat_type: 'goals', line: 0.5, direction: 'over',
      });
      expect(r.features.rest_days).toBe(3);
    });

    test('returns null when no last fixture cached', async () => {
      primePlayer('Fresh', { team: 'Croatia', goals_per_90: 0.4 });
      const r = await extractor.extractSoccerFeatures({
        player: 'Fresh', stat_type: 'goals', line: 0.5, direction: 'over',
      });
      expect(r.features.rest_days).toBeNull();
    });

    test('returns null for malformed utcDate', async () => {
      primePlayer('Edge Case', { team: 'Portugal', goals_per_90: 0.4 });
      mockCacheStore.set('soccer:lastfixture:Portugal', { utcDate: 'not-a-date' });
      const r = await extractor.extractSoccerFeatures({
        player: 'Edge Case', stat_type: 'goals', line: 0.5, direction: 'over',
      });
      expect(r.features.rest_days).toBeNull();
    });
  });

  describe('opponent defense overlay', () => {
    test('reads team defense aggregates from cache', async () => {
      primePlayer('Forward', { team: 'England', goals_per_90: 0.6 });
      mockCacheStore.set('soccer:nextmatch:England', {
        opponent: 'Italy', venue: "Levi's Stadium", isHome: true, referee: null,
      });
      mockCacheStore.set('soccer:teamdefense:wc:Italy', {
        goals_conceded_per_game: 0.4, clean_sheet_rate: 0.55,
        defensive_rank: 3, defensive_rank_norm: 0.05, // top defense
      });
      const r = await extractor.extractSoccerFeatures({
        player: 'Forward', stat_type: 'goals', line: 0.5, direction: 'over',
      });
      expect(r.features.opp_goals_conceded_per_game).toBeCloseTo(0.4);
      expect(r.features.opp_clean_sheet_rate).toBeCloseTo(0.55);
      expect(r.features.opp_rank_stat).toBeCloseTo(0.05); // engine1 reads this
    });
  });

  describe('tournament history overlay', () => {
    test('marks designated tournament players', async () => {
      primePlayer('Lionel Messi', { team: 'Argentina', goals_per_90: 0.8 });
      const r = await extractor.extractSoccerFeatures({
        player: 'Lionel Messi', stat_type: 'goals', line: 0.5, direction: 'over',
      });
      expect(r.features.tournament_player).toBe(true);
      expect(r.features.wc_goals_career).toBeGreaterThan(0);
    });
    test('non-tournament players get false', async () => {
      primePlayer('Rookie One', { team: 'USA', goals_per_90: 0.2 });
      const r = await extractor.extractSoccerFeatures({
        player: 'Rookie One', stat_type: 'goals', line: 0.5, direction: 'over',
      });
      expect(r.features.tournament_player).toBe(false);
      expect(r.features.wc_goals_career).toBeNull();
    });
  });

  describe('shape compatibility with engine1', () => {
    test('returns the same top-level keys as the NBA path', async () => {
      primePlayer('Compat Test', { team: 'USA', goals_per_90: 0.3 });
      const r = await extractor.extractSoccerFeatures({
        player: 'Compat Test', stat_type: 'goals', line: 0.5, direction: 'over',
      });
      expect(Object.keys(r).sort()).toEqual(
        ['consistency', 'features', 'meta', 'prop', 'trap'].sort()
      );
      // meta carries the same NBA-path field names so callers can read
      // teamAbbr / opponentAbbr / errors uniformly.
      expect(r.meta).toHaveProperty('teamAbbr');
      expect(r.meta).toHaveProperty('opponentAbbr');
      expect(r.meta).toHaveProperty('errors');
      expect(r.meta).toHaveProperty('sport', 'soccer');
    });
  });
});
