/**
 * Closing Line Value (CLV) tracking.
 *
 *   CLV measures how much edge we found vs the market close. Beating the
 *   close consistently is the canonical signal of real edge, regardless
 *   of any individual prop's outcome. This is how we prove (to ourselves
 *   and to users) that VYNDR's grades are doing something real.
 *
 *   Computation:
 *     - For OVER: CLV = closing_line - graded_line
 *       We graded a line at 25.5, close was 27.5 → we saw the over was
 *       too cheap before the market did → +2.0 CLV.
 *     - For UNDER: CLV = graded_line - closing_line
 *       We graded under 25.5, close was 23.5 → +2.0 CLV.
 *
 *   Closing lines come from oddspapi via the resolution poller, stored in
 *   closing_lines (migration 016). The match key is
 *     (game_id, player_espn_id OR player_name, stat_type)
 *   so a graded prop without a captured close returns null — not zero.
 */

const { getSupabaseServiceClient } = require('../../utils/supabase');

function rawCLV(direction, gradedLine, closingLine) {
  // Guard against null/undefined first — Number(null) === 0 is finite,
  // which would silently produce a 0-based CLV instead of "unknown."
  if (gradedLine == null || closingLine == null) return null;
  const g = Number(gradedLine);
  const c = Number(closingLine);
  if (!Number.isFinite(g) || !Number.isFinite(c)) return null;
  return direction === 'over' ? c - g : g - c;
}

async function fetchGrade(gradeId) {
  const supabase = getSupabaseServiceClient();
  const { data, error } = await supabase
    .from('grade_history')
    .select('id, game_id, sport, player_id, player_name, stat_type, line, direction, clv')
    .eq('id', gradeId)
    .maybeSingle();
  if (error) {
    console.warn('[clv] grade lookup failed:', error.message);
    return null;
  }
  return data;
}

async function fetchClosingLine(grade) {
  const supabase = getSupabaseServiceClient();
  let query = supabase
    .from('closing_lines')
    .select('id, pinnacle_line')
    .eq('game_id', grade.game_id)
    .eq('stat_type', grade.stat_type);
  // Prefer ID match (canonical), fall back to name match.
  query = grade.player_id
    ? query.eq('player_espn_id', grade.player_id)
    : query.eq('player_name', grade.player_name);
  const { data, error } = await query.maybeSingle();
  if (error) {
    console.warn('[clv] closing line lookup failed:', error.message);
    return null;
  }
  return data;
}

async function persistCLV(gradeId, clv, closingLineId) {
  const supabase = getSupabaseServiceClient();
  const { error } = await supabase
    .from('grade_history')
    .update({ clv, closing_line_id: closingLineId || null })
    .eq('id', gradeId);
  if (error) console.warn('[clv] persist failed:', error.message);
}

async function computeCLV(gradeId) {
  const grade = await fetchGrade(gradeId);
  if (!grade) return null;
  const closing = await fetchClosingLine(grade);
  if (!closing) {
    return {
      gradeId,
      clv: null,
      graded_line: Number(grade.line),
      closing_line: null,
      direction: grade.direction,
      sport: grade.sport,
      reason: 'no_closing_line',
    };
  }
  const clv = rawCLV(grade.direction, grade.line, closing.pinnacle_line);
  if (clv != null) await persistCLV(gradeId, clv, closing.id);
  return {
    gradeId,
    clv,
    graded_line: Number(grade.line),
    closing_line: Number(closing.pinnacle_line),
    direction: grade.direction,
    sport: grade.sport,
  };
}

async function batchComputeCLV(gradeIds) {
  const out = [];
  for (const id of gradeIds) {
    try { out.push(await computeCLV(id)); }
    catch (err) {
      console.warn('[clv] batch entry failed:', id, err.message);
      out.push({ gradeId: id, clv: null, error: err.message });
    }
  }
  return out;
}

async function getCLVSummary(sport, period = 'all_time') {
  const supabase = getSupabaseServiceClient();
  let query = supabase
    .from('grade_history')
    .select('clv')
    .eq('sport', sport)
    .not('clv', 'is', null);
  if (period === 'last_30d') {
    const since = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
    query = query.gte('graded_at', since);
  } else if (period === 'last_7d') {
    const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
    query = query.gte('graded_at', since);
  }
  const { data, error } = await query;
  if (error) {
    console.warn('[clv] summary query failed:', error.message);
    return { avg_clv: null, median_clv: null, positive_rate: null, total: 0 };
  }
  if (!data || data.length === 0) {
    return { avg_clv: null, median_clv: null, positive_rate: null, total: 0 };
  }
  const values = data.map((r) => Number(r.clv)).filter((v) => Number.isFinite(v));
  const avg = values.reduce((a, b) => a + b, 0) / values.length;
  const sorted = [...values].sort((a, b) => a - b);
  const mid = Math.floor(sorted.length / 2);
  const median = sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
  const positive = values.filter((v) => v > 0).length;
  return {
    avg_clv: avg,
    median_clv: median,
    positive_rate: positive / values.length,
    total: values.length,
  };
}

module.exports = { computeCLV, batchComputeCLV, getCLVSummary, rawCLV };
