'use strict';

/**
 * heroPropService (Truth-Everywhere Part 2, item 5) — the DAILY HERO PROP.
 *
 * The landing card is a live rule, not a hand-picked example: the graded prop
 * where VYNDR disagrees MOST with the market — the largest |model - consensus|
 * gap — gated to A/B grades (conviction, not noise). Highest-confidence would
 * just be the model agreeing loudly with an undisputed number; the disagreement
 * is the read that makes a stranger argue.
 *
 * Deterministic, no curation. Reads the pre-graded snapshot caches (no grading,
 * no API credits). Empty slate → the MOST RECENT real graded read with its real
 * date. Never a hand-written fallback. Truly nothing cached → { available:false }
 * and the card hides.
 */

const DEFAULT_SPORTS = ['mlb', 'wnba', 'nba', 'soccer'];
// A/B tiers only — the conviction gate.
const isAB = (g) => /^[AB]/.test(String(g || '').trim().toUpperCase());

function toHero(g, sport, gap, isRecent) {
  const at = g.gradedAt || {};
  return {
    available: true,
    is_recent: !!isRecent,          // true = the empty-slate "most recent real read"
    sport,
    player: g.player_name || g.player || null,
    stat_type: g.stat_type || g.stat || null,
    line: g.line ?? at.line ?? null,          // the book's number (consensus)
    direction: g.direction || 'over',
    projection: g.projection ?? null,          // VYNDR's number (model)
    grade: g.grade || null,
    book: g.book || null,
    graded_at: at.timestamp || null,           // real timestamp — "graded 2:14 PM"
    gap: gap == null ? null : Math.round(gap * 100) / 100,
    team: g.team || null,
    reasoning: (g.reasoning && g.reasoning.summary) || null, // blurred paywall teaser
    // Model Train (steps 2/4/6) — EV ranking, the value flag, and the value
    // triplet (book price · fair de-vigged price · model price).
    ev_pct: g.ev_pct ?? null,
    value: g.value ?? null,
    takeable: g.takeable ?? null,
    book_odds: g.book_odds ?? (at.odds != null ? Number(at.odds) : null),
    fair_odds: g.fair_odds ?? null,
    model_odds: g.model_odds ?? null,
  };
}

// A prop is a valid hero candidate only with a real projection AND a real line
// (both required to compute an honest gap; a missing value never counts as 0).
function candidate(g) {
  const line = Number(g && g.line);
  const proj = Number(g && g.projection);
  return Number.isFinite(line) && line > 0 && Number.isFinite(proj) && proj > 0;
}

async function pickHeroProp(deps = {}) {
  const cacheGet = deps.cacheGet || require('../utils/redis').cacheGet;
  const sports = deps.sports || DEFAULT_SPORTS;

  const all = [];
  for (const sport of sports) {
    let grades = null;
    const snap = await cacheGet(`snapshot:${sport}:latest`);
    if (snap && Array.isArray(snap.grades)) grades = snap.grades;
    else {
      const env = await cacheGet(`grades:${sport}`);
      if (env && Array.isArray(env.grades)) grades = env.grades;
    }
    for (const g of grades || []) {
      if (!g || !g.grade || g.insufficient_data) continue;
      all.push({ g, sport });
    }
  }

  // HERO RULE v2 (Model Train, step 5): the highest EV among reads that pass the
  // TAKEABLE gate, A/B grades only. A huge model-vs-line gap on a -900 line is
  // trivia; the hero is the best OPPORTUNITY at a price you'd actually take.
  const { isTakeable } = require('../config/valueEngine');
  let hero = null, heroEv = -Infinity;
  for (const { g, sport } of all) {
    if (!isAB(g.grade) || !candidate(g)) continue;
    if (!Number.isFinite(Number(g.ev_pct))) continue;      // need a real EV to rank
    if (!isTakeable(g.book_odds)) continue;                // promoted surface → takeable only
    if (Number(g.ev_pct) > heroEv) { heroEv = Number(g.ev_pct); hero = { g, sport }; }
  }
  if (hero) {
    const gap = candidate(hero.g) ? Math.abs(Number(hero.g.projection) - Number(hero.g.line)) : null;
    return toHero(hero.g, hero.sport, gap, false);
  }

  // Empty slate → the MOST RECENT real graded read (any grade), by timestamp.
  let recent = null, recentTs = '';
  for (const { g, sport } of all) {
    const ts = (g.gradedAt && g.gradedAt.timestamp) || '';
    if (ts && ts > recentTs) { recentTs = ts; recent = { g, sport }; }
  }
  if (recent) {
    const gp = candidate(recent.g) ? Math.abs(Number(recent.g.projection) - Number(recent.g.line)) : null;
    return toHero(recent.g, recent.sport, gp, true);
  }

  // Truly nothing cached — the card hides. Never a fabricated fallback.
  return { available: false };
}

module.exports = { pickHeroProp, __internals: { isAB, candidate, DEFAULT_SPORTS } };
