// S6 (A1 board) — ●●○●● last-10 vs tonight's locked line (ROW-GRAMMAR §4).
// Pure math over the rosterlogs blob; stat access reuses streaksService's
// accessors (one source of truth for every field spelling). Real logs only.

const fs = require('fs');
const path = require('path');
const { computeLast10Dots, indexRosterLogs, attachLast10Dots, __internals } = require('../../src/services/last10Dots');

describe('computeLast10Dots', () => {
  const games = [
    { date: '2026-07-10', hits: 2, totalBases: 5 },
    { date: '2026-07-09', hits: 0, totalBases: 0 },
    { date: '2026-07-08', hits: 1, totalBases: 2 },
  ];

  test('hit = value ≥ line, boolean per game, newest first', () => {
    expect(computeLast10Dots(games, 'mlb', 'hits', 1.5)).toEqual([true, false, false]);
    expect(computeLast10Dots(games, 'mlb', 'total_bases', 1.5)).toEqual([true, false, true]);
  });

  test('tolerates the alternate field spellings (streaksService accessors)', () => {
    const alt = [{ H: 3 }, { h: 0 }];
    expect(computeLast10Dots(alt, 'mlb', 'hits', 0.5)).toEqual([true, false]);
  });

  test('nba/wnba stats work too (points, pra)', () => {
    const log = [{ pts: 31, reb: 8, ast: 7 }, { pts: 12, reb: 2, ast: 1 }];
    expect(computeLast10Dots(log, 'wnba', 'points', 20.5)).toEqual([true, false]);
    expect(computeLast10Dots(log, 'nba', 'pra', 40.5)).toEqual([true, false]);
  });

  test('caps at 10 games', () => {
    const long = Array.from({ length: 14 }, (_, i) => ({ hits: i % 2 }));
    expect(computeLast10Dots(long, 'mlb', 'hits', 0.5)).toHaveLength(10);
  });

  test('absent → nothing: unknown stat, no games, bad line all return null', () => {
    expect(computeLast10Dots(games, 'mlb', 'exit_velocity', 1.5)).toBeNull();
    expect(computeLast10Dots([], 'mlb', 'hits', 1.5)).toBeNull();
    expect(computeLast10Dots(games, 'mlb', 'hits', null)).toBeNull();
    expect(computeLast10Dots(games, 'unknown_sport', 'hits', 1.5)).toBeNull();
  });
});

describe('indexRosterLogs + attachLast10Dots', () => {
  const blob = [
    { name: 'A.J. Ewing', team: 'NYY', games: [{ hits: 2 }, { hits: 0 }, { hits: 1 }] },
    { name: 'No Games Guy', team: 'BOS', games: [] },
  ];

  test('indexes by nameKey (variants merge) and skips empty logs', () => {
    const idx = indexRosterLogs(blob);
    expect(idx['aj ewing']).toHaveLength(3);
    expect(Object.keys(idx)).toHaveLength(1);
  });

  test('attaches last10_dots against the LOCKED line; untouched when absent', () => {
    const idx = indexRosterLogs(blob);
    const grades = [
      { player: 'AJ Ewing', stat_type: 'hits', line: 2.5, gradedAt: { line: 0.5 }, grade: 'A' },
      { player: 'Someone Else', stat_type: 'hits', line: 0.5, grade: 'B' },
      { player: 'AJ Ewing', stat_type: 'exit_velocity', line: 90, grade: 'C' },
    ];
    const out = attachLast10Dots(grades, idx, 'mlb');
    // locked line (0.5) is used, not the current line (2.5).
    expect(out[0].last10_dots).toEqual([true, false, true]);
    expect(out[1].last10_dots).toBeUndefined(); // no log → nothing
    expect(out[2].last10_dots).toBeUndefined(); // no accessor → nothing
    expect(out[1]).toBe(grades[1]); // pass-through untouched
  });

  test('empty roster index is a no-op', () => {
    const grades = [{ player: 'AJ Ewing', stat_type: 'hits', line: 0.5 }];
    expect(attachLast10Dots(grades, {}, 'mlb')).toBe(grades);
  });
});

describe('wiring locks', () => {
  test('the public snapshot route reads rosterlogs and attaches dots', () => {
    const route = fs.readFileSync(path.join(__dirname, '..', '..', 'src', 'routes', 'snapshot.js'), 'utf8');
    expect(route).toContain('rosterlogs:${sport}');
    expect(route).toContain('attachLast10Dots');
  });
  test('the slate adapter threads last10_dots + history onto strip props', () => {
    const adapter = fs.readFileSync(path.join(__dirname, '..', '..', 'web', 'src', 'lib', 'slateAdapter.js'), 'utf8');
    expect(adapter).toContain('rec.last10_dots');
    expect(adapter).toContain('rec.history');
  });
  test('every accessor is a function (the streaksService contract holds)', () => {
    for (const [sport, map] of Object.entries(__internals.STAT_ACCESSORS)) {
      for (const [stat, fn] of Object.entries(map)) {
        expect({ sport, stat, isFn: typeof fn === 'function' }).toEqual({ sport, stat, isFn: true });
      }
    }
  });
});
