// Unit: grade-slate writer (Session 32). Closes the content pipeline by
// persisting a sport's graded slate to the `grades:{sport}` cache that
// contentTemplateService reads.

const svc = require('../../src/services/gradeSlateService');
const content = require('../../src/services/contentTemplateService');
const { shouldGradeSlate } = require('../../src/services/oddsService');
const { dedupeProps } = svc.__internals;

// Multi-book odds rows: same player+stat+line appears once per book, plus a
// distinct prop. dedupe should collapse the book duplicates.
const props = [
  { player: 'Wembanyama', stat_type: 'points', line: 28.5, book: 'draftkings', over_odds: -110, under_odds: -110 },
  { player: 'Wembanyama', stat_type: 'points', line: 28.5, book: 'fanduel', over_odds: -105, under_odds: -115 },
  { player: 'Brunson', stat_type: 'assists', line: 7.5, book: 'betmgm', over_odds: +100, under_odds: -120 },
];

// Fake grader: returns the legacy grade shape. Over/under differ in
// confidence so we can prove the writer keeps the stronger side.
function fakeGrade({ player, stat_type, line, direction }) {
  const table = {
    'Wembanyama::over': { confidence: 80, grade: 'A' },
    'Wembanyama::under': { confidence: 40, grade: 'C' },
    'Brunson::over': { confidence: 35, grade: 'C' },
    'Brunson::under': { confidence: 64, grade: 'B+' },
  };
  const hit = table[`${player}::${direction}`] || { confidence: 10, grade: 'C' };
  return {
    player, stat_type, line, direction,
    grade: hit.grade, confidence: hit.confidence, edge_pct: 2.0,
    reasoning: { summary: `${player} ${direction} ${line}` },
  };
}

describe('shouldGradeSlate (auto-grade gate)', () => {
  const orig = process.env.GRADE_SLATE_ON_FETCH;
  afterEach(() => {
    if (orig === undefined) delete process.env.GRADE_SLATE_ON_FETCH;
    else process.env.GRADE_SLATE_ON_FETCH = orig;
  });

  test('off by default under the test env', () => {
    delete process.env.GRADE_SLATE_ON_FETCH;
    expect(shouldGradeSlate()).toBe(false); // NODE_ENV === 'test'
  });
  test('GRADE_SLATE_ON_FETCH=1 forces on', () => {
    process.env.GRADE_SLATE_ON_FETCH = '1';
    expect(shouldGradeSlate()).toBe(true);
  });
  test('GRADE_SLATE_ON_FETCH=0 forces off', () => {
    process.env.GRADE_SLATE_ON_FETCH = '0';
    expect(shouldGradeSlate()).toBe(false);
  });
});

describe('dedupeProps', () => {
  test('collapses multi-book rows to unique player+stat+line', () => {
    expect(dedupeProps(props, 25)).toHaveLength(2);
  });
  test('respects the limit', () => {
    expect(dedupeProps(props, 1)).toHaveLength(1);
  });
  test('skips rows missing player/stat/line', () => {
    const bad = [{ stat_type: 'points', line: 1 }, { player: 'X', line: 1 }, { player: 'X', stat_type: 'points' }];
    expect(dedupeProps(bad, 25)).toHaveLength(0);
  });
});

describe('gradeAndCacheSlate', () => {
  test('writes grades:{sport} with the envelope content reads, sorted by confidence desc', async () => {
    const writes = [];
    const cacheSet = async (key, value, ttl) => { writes.push({ key, value, ttl }); };
    const res = await svc.gradeAndCacheSlate('nba', props, {
      grade: fakeGrade, cacheSet, source: 'propline', now: () => '2026-06-15T00:00:00.000Z',
    });

    expect(res).toEqual({ written: true, count: 2 });
    expect(writes).toHaveLength(1);

    const { key, value } = writes[0];
    // Cache key must match contentTemplateService.getGrades fallback read.
    expect(key).toBe('grades:nba');
    expect(value.source).toBe('propline');
    expect(value.updated_at).toBe('2026-06-15T00:00:00.000Z');
    expect(Array.isArray(value.grades)).toBe(true);
    // Sorted by confidence desc.
    expect(value.grades.map((g) => g.confidence)).toEqual([80, 64]);
  });

  test('keeps the higher-confidence side per prop (engine1 is direction-aware)', async () => {
    let written;
    const cacheSet = async (_k, value) => { written = value; };
    await svc.gradeAndCacheSlate('nba', props, { grade: fakeGrade, cacheSet });

    const wemby = written.grades.find((g) => g.player === 'Wembanyama');
    const brunson = written.grades.find((g) => g.player === 'Brunson');
    expect(wemby.direction).toBe('over');   // over (80) beat under (40)
    expect(brunson.direction).toBe('under'); // under (64) beat over (35)
  });

  test('respects the TTL', async () => {
    let ttlSeen;
    const cacheSet = async (_k, _v, ttl) => { ttlSeen = ttl; };
    await svc.gradeAndCacheSlate('nba', props, { grade: fakeGrade, cacheSet, ttl: 1234 });
    expect(ttlSeen).toBe(1234);

    ttlSeen = undefined;
    await svc.gradeAndCacheSlate('nba', props, { grade: fakeGrade, cacheSet });
    expect(ttlSeen).toBe(svc.__internals.DEFAULT_TTL);
  });

  test('empty props → nothing written', async () => {
    let called = false;
    const cacheSet = async () => { called = true; };
    expect(await svc.gradeAndCacheSlate('nba', [], { grade: fakeGrade, cacheSet })).toEqual({ written: false, count: 0 });
    expect(called).toBe(false);
  });

  test('all grader failures → nothing written', async () => {
    let called = false;
    const cacheSet = async () => { called = true; };
    const boom = () => { throw new Error('grader down'); };
    const res = await svc.gradeAndCacheSlate('nba', props, { grade: boom, cacheSet });
    expect(res.written).toBe(false);
    expect(called).toBe(false);
  });

  test('output flows into contentTemplateService → dataLevel full, picks, POTD', async () => {
    let written;
    const cacheSet = async (_k, value) => { written = value; };
    await svc.gradeAndCacheSlate('nba', props, { grade: fakeGrade, cacheSet });

    // contentTemplateService.getGrades returns cache.grades (the array).
    const data = { sport: 'nba', schedule: [], gameLines: {}, grades: written.grades };
    expect(content.determineDataLevel(data)).toBe('full');

    const thread = content.generateSlateThread('nba', { ...data, dataLevel: 'full' });
    const picks = thread.posts.filter((p) => p.role === 'pick');
    expect(picks.length).toBe(2);
    expect(picks[0].player).toBe('Wembanyama'); // highest confidence pick first

    const potd = content.generatePOTD('nba', { ...data, dataLevel: 'full' });
    expect(potd.dataLevel).toBe('full');
    expect(potd.player).toBe('Wembanyama');
    expect(potd.confidence).toBe(80);
  });
});
