/**
 * Consistency score — how predictable is this player for this stat?
 *
 *   cv = stddev / mean
 *
 * Coefficient of variation collapses sample-size differences and lets us
 * compare a 25-point scorer with low variance to a 12-point scorer with
 * the same absolute variance. Lower cv = more reliable.
 *
 * The consistency score modifies Engine 2's confidence. An "elite"
 * consistency player gets a tighter projection range; a "boom_bust"
 * player gets a wider one.
 */

const gameLogService = require('./gameLogService');

function statFromGameLog(row, statType) {
  if (!row) return null;
  switch (statType) {
    case 'pts_reb_ast':
      return (Number(row.points) || 0) + (Number(row.rebounds) || 0) + (Number(row.assists) || 0);
    case 'pts_reb':
      return (Number(row.points) || 0) + (Number(row.rebounds) || 0);
    case 'pts_ast':
      return (Number(row.points) || 0) + (Number(row.assists) || 0);
    case 'reb_ast':
      return (Number(row.rebounds) || 0) + (Number(row.assists) || 0);
    case 'stl_blk':
      return (Number(row.steals) || 0) + (Number(row.blocks) || 0);
    default: {
      const v = Number(row[statType]);
      return Number.isFinite(v) ? v : null;
    }
  }
}

function classify(cv) {
  if (cv < 0.15) return { consistency: 'elite', score: 1.0 };
  if (cv < 0.30) return { consistency: 'reliable', score: 0.7 };
  if (cv < 0.50) return { consistency: 'volatile', score: 0.4 };
  return { consistency: 'boom_bust', score: 0.1 };
}

/**
 * Session 63 — the CV thresholds above are NBA-calibrated (points ~20/game,
 * cv ~0.2-0.4). They are MEANINGLESS for a low-count stat.
 *
 * For a Poisson-ish counting stat, cv ≈ 1/sqrt(mean). So mean < 4 forces
 * cv > 0.5 — i.e. EVERY such stat classifies 'boom_bust' no matter how the
 * player actually behaves. Verified against real logs: Alonso hits
 * [0,0,0,1,2,1,0,1,1,0] → mean 0.60, cv 1.17 → boom_bust; Henderson
 * [1,0,0,3,1,1,0,0,1,0] → mean 0.70, cv 1.36 → boom_bust.
 *
 * When the estimator path was revived, this would have stamped a blanket
 * -1.0 on nearly every MLB prop — a systematic downgrade masquerading as a
 * signal. Below the floor we return 'unknown' so engine1 adds NO factor:
 * absent beats wrong.
 *
 * The RIGHT long-term fix is an index-of-dispersion (variance/mean vs the
 * Poisson baseline) classifier, which is scale-free. That is a modelling
 * change with its own validation and is tracked separately — this floor is
 * the honest stopgap, not the answer.
 */
const MIN_MEAN_FOR_CV = Number(process.env.CONSISTENCY_MIN_MEAN || 4);

function cvIsMeaningful(mean) {
  return Number.isFinite(mean) && Math.abs(mean) >= MIN_MEAN_FOR_CV;
}

function statsFor(values) {
  const clean = values.filter((v) => Number.isFinite(v));
  if (clean.length < 2) return null;
  const mean = clean.reduce((a, b) => a + b, 0) / clean.length;
  if (mean === 0) return null;
  const variance = clean.reduce((s, v) => s + (v - mean) ** 2, 0) / (clean.length - 1);
  const stddev = Math.sqrt(variance);
  return { mean, stddev, cv: stddev / Math.abs(mean), games: clean.length };
}

async function getConsistency(input = {}) {
  const { playerName, sport, statType, gameLogs: providedLogs } = input;
  const logs = providedLogs || await gameLogService.getGameLogs(playerName, sport, 20);
  if (!logs || logs.length < 2) {
    return { consistency: 'unknown', score: null, games: logs?.length ?? 0 };
  }
  const values = logs.map((row) => statFromGameLog(row, statType)).filter((v) => v != null);
  const s = statsFor(values);
  if (!s) return { consistency: 'unknown', score: null, games: values.length };
  // Session 63 — refuse to classify when CV cannot discriminate at this scale.
  if (!cvIsMeaningful(s.mean)) {
    return { ...s, consistency: 'unknown', score: null, reason: 'low_mean_cv_unreliable' };
  }
  return { ...s, ...classify(s.cv) };
}

module.exports = {
  getConsistency, classify, statsFor, statFromGameLog, cvIsMeaningful, MIN_MEAN_FOR_CV,
};
