/**
 * Session 64 Phase 3 — opp_rank_stat is ONE column with ONE meaning.
 *
 * The highest-risk item is POLARITY. Backwards polarity does not fail loudly —
 * it silently adjusts every MLB grade in the WRONG direction, which is worse
 * than having no value at all. So the contract is asserted against WNBA's live
 * semantics, not merely "a number appears".
 */
const os = require('../../src/services/opponentStrength');

// 30 teams; BAA against ranges 0.200 (toughest staff) → 0.290 (weakest).
const league = Array.from({ length: 30 }, (_, i) => ({
  teamId: i + 1,
  stat: { avg: 0.200 + i * 0.003, slg: 0.350 + i * 0.005, gamesPlayed: 90, homeRuns: 10 + i, strikeOuts: 300 + i },
}));

describe('THE SHARED CONTRACT — scale and polarity match WNBA', () => {
  test('scale is 0–1, never a raw count', () => {
    const r = os.deriveMlbOppRank(league, 15, 'hits');
    expect(r.value).toBeGreaterThanOrEqual(0);
    expect(r.value).toBeLessThanOrEqual(1);
    expect(r.value).not.toBe(league[14].stat.avg);   // not the raw stat
  });

  test('POLARITY: the WEAKEST staff (highest BAA) scores HIGH = weak opponent', () => {
    const weakest = os.deriveMlbOppRank(league, 30, 'hits');   // BAA .287
    expect(weakest.value).toBeGreaterThanOrEqual(0.70);
  });

  test('POLARITY: the TOUGHEST staff (lowest BAA) scores LOW = tough opponent', () => {
    const toughest = os.deriveMlbOppRank(league, 1, 'hits');   // BAA .200
    expect(toughest.value).toBeLessThanOrEqual(0.30);
  });

  test('MLB polarity EQUALS WNBA polarity — high fires weak, low fires tough', () => {
    // engine1's live thresholds, applied identically to both sports.
    const WEAK = 0.70;
    const TOUGH = 0.30;
    const weak = os.deriveMlbOppRank(league, 30, 'hits').value;
    const tough = os.deriveMlbOppRank(league, 1, 'hits').value;
    // The same comparison engine1 makes for WNBA must classify MLB the same way.
    expect(weak >= WEAK).toBe(true);
    expect(tough <= TOUGH).toBe(true);
    expect(weak).toBeGreaterThan(tough);            // monotone in the right direction
  });

  test('a mid-pack staff lands mid-scale and fires NEITHER factor', () => {
    const mid = os.deriveMlbOppRank(league, 15, 'hits').value;
    expect(mid).toBeGreaterThan(0.30);
    expect(mid).toBeLessThan(0.70);
  });
});

describe('HONEST NULLS — never a confident value off thin data', () => {
  test('a thin LEAGUE baseline returns null, not a rank', () => {
    const r = os.deriveMlbOppRank(league.slice(0, 5), 1, 'hits');
    expect(r.value).toBeNull();
    expect(r.reason).toBe('league_baseline_too_thin');
  });

  test("a thin OPPONENT sample returns null", () => {
    const thin = league.map((t, i) => (i === 0 ? { ...t, stat: { ...t.stat, gamesPlayed: 3 } } : t));
    const r = os.deriveMlbOppRank(thin, 1, 'hits');
    expect(r.value).toBeNull();
    expect(r.reason).toBe('opponent_sample_too_thin');
  });

  test('an unmapped stat returns null rather than a wrong field', () => {
    expect(os.deriveMlbOppRank(league, 1, 'stolen_bases').reason).toBe('stat_not_mapped');
  });

  test('an unknown opponent returns null', () => {
    expect(os.deriveMlbOppRank(league, 999, 'hits').reason).toBe('opponent_not_found');
  });

  test('a missing field returns null, never 0', () => {
    const noField = league.map((t) => ({ ...t, stat: { ...t.stat, avg: null } }));
    const r = os.deriveMlbOppRank(noField, 1, 'hits');
    expect(r.value).toBeNull();
    expect(r.value).not.toBe(0);
  });
});

describe('HEALTH — we are fixing a silent null, not rebuilding one', () => {
  test('empty source pages', () => {
    expect(os.opponentStrengthHealth({ sport: 'mlb', teamsLoaded: 0 }).alarm).toBe(true);
  });
  test('all-null despite a loaded source pages', () => {
    expect(os.opponentStrengthHealth({ sport: 'mlb', teamsLoaded: 30, attempted: 25, derived: 0 }).alarm).toBe(true);
  });
  test('healthy derivation is quiet', () => {
    expect(os.opponentStrengthHealth({ sport: 'mlb', teamsLoaded: 30, attempted: 25, derived: 24 }).alarm).toBe(false);
  });
});
