const engine1 = require('../../src/services/intelligence/engine1');

function withProp(features = {}, trap = {}, consistency = {}, prop = { line: 25.5, direction: 'over' }) {
  return engine1.gradeProp({ features, trap, consistency, prop });
}

describe('engine1.gradeProp', () => {
  test('hot recent form + weak D + home + rested → A-tier', () => {
    const result = withProp(
      { l5_avg: 32, l20_avg: 27, opp_rank_stat: 0.85, home_away: 1.0, rest_days: 2 },
      { composite: 0.1 },
      { consistency: 'reliable', score: 0.7 },
    );
    expect(['A+', 'A', 'A-']).toContain(result.grade);
    expect(result.top_factors.length).toBeGreaterThan(0);
  });

  test('cold L5 + strong opponent D + back-to-back → D/F tier', () => {
    const result = withProp(
      { l5_avg: 18, l20_avg: 21, opp_rank_stat: 0.1, home_away: 0.0, rest_days: 0 },
      { composite: 0.6 },
      { consistency: 'boom_bust', score: 0.1 },
    );
    expect(['F', 'D', 'C-']).toContain(result.grade);
  });

  test('high trap composite downgrades a hot prop to C-tier', () => {
    const hotNoTrap = withProp(
      { l5_avg: 30, l20_avg: 26, opp_rank_stat: 0.8, home_away: 1.0, rest_days: 2 },
      { composite: 0.1 },
      { consistency: 'reliable' },
    );
    const hotWithTrap = withProp(
      { l5_avg: 30, l20_avg: 26, opp_rank_stat: 0.8, home_away: 1.0, rest_days: 2 },
      { composite: 0.7 },
      { consistency: 'reliable' },
    );
    // The trap version should land at least one step lower on the scale.
    const idxHigh = engine1.GRADE_SCALE.indexOf(hotNoTrap.grade);
    const idxLow = engine1.GRADE_SCALE.indexOf(hotWithTrap.grade);
    expect(idxLow).toBeLessThan(idxHigh);
  });

  test('UNDER direction inverts the L5 polarity', () => {
    // Player averaging 18 with line 25.5 over → cold for OVER.
    const over = withProp(
      { l5_avg: 18, l20_avg: 19 },
      { composite: 0.1 },
      { consistency: 'reliable' },
      { line: 25.5, direction: 'over' },
    );
    const under = withProp(
      { l5_avg: 18, l20_avg: 19 },
      { composite: 0.1 },
      { consistency: 'reliable' },
      { line: 25.5, direction: 'under' },
    );
    const overIdx = engine1.GRADE_SCALE.indexOf(over.grade);
    const underIdx = engine1.GRADE_SCALE.indexOf(under.grade);
    expect(underIdx).toBeGreaterThan(overIdx);
  });

  test('returns confidence proportional to grade', () => {
    const a = withProp({ l5_avg: 32, l20_avg: 27, opp_rank_stat: 0.85, home_away: 1.0, rest_days: 2 },
      { composite: 0.05 }, { consistency: 'elite', score: 1.0 });
    const c = withProp({}, {}, {});
    expect(a.confidence).toBeGreaterThan(c.confidence);
  });

  test('with no features at all, returns neutral C', () => {
    const result = withProp({}, {}, {});
    expect(result.grade).toBe('C');
    expect(result.top_factors).toEqual([]);
  });

  test('all_factors lists every contributing factor, top_factors slices to 3', () => {
    const result = withProp(
      { l5_avg: 32, l20_avg: 27, opp_rank_stat: 0.85, home_away: 1.0, rest_days: 2, coach_pace_delta: 1.2 },
      { composite: 0.1 },
      { consistency: 'elite', score: 1.0 },
    );
    expect(result.top_factors.length).toBeLessThanOrEqual(3);
    expect(result.all_factors.length).toBeGreaterThanOrEqual(result.top_factors.length);
  });
});

describe('GRADE_SCALE invariants', () => {
  test('11 grades in ascending order F → A+', () => {
    expect(engine1.GRADE_SCALE).toEqual(['F', 'D', 'C-', 'C', 'C+', 'B-', 'B', 'B+', 'A-', 'A', 'A+']);
  });

  test('confidence values strictly ascending', () => {
    const conf = engine1.GRADE_SCALE.map((g) => engine1.GRADE_TO_CONFIDENCE[g]);
    for (let i = 1; i < conf.length; i += 1) {
      expect(conf[i]).toBeGreaterThan(conf[i - 1]);
    }
  });
});
