const {
  toLegacyShape,
  __internals: {
    FOUR_LETTER_MAP,
    fourLetterGrade,
    legacyConfidence,
    killConditionsFromFactors,
    buildReasoningSummary,
    legacyEdgePct,
  },
} = require('../../src/utils/gradeAdapter');

describe('gradeAdapter — fourLetterGrade', () => {
  test('A-tier (A+, A, A-) all map to "A"', () => {
    expect(fourLetterGrade('A+')).toBe('A');
    expect(fourLetterGrade('A')).toBe('A');
    expect(fourLetterGrade('A-')).toBe('A');
  });
  test('B-tier maps to "B"', () => {
    expect(fourLetterGrade('B+')).toBe('B');
    expect(fourLetterGrade('B')).toBe('B');
    expect(fourLetterGrade('B-')).toBe('B');
  });
  test('C-tier maps to "C"', () => {
    expect(fourLetterGrade('C+')).toBe('C');
    expect(fourLetterGrade('C')).toBe('C');
    expect(fourLetterGrade('C-')).toBe('C');
  });
  test('D maps to "D", F maps to "F" (DemoScan handles F → "—")', () => {
    expect(fourLetterGrade('D')).toBe('D');
    expect(fourLetterGrade('F')).toBe('F');
  });
  test('unknown / null / non-string returns null', () => {
    expect(fourLetterGrade('Z')).toBeNull();
    expect(fourLetterGrade(null)).toBeNull();
    expect(fourLetterGrade(undefined)).toBeNull();
    expect(fourLetterGrade(42)).toBeNull();
  });
  test('FOUR_LETTER_MAP exposes every engine1 grade', () => {
    const expected = ['A+','A','A-','B+','B','B-','C+','C','C-','D','F'];
    for (const g of expected) expect(FOUR_LETTER_MAP[g]).toBeDefined();
  });
});

describe('gradeAdapter — legacyConfidence', () => {
  test('0.0 → 0, 0.5 → 50, 1.0 → 100', () => {
    expect(legacyConfidence(0.0)).toBe(0);
    expect(legacyConfidence(0.5)).toBe(50);
    expect(legacyConfidence(1.0)).toBe(100);
  });
  test('0.873 → 87 (rounded)', () => {
    expect(legacyConfidence(0.873)).toBe(87);
  });
  test('out-of-range clamps to [0, 100]', () => {
    expect(legacyConfidence(-0.5)).toBe(0);
    expect(legacyConfidence(2.0)).toBe(100);
  });
  test('non-numeric input returns null', () => {
    expect(legacyConfidence(null)).toBeNull();
    expect(legacyConfidence(undefined)).toBeNull();
    expect(legacyConfidence('abc')).toBeNull();
  });
});

describe('gradeAdapter — killConditionsFromFactors', () => {
  test('translates known negative factors to kill condition entries', () => {
    const out = killConditionsFromFactors(['trap_composite_high', 'l5_cold_vs_line']);
    const codes = out.map((k) => k.code);
    expect(codes).toContain('TRAP');
    expect(codes).toContain('COLD_L5');
  });

  test('positive factors are ignored', () => {
    const out = killConditionsFromFactors(['l5_hot_vs_line', 'home_game', 'rested_2plus']);
    expect(out).toEqual([]);
  });

  test('deduplicates by code (l5_cold + l5_hot_under both → COLD_L5)', () => {
    const out = killConditionsFromFactors(['l5_cold_vs_line', 'l5_hot_vs_under']);
    expect(out).toHaveLength(1);
    expect(out[0].code).toBe('COLD_L5');
  });

  test('empty / non-array → []', () => {
    expect(killConditionsFromFactors([])).toEqual([]);
    expect(killConditionsFromFactors(null)).toEqual([]);
    expect(killConditionsFromFactors(undefined)).toEqual([]);
  });
});

describe('gradeAdapter — buildReasoningSummary', () => {
  test('uses summaryOverride when provided', () => {
    const out = buildReasoningSummary({ grade: 'A' }, {}, 'fancy override');
    expect(out).toBe('fancy override');
  });

  test('builds a sentence from top_factors', () => {
    const out = buildReasoningSummary({
      grade: 'A-',
      top_factors: ['l5_hot_vs_line', 'home_game', 'rested_2plus'],
    }, { player: 'X' });
    expect(out).toContain('A-');
    expect(out).toContain('l5_hot_vs_line');
    expect(out).toContain('favoring the play');
  });

  test('falls back gracefully when no factors are surfaced', () => {
    const out = buildReasoningSummary({ grade: 'B' });
    expect(typeof out).toBe('string');
    expect(out).toContain('B');
  });
});

describe('gradeAdapter — legacyEdgePct', () => {
  test('numeric override passes through', () => {
    expect(legacyEdgePct(5.2)).toBe(5.2);
    expect(legacyEdgePct(-3)).toBe(-3);
  });

  test('non-numeric returns 0 (legacy callers expect the field present)', () => {
    expect(legacyEdgePct(null)).toBe(0);
    expect(legacyEdgePct(undefined)).toBe(0);
    expect(legacyEdgePct('abc')).toBe(0);
  });
});

describe('gradeAdapter — toLegacyShape (round-trip)', () => {
  const engine1Result = {
    grade: 'A-',
    confidence: 0.78,
    top_factors: ['l5_hot_vs_line', 'opp_rank_stat', 'rested_2plus'],
    all_factors: ['l5_hot_vs_line', 'opp_rank_stat', 'rested_2plus', 'home_game'],
  };
  const prop = {
    player: 'Jalen Brunson',
    stat_type: 'points',
    line: 26.5,
    direction: 'over',
    book: 'draftkings',
    sport: 'nba',
  };

  test('produces every field DemoScan consumes', () => {
    const out = toLegacyShape(engine1Result, prop);
    expect(out).toHaveProperty('grade', 'A');
    expect(out).toHaveProperty('confidence', 78);
    expect(out).toHaveProperty('reasoning.summary');
    expect(typeof out.reasoning.summary).toBe('string');
    expect(out).toHaveProperty('kill_conditions_triggered');
    expect(Array.isArray(out.kill_conditions_triggered)).toBe(true);
    expect(out).toHaveProperty('edge_pct');
    expect(out).toHaveProperty('player', 'Jalen Brunson');
    expect(out).toHaveProperty('stat_type', 'points');
    expect(out).toHaveProperty('line', 26.5);
    expect(out).toHaveProperty('direction', 'over');
    expect(out).toHaveProperty('book', 'draftkings');
  });

  test('edgePct override flows through', () => {
    const out = toLegacyShape(engine1Result, prop, { edgePct: 4.7 });
    expect(out.edge_pct).toBe(4.7);
  });

  test('summaryOverride wins over the factor-based sentence', () => {
    const out = toLegacyShape(engine1Result, prop, { summaryOverride: 'Hand-written take.' });
    expect(out.reasoning.summary).toBe('Hand-written take.');
  });

  test('partial engine1 input does not crash', () => {
    const partial = { grade: 'B+' };
    const out = toLegacyShape(partial, prop);
    expect(out.grade).toBe('B');
    expect(out.confidence).toBeNull();
    expect(out.kill_conditions_triggered).toEqual([]);
    expect(typeof out.reasoning.summary).toBe('string');
  });

  test('null engine1 input returns null', () => {
    expect(toLegacyShape(null, prop)).toBeNull();
    expect(toLegacyShape(undefined, prop)).toBeNull();
  });

  test('engine1_factors travel through reasoning.steps for debugging', () => {
    const out = toLegacyShape(engine1Result, prop);
    expect(Array.isArray(out.reasoning.steps.engine1_factors)).toBe(true);
    expect(out.reasoning.steps.engine1_factors).toEqual(engine1Result.all_factors);
    expect(out.reasoning.steps.final_grade).toBe('A');
  });

  test('legacy-shape compatibility: every key DemoScan reads is present', () => {
    const out = toLegacyShape(engine1Result, prop);
    // DemoScan reads: grade, confidence, reasoning.summary,
    // kill_conditions_triggered (with .code on each entry),
    // implied_probability (optional — adapter doesn't add it but doesn't
    // need to since the field is allowed to be undefined).
    expect(out.grade).toBeDefined();
    expect(out.confidence).toBeDefined();
    expect(out.reasoning.summary).toBeDefined();
    for (const kc of out.kill_conditions_triggered) {
      expect(kc.code).toBeDefined();
      expect(typeof kc.code).toBe('string');
    }
  });
});
