// Unit: streaks engine (Session 23). Pure function — no mocks needed.

const { computeStreaks, computePlayerStreaks } = require('../../src/services/streaksService');

// Most-recent-first game logs.
function nbaGames(...rows) { return rows; }

describe('streaksService — NBA', () => {
  test('detects a consecutive 25+ points streak from the latest games', () => {
    const player = {
      name: 'Wembanyama', playerId: 'W1', team: 'SA',
      games: [{ points: 31 }, { points: 28 }, { points: 33 }, { points: 22 }, { points: 40 }],
    };
    const streaks = computePlayerStreaks(player, 'nba');
    const pts = streaks.find((s) => s.category === 'points');
    expect(pts).toBeDefined();
    // 31,28,33 meet 25+, then 22 breaks → run of 3.
    expect(pts.currentStreak).toBe(3);
    expect(pts.description).toBe('3-game 25+ pts streak');
  });

  test('streak breaks immediately when the latest game misses the threshold', () => {
    const player = { name: 'X', games: [{ points: 10 }, { points: 30 }, { points: 30 }] };
    const streaks = computePlayerStreaks(player, 'nba');
    expect(streaks.find((s) => s.category === 'points')).toBeUndefined(); // run of 0 < MIN_STREAK
  });

  test('only the strongest streak per category surfaces', () => {
    // 20+ and 25+ both qualify; keep the 25+ (higher run not guaranteed, but
    // one points entry only).
    const player = { name: 'Y', games: [{ points: 26 }, { points: 27 }, { points: 21 }] };
    const streaks = computePlayerStreaks(player, 'nba');
    const ptsEntries = streaks.filter((s) => s.category === 'points');
    expect(ptsEntries).toHaveLength(1);
  });

  test('double-double streak counts categories >= 10', () => {
    const player = {
      name: 'Jokic', games: [
        { points: 20, rebounds: 12, assists: 11 },
        { points: 15, rebounds: 10, assists: 9 },
        { points: 8, rebounds: 11, assists: 3 },
      ],
    };
    const streaks = computePlayerStreaks(player, 'nba');
    const dd = streaks.find((s) => s.type === 'double_double');
    expect(dd).toBeDefined();
    // g0: 3 doubles, g1: 2 doubles, g2: 1 double → dd (>=2) run breaks at g2.
    expect(dd.currentStreak).toBe(2);
    expect(dd.description).toBe('2-game double-double streak');
  });

  test('empty game logs → empty streaks (not error)', () => {
    expect(computePlayerStreaks({ name: 'Z', games: [] }, 'nba')).toEqual([]);
    expect(computeStreaks([], 'nba')).toEqual([]);
  });

  test('chronological input is reversed before counting', () => {
    const player = { name: 'C', games: [{ points: 22 }, { points: 30 }, { points: 31 }] };
    // As chronological (oldest first) the latest is 31,30 → run 2.
    const streaks = computePlayerStreaks(player, 'nba', { chronological: true });
    const pts = streaks.find((s) => s.category === 'points');
    expect(pts.currentStreak).toBe(2);
  });
});

describe('streaksService — MLB', () => {
  test('classic hit streak counts consecutive games with a hit', () => {
    const player = {
      name: 'Acuna', team: 'ATL',
      games: [{ hits: 2 }, { hits: 1 }, { hits: 3 }, { hits: 0 }, { hits: 1 }],
    };
    const streaks = computePlayerStreaks(player, 'mlb');
    const hit = streaks.find((s) => s.type === 'hit_streak');
    expect(hit.currentStreak).toBe(3);
    expect(hit.description).toBe('3-game hit streak');
  });

  test('quality-start streak uses IP + ER', () => {
    const pitcher = {
      name: 'Strider',
      games: [
        { inningsPitched: 7, earnedRuns: 2 },
        { inningsPitched: 6, earnedRuns: 3 },
        { inningsPitched: 5, earnedRuns: 1 }, // < 6 IP breaks it
      ],
    };
    const streaks = computePlayerStreaks(pitcher, 'mlb');
    const qs = streaks.find((s) => s.type === 'qs_streak');
    expect(qs.currentStreak).toBe(2);
  });

  test('MLB specs do not produce NBA streak types', () => {
    const player = { name: 'P', games: [{ hits: 2 }, { hits: 2 }] };
    const streaks = computePlayerStreaks(player, 'mlb');
    expect(streaks.every((s) => s.type !== 'points_25')).toBe(true);
  });
});

describe('streaksService — fan-out + filtering', () => {
  const roster = [
    { name: 'A', games: [{ points: 30 }, { points: 30 }, { points: 30 }] },
    { name: 'B', games: [{ assists: 9 }, { assists: 8 }] },
    { name: 'C', games: [{ points: 26 }, { points: 27 }] },
  ];

  test('sorts by streak length descending', () => {
    const streaks = computeStreaks(roster, 'nba');
    expect(streaks[0].player).toBe('A'); // 3-game points streak leads
    expect(streaks[0].currentStreak).toBe(3);
  });

  test('stat filter narrows to a single category', () => {
    const streaks = computeStreaks(roster, 'nba', { stat: 'points' });
    expect(streaks.every((s) => s.category === 'points')).toBe(true);
    expect(streaks.map((s) => s.player).sort()).toEqual(['A', 'C']);
  });

  test('limit caps the result', () => {
    const streaks = computeStreaks(roster, 'nba', { limit: 1 });
    expect(streaks).toHaveLength(1);
  });

  test('all filter (default) returns every category', () => {
    const streaks = computeStreaks(roster, 'nba', { stat: 'all' });
    const cats = new Set(streaks.map((s) => s.category));
    expect(cats.has('points')).toBe(true);
    expect(cats.has('assists')).toBe(true);
  });
});
