// Session 16 — soccer weather wiring. The feature extractor fetches
// Open-Meteo for outdoor WC venues. Dome venues skip the fetch
// (operators close the roof); unknown venues skip silently.

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

const mockWeather = jest.fn();
jest.mock('../../src/services/weatherService', () => ({
  getWeather: (...a) => mockWeather(...a),
}));

const { extractSoccerFeatures } = require('../../src/services/intelligence/soccerFeatureExtractor');
const { normalizeName } = require('../../src/utils/normalize');

beforeEach(() => {
  mockCache.clear();
  mockWeather.mockReset();
});

function primePlayerAndMatch(player, team, opts = {}) {
  mockCache.set(`soccer:player:${normalizeName(player)}`, {
    team, goals_per_90: 0.5,
  });
  mockCache.set(`soccer:nextmatch:${team}`, {
    opponent: opts.opponent || 'X',
    venue: opts.venue || 'MetLife Stadium',
    isHome: opts.isHome ?? true,
    referee: opts.referee || null,
  });
}

describe('soccer weather wiring (Session 16)', () => {
  test('outdoor WC venue → weather features populated', async () => {
    primePlayerAndMatch('Harry Kane', 'England', { venue: 'MetLife Stadium' });
    mockWeather.mockResolvedValueOnce({
      temp_f: 81.2, wind_mph: 9.4, wind_dir: 220, precip_mm: 0,
    });

    const r = await extractSoccerFeatures({
      player: 'Harry Kane', stat_type: 'goals', line: 0.5, direction: 'over',
    });

    expect(mockWeather).toHaveBeenCalledTimes(1);
    expect(r.features.weather_temp_f).toBeCloseTo(81.2);
    expect(r.features.weather_wind_mph).toBeCloseTo(9.4);
    expect(r.features.weather_wind_dir).toBe(220);
    expect(r.features.weather_precip_mm).toBe(0);
  });

  test('dome WC venue (BC Place) → weather fetch skipped, fields null', async () => {
    primePlayerAndMatch('Sub Player', 'Canada', { venue: 'BC Place' });
    const r = await extractSoccerFeatures({
      player: 'Sub Player', stat_type: 'goals', line: 0.5, direction: 'over',
    });
    expect(mockWeather).not.toHaveBeenCalled();
    expect(r.features.weather_temp_f).toBeNull();
    expect(r.features.weather_wind_mph).toBeNull();
  });

  test('Estadio Azteca (open-air, high-altitude) → weather fetched + altitude_impact still high', async () => {
    primePlayerAndMatch('Forward', 'Mexico', { venue: 'Estadio Azteca' });
    mockWeather.mockResolvedValueOnce({ temp_f: 68, wind_mph: 5, wind_dir: 90, precip_mm: 0 });
    const r = await extractSoccerFeatures({
      player: 'Forward', stat_type: 'goals', line: 0.5, direction: 'over',
    });
    expect(r.features.weather_temp_f).toBe(68);
    expect(r.features.altitude_impact).toBe('high');
  });

  test('venue not in the WC index → weather fetch skipped', async () => {
    primePlayerAndMatch('X', 'Y', { venue: 'Random Stadium' });
    const r = await extractSoccerFeatures({
      player: 'X', stat_type: 'goals', line: 0.5, direction: 'over',
    });
    expect(mockWeather).not.toHaveBeenCalled();
    expect(r.features.weather_temp_f).toBeNull();
  });

  test('weather service returns null → feature fields stay null (no throw)', async () => {
    primePlayerAndMatch('X', 'England', { venue: 'MetLife Stadium' });
    mockWeather.mockResolvedValueOnce(null);
    const r = await extractSoccerFeatures({
      player: 'X', stat_type: 'goals', line: 0.5, direction: 'over',
    });
    expect(mockWeather).toHaveBeenCalledTimes(1);
    expect(r.features.weather_temp_f).toBeNull();
    expect(r.features.weather_wind_mph).toBeNull();
  });

  test('weather service throws → graceful degrade, grade still produced', async () => {
    primePlayerAndMatch('X', 'England', { venue: 'MetLife Stadium' });
    mockWeather.mockRejectedValueOnce(new Error('timeout'));
    const r = await extractSoccerFeatures({
      player: 'X', stat_type: 'goals', line: 0.5, direction: 'over',
    });
    expect(r.features.weather_temp_f).toBeNull();
    // Other features (goals_per_90 etc.) still populated.
    expect(r.features.goals_per_90).toBe(0.5);
  });

  test('no venue resolved → weather skipped entirely (no fetch attempt)', async () => {
    mockCache.set(`soccer:player:${normalizeName('Solo')}`, { team: 'England', goals_per_90: 0.5 });
    // No nextmatch entry → venueName is null.
    const r = await extractSoccerFeatures({
      player: 'Solo', stat_type: 'goals', line: 0.5, direction: 'over',
    });
    expect(mockWeather).not.toHaveBeenCalled();
    expect(r.features.weather_temp_f).toBeNull();
  });
});
