// Wave 0 — THE PROOF: an NBA/WNBA prop now GRADES instead of refusing.
//
// Root cause (pre-fix): featureCache's non-MLB branch called gameLogService
// (Python nba_api, offline in prod) → null logs → no l5/l20 → projectionFor
// null → insufficientDataResult → the whole slate refused. This test injects
// the Python source as NULL (offline) and the ESPN gamelog as the free fallback,
// and asserts real l5_avg/l20_avg emerge → projectionFor returns a projection.

// Python game-log service is OFFLINE (returns null) — the prod failure mode.
jest.mock('../../src/services/intelligence/gameLogService', () => ({
  getGameLogs: async () => null,
  getCareerPlayoffGames: async () => null,
  getWithWithoutStats: async () => null,
}));

// ESPN gamelog fallback — a normalized result (as espnStatsAdapter would return).
const espnLog = { res: null };
jest.mock('../../src/services/adapters/espnStatsAdapter', () => ({
  getPlayerGameLog: async () => espnLog.res,
}));

const { __internals: fc } = require('../../src/services/intelligence/featureCache');
const { __internals: eng } = require('../../src/services/intelligence/analyzeViaEngine1');

// Most-recent first, matching getPlayerGameLog's contract.
const lebronLog = {
  found: true,
  id: '1966',
  last10: [
    { date: '2026-01-05T00:30:00Z', stat: { points: 24, rebounds: 12, assists: 3, threes: 2, minutes: 40, pra: 39 } },
    { date: '2026-01-03T00:30:00Z', stat: { points: 28, rebounds: 10, assists: 7, threes: 3, minutes: 36, pra: 45 } },
    { date: '2026-01-01T00:30:00Z', stat: { points: 23, rebounds: 8, assists: 5, threes: 1, minutes: 32, pra: 36 } },
    { date: '2025-12-29T00:30:00Z', stat: { points: 30, rebounds: 9, assists: 6, threes: 4, minutes: 38, pra: 45 } },
    { date: '2025-12-27T00:30:00Z', stat: { points: 21, rebounds: 11, assists: 8, threes: 2, minutes: 34, pra: 40 } },
    { date: '2025-12-25T00:30:00Z', stat: { points: 26, rebounds: 7, assists: 9, threes: 3, minutes: 37, pra: 42 } },
  ],
};

describe('nbaGameLogFeatures (pure)', () => {
  it('derives l5/l10/l20 + rest + usage for an NBA points prop', () => {
    const f = fc.nbaGameLogFeatures(lebronLog, 'points');
    // l5 = mean of the 5 most-recent points (24,28,23,30,21) = 25.2
    expect(f.l5_avg).toBeCloseTo((24 + 28 + 23 + 30 + 21) / 5, 5);
    expect(f.l20_avg).toBeGreaterThan(0);
    expect(f.minutes_per_game).toBeGreaterThan(0);
    // consecutive games (2d apart) → 1d gap → 0 rest? dates are 2d apart → gap 2 → rest 1
    expect(f.rest_days).toBe(1);
  });

  it('handles combo (pra) + threes via the log-field map', () => {
    expect(fc.nbaGameLogFeatures(lebronLog, 'pra').l5_avg).toBeGreaterThan(0);
    expect(fc.nbaGameLogFeatures(lebronLog, 'threes').l5_avg).toBeGreaterThan(0);
    // pts_reb_ast combo sums components → equals the precomputed pra
    expect(fc.nbaGameLogFeatures(lebronLog, 'pts_reb_ast').l5_avg)
      .toBeCloseTo(fc.nbaGameLogFeatures(lebronLog, 'pra').l5_avg, 5);
  });

  it('returns {} for an unfound player or unmapped stat', () => {
    expect(fc.nbaGameLogFeatures({ found: false }, 'points')).toEqual({});
    expect(fc.nbaGameLogFeatures(lebronLog, 'not_a_stat')).toEqual({});
  });
});

describe('gameLogFeatures — ESPN fallback when Python is offline', () => {
  it('emits non-null l5_avg/l20_avg for nba (Python null → ESPN fallback)', async () => {
    espnLog.res = lebronLog;
    const f = await fc.gameLogFeatures('LeBron James', 'nba', 'points');
    expect(f.l5_avg).not.toBeNull();
    expect(f.l5_avg).toBeGreaterThan(0);
    expect(f.l20_avg).toBeGreaterThan(0);
  });

  it('works for wnba too', async () => {
    espnLog.res = lebronLog; // shape-identical
    const f = await fc.gameLogFeatures("A'ja Wilson", 'wnba', 'rebounds');
    expect(f.l5_avg).toBeGreaterThan(0);
  });

  it('degrades to {} when ESPN also has nothing (still no fabrication)', async () => {
    espnLog.res = { found: false };
    const f = await fc.gameLogFeatures('Ghost Player', 'nba', 'points');
    expect(f).toEqual({});
  });
});

describe('THE UNLOCK — projectionFor now returns a grade, not insufficient_data', () => {
  it('empty features (the old prod state) → projection null → REFUSE', () => {
    expect(eng.projectionFor({}, { stat_type: 'points', line: 20 })).toBeNull();
  });

  it('ESPN-derived features → finite projection → the prop GRADES', async () => {
    espnLog.res = lebronLog;
    const features = await fc.gameLogFeatures('LeBron James', 'nba', 'points');
    const projection = eng.projectionFor(features, { stat_type: 'points', line: 24.5 });
    expect(projection).not.toBeNull();
    expect(Number.isFinite(projection)).toBe(true);
    // and the intel card lights up off the same vector
    const intel = eng.buildIntelFields(features);
    expect(intel.season_avg).toBeDefined();
    expect(intel.last10_avg).toBeDefined();
    expect(intel.form).toBeDefined();
  });
});
