// Unit: content template engine (Session 29). Pure generators.

const svc = require('../../src/services/contentTemplateService');

// ---- fixtures ----
const schedule = [
  { id: '1', homeTeam: { name: 'Cincinnati Reds', abbreviation: 'CIN' }, awayTeam: { name: 'Arizona Diamondbacks', abbreviation: 'ARI' }, gameTime: '2026-06-13T23:10:00Z', venue: 'GABP' },
  { id: '2', homeTeam: { name: 'New York Mets', abbreviation: 'NYM' }, awayTeam: { name: 'Atlanta Braves', abbreviation: 'ATL' }, gameTime: '2026-06-13T23:40:00Z', venue: 'Citi Field' },
];
const gameLines = {
  '20260613_ARI@CIN': {
    homeTeam: 'CIN', awayTeam: 'ARI',
    books: {
      bet365: { homeML: '-130', awayML: '+110', total: '9.5', homeSpread: '-1.5' },
      betmgm: { homeML: '-120', awayML: '+100', total: '9', homeSpread: '-1.5' },
    },
  },
};
const grades = [
  { player: 'Wembanyama', stat: 'points', line: 28.5, side: 'over', grade: 'A', confidence: 78, edge: 6.2, projection: 31.1 },
  { player_name: 'Brunson', stat_type: 'assists', line: 7.5, direction: 'under', grade: 'B+', confidence: 64, edge_pct: 3.1, projection: 6.8 },
];

describe('determineDataLevel', () => {
  test('full when grades exist', () => {
    expect(svc.determineDataLevel({ grades, gameLines, schedule })).toBe('full');
  });
  test('lines when only game lines', () => {
    expect(svc.determineDataLevel({ grades: [], gameLines, schedule })).toBe('lines');
  });
  test('schedule when only schedule', () => {
    expect(svc.determineDataLevel({ grades: [], gameLines: {}, schedule })).toBe('schedule');
  });
  test('empty when nothing', () => {
    expect(svc.determineDataLevel({ grades: [], gameLines: {}, schedule: [] })).toBe('empty');
  });
});

describe('generateSlateThread', () => {
  test('full data → hook + up-to-5 picks + CTA', () => {
    const t = svc.generateSlateThread('nba', { schedule, gameLines, grades, dataLevel: 'full' });
    expect(t.posts[0].role).toBe('hook');
    expect(t.posts[t.posts.length - 1].role).toBe('cta');
    const picks = t.posts.filter((p) => p.role === 'pick');
    expect(picks).toHaveLength(2);
    // Sorted by confidence desc — Wemby (78) first.
    expect(picks[0].player).toBe('Wembanyama');
    expect(picks[1].player).toBe('Brunson'); // normalized from player_name
    expect(picks[1].side).toBe('under');     // normalized from direction
  });

  test('lines-only → game highlights (+ movers when present)', () => {
    const t = svc.generateSlateThread('mlb', { schedule, gameLines, grades: [], movers: [{ player: 'X', delta: 2 }] });
    expect(t.dataLevel).toBe('lines');
    const highlights = t.posts.filter((p) => p.role === 'game_highlight');
    expect(highlights.length).toBeGreaterThan(0);
    expect(highlights[0].bookCount).toBe(2);
    expect(t.posts.some((p) => p.role === 'movers')).toBe(true);
  });

  test('schedule-only → game list', () => {
    const t = svc.generateSlateThread('mlb', { schedule, gameLines: {}, grades: [] });
    expect(t.dataLevel).toBe('schedule');
    const sched = t.posts.find((p) => p.role === 'schedule');
    expect(sched.games).toHaveLength(2);
    expect(sched.games[0].home).toBe('Cincinnati Reds');
  });

  test('hook text adapts to data level', () => {
    expect(svc.generateSlateThread('mlb', { schedule, grades }).posts[0].text).toMatch(/graded/i);
    expect(svc.generateSlateThread('mlb', { schedule, gameLines, grades: [] }).posts[0].text).toMatch(/sportsbooks/i);
  });

  test('empty schedule → minimal thread, not a crash', () => {
    const t = svc.generateSlateThread('mlb', { schedule: [], gameLines: {}, grades: [] });
    expect(t.dataLevel).toBe('empty');
    expect(t.posts[0].role).toBe('hook');
    expect(t.posts.some((p) => p.role === 'cta')).toBe(true);
  });
});

describe('generatePOTD', () => {
  test('full → best grade selected', () => {
    const p = svc.generatePOTD('nba', { grades, dataLevel: 'full' });
    expect(p.dataLevel).toBe('full');
    expect(p.player).toBe('Wembanyama'); // highest confidence
    expect(p.grade).toBe('A');
  });
  test('lines → game of the day', () => {
    const p = svc.generatePOTD('mlb', { grades: [], gameLines, schedule });
    expect(p.dataLevel).toBe('lines');
    expect(p.subtype).toBe('game_of_the_day');
    expect(p.game).toContain('@');
  });
  test('no data → available:false', () => {
    const p = svc.generatePOTD('mlb', { grades: [], gameLines: {}, schedule: [] });
    expect(p.available).toBe(false);
  });
});

describe('generateResultsRecap', () => {
  const resolved = [
    { player: 'A', stat: 'points', grade: 'A', confidence: 80, result: 'win', clv: 1.2 },
    { player: 'B', stat: 'hits', grade: 'A-', confidence: 70, result: 'win', clv: 0.8 },
    { player: 'C', stat: 'reb', grade: 'B', confidence: 60, result: 'win', clv: -0.2 },
    { player: 'D', stat: 'ast', grade: 'A', confidence: 75, result: 'loss', clv: -1.0 },
    { player: 'E', stat: 'ks', grade: 'B', confidence: 55, result: 'loss', clv: 0.1 },
    { player: 'F', stat: 'tb', grade: 'C', confidence: 50, result: 'push' },
  ];
  test('record + win rate', () => {
    const r = svc.generateResultsRecap('mlb', resolved);
    expect(r.record).toEqual({ wins: 3, losses: 2, pushes: 1 });
    expect(r.winRate).toBe(0.6); // 3 / (3+2)
  });
  test('top hits sorted by confidence', () => {
    const r = svc.generateResultsRecap('mlb', resolved);
    expect(r.topHits[0].player).toBe('A'); // 80 conf win
    expect(r.topHits).toHaveLength(3);
  });
  test('biggest miss = highest-confidence loss', () => {
    const r = svc.generateResultsRecap('mlb', resolved);
    expect(r.biggestMiss.player).toBe('D'); // 75 conf loss
  });
  test('by-tier breakdown', () => {
    const r = svc.generateResultsRecap('mlb', resolved);
    expect(r.byTier.A).toEqual({ wins: 2, losses: 1, total: 3 });
    expect(r.byTier.B).toEqual({ wins: 1, losses: 1, total: 2 });
  });
  test('brier + clv computed', () => {
    const r = svc.generateResultsRecap('mlb', resolved);
    expect(typeof r.metrics.brierScore).toBe('number');
    expect(typeof r.metrics.clv).toBe('number');
  });
  test('no resolved grades → available:false', () => {
    expect(svc.generateResultsRecap('mlb', []).available).toBe(false);
  });
});

describe('generateMatchupPreview', () => {
  const streaks = [
    { player: 'Acuna', team: 'ARI', description: '3-game hit streak', currentStreak: 3 },
    { player: 'Soto', team: 'NYM', description: '2-game HR streak', currentStreak: 2 },
  ];
  test('includes teams, time, venue', () => {
    const p = svc.generateMatchupPreview(schedule[0], gameLines['20260613_ARI@CIN'], streaks);
    expect(p.home.abbreviation).toBe('CIN');
    expect(p.away.abbreviation).toBe('ARI');
    expect(p.venue).toBe('GABP');
  });
  test('lines summary from game lines', () => {
    const p = svc.generateMatchupPreview(schedule[0], gameLines['20260613_ARI@CIN'], streaks);
    expect(p.lines.bookCount).toBe(2);
    expect(p.lines.total).toBe(9.3); // consensus of 9.5 + 9, rounded to 1dp
    expect(p.lines.homeFavorite).toBe(true); // homeSpread -1.5
  });
  test('player streaks matched to the two teams', () => {
    const p = svc.generateMatchupPreview(schedule[0], gameLines['20260613_ARI@CIN'], streaks);
    expect(p.playerStreaks.map((s) => s.player)).toEqual(['Acuna']); // only ARI matches this game
  });
  test('no game lines → lines:null, not a crash', () => {
    const p = svc.generateMatchupPreview(schedule[0], null, streaks);
    expect(p.lines).toBeNull();
    expect(p.narrative).toContain('@'.length ? '' : ''); // narrative present
    expect(typeof p.narrative).toBe('string');
  });
});
