// Unit: parlay builder service (Session 28). Pure functions.

const { calculateParlay, detectCorrelation, suggestParlays, __internals } = require('../../src/services/parlayService');

describe('parlayService — odds conversions', () => {
  const { americanToDecimal, decimalToAmerican } = __internals;
  test('americanToDecimal: +100 → 2.0, -110 → ~1.909', () => {
    expect(americanToDecimal(100)).toBeCloseTo(2.0, 5);
    expect(americanToDecimal(-110)).toBeCloseTo(1.9091, 3);
  });
  test('decimalToAmerican round-trips', () => {
    expect(decimalToAmerican(2.0)).toBe(100);
    expect(decimalToAmerican(1.5)).toBe(-200);
  });
});

describe('parlayService — calculateParlay', () => {
  test('2-leg combined odds multiply correctly', () => {
    // -110 (1.909) × -110 (1.909) = 3.645 → +264 American
    const r = calculateParlay([
      { player: 'A', stat: 'points', side: 'over', line: 20, odds: -110, grade: 'B', confidence: 60 },
      { player: 'B', stat: 'hits', side: 'over', line: 1, odds: -110, grade: 'B', confidence: 60, gameId: 'g2' },
    ]);
    expect(r.combinedDecimal).toBeCloseTo(3.645, 2);
    expect(r.combinedOdds).toBe(264);
    expect(r.legCount).toBe(2);
  });

  test('3-leg combined odds', () => {
    const r = calculateParlay([
      { player: 'A', stat: 'points', odds: 100, grade: 'A', confidence: 70 },
      { player: 'B', stat: 'hits', odds: 100, grade: 'A', confidence: 70, gameId: 'g2' },
      { player: 'C', stat: 'goals', odds: 100, grade: 'A', confidence: 70, gameId: 'g3' },
    ]);
    expect(r.combinedDecimal).toBeCloseTo(8.0, 5); // 2×2×2
    expect(r.combinedOdds).toBe(700);
  });

  test('combined grade is confidence-weighted', () => {
    const r = calculateParlay([
      { player: 'A', stat: 'points', odds: -110, grade: 'A', confidence: 90 },
      { player: 'B', stat: 'hits', odds: -110, grade: 'C', confidence: 10, gameId: 'g2' },
    ]);
    // Heavily weighted toward the A leg.
    expect(['A-', 'A', 'B+']).toContain(r.combinedGrade);
  });

  test('payoutPer10 computed', () => {
    const r = calculateParlay([{ player: 'A', stat: 'points', odds: 100, grade: 'B' }]);
    expect(r.payoutPer10).toBe(20); // $10 at +100 → $20 total
  });

  test('empty legs → 400 error, not crash', () => {
    expect(() => calculateParlay([])).toThrow();
    try { calculateParlay([]); } catch (e) { expect(e.statusCode).toBe(400); }
  });

  test('kill conditions aggregated', () => {
    const r = calculateParlay([
      { player: 'A', stat: 'points', odds: -110, grade: 'B', killConditions: ['blowout risk'] },
      { player: 'B', stat: 'hits', odds: -110, grade: 'B', gameId: 'g2' },
    ]);
    expect(r.hasKillCondition).toBe(true);
    expect(r.killConditions[0].player).toBe('A');
  });
});

describe('parlayService — correlation detection', () => {
  test('different games → independent', () => {
    const c = detectCorrelation(
      { player: 'A', stat: 'points', gameId: 'g1', team: 'X' },
      { player: 'B', stat: 'assists', gameId: 'g2', team: 'Y' },
    );
    expect(c.correlated).toBe(false);
    expect(c.type).toBe('independent');
  });

  test('same-game teammates assists+points → positive', () => {
    const c = detectCorrelation(
      { player: 'A', stat: 'assists', gameId: 'g1', team: 'X' },
      { player: 'B', stat: 'points', gameId: 'g1', team: 'X' },
    );
    expect(c.correlated).toBe(true);
    expect(c.type).toBe('positive');
  });

  test('same-game opposing rebounds → negative (they fight)', () => {
    const c = detectCorrelation(
      { player: 'A', stat: 'rebounds', gameId: 'g1', team: 'X' },
      { player: 'B', stat: 'rebounds', gameId: 'g1', team: 'Y' },
    );
    expect(c.correlated).toBe(true);
    expect(c.type).toBe('negative');
  });

  test('same player opposite sides on same prop → negative conflict', () => {
    const c = detectCorrelation(
      { player: 'A', stat: 'points', side: 'over', gameId: 'g1' },
      { player: 'A', stat: 'points', side: 'under', gameId: 'g1' },
    );
    expect(c.type).toBe('negative');
  });

  test('negative correlation surfaced on the parlay', () => {
    const r = calculateParlay([
      { player: 'A', stat: 'rebounds', side: 'over', odds: -110, grade: 'B', gameId: 'g1', team: 'X' },
      { player: 'B', stat: 'rebounds', side: 'over', odds: -110, grade: 'B', gameId: 'g1', team: 'Y' },
    ]);
    expect(r.hasNegativeCorrelation).toBe(true);
    expect(r.correlations).toHaveLength(1);
  });
});

describe('parlayService — suggestParlays', () => {
  const pool = [
    { player: 'A', stat: 'points', odds: -110, grade: 'A', gameId: 'g1', team: 'X' },
    { player: 'B', stat: 'hits', odds: -110, grade: 'A-', gameId: 'g2', team: 'Y' },
    { player: 'C', stat: 'goals', odds: -110, grade: 'B+', gameId: 'g3', team: 'Z' },
    { player: 'D', stat: 'assists', odds: -110, grade: 'B', gameId: 'g4', team: 'W' },
  ];

  test('returns a suggestion of the requested leg count', () => {
    const s = suggestParlays(pool, { legs: 3, max: 1 });
    expect(s).toHaveLength(1);
    expect(s[0].legs).toHaveLength(3);
    expect(s[0].combinedGrade).toBeDefined();
  });

  test('too few props → empty', () => {
    expect(suggestParlays([pool[0]], { legs: 3 })).toEqual([]);
  });
});
