/**
 * Engine 1 weight adjustment — the learning loop.
 *
 * Every resolved prop nudges Engine 1's factor weights in the direction
 * the outcome implies. Factors that contributed to a winning grade get a
 * small boost; factors behind a losing grade get pulled down. Each nudge
 * is tiny on purpose:
 *   - max ±0.5% per resolution
 *   - weights clamped to [0.1, 5.0]
 *   - versioned per (sport, stat_type, factor_name) for rollback
 *   - skipped entirely until 20+ resolutions exist for the sport
 *     (don't overfit a small sample)
 *
 * Every adjustment writes a new row in engine1_weights — the table is
 * append-only. To recall a factor's current weight, we read the latest
 * version. Rolling back means inserting a new row whose weight equals
 * an older version's weight.
 */

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

const LEARNING_RATE = 0.005;
const MIN_WEIGHT = 0.1;
const MAX_WEIGHT = 5.0;
const MIN_RESOLUTIONS_TO_LEARN = 20;
const DEFAULT_WEIGHT = 1.0;

const GRADE_CONFIDENCE = {
  'A+': 1.00, 'A': 0.90, 'A-': 0.80,
  'B+': 0.65, 'B': 0.55, 'B-': 0.45,
  'C+': 0.35, 'C': 0.25, 'C-': 0.20,
  'D': 0.15, 'F': 0.10,
};

function clamp(w) {
  return Math.max(MIN_WEIGHT, Math.min(MAX_WEIGHT, w));
}

async function countResolutions(sport) {
  const supabase = getSupabaseServiceClient();
  const { count, error } = await supabase
    .from('resolution_results')
    .select('id', { head: true, count: 'exact' })
    .eq('sport', sport);
  if (error) {
    console.warn('[weightAdjuster] count failed:', error.message);
    return 0;
  }
  return Number(count) || 0;
}

async function getCurrentWeights(sport, statType) {
  // Latest version of each factor for (sport, stat_type).
  const supabase = getSupabaseServiceClient();
  const { data, error } = await supabase
    .from('engine1_weights')
    .select('factor_name, weight, version')
    .eq('sport', sport)
    .eq('stat_type', statType)
    .order('version', { ascending: false });
  if (error) {
    console.warn('[weightAdjuster] read failed:', error.message);
    return {};
  }
  const latest = {};
  for (const row of data || []) {
    if (!(row.factor_name in latest)) {
      latest[row.factor_name] = { weight: Number(row.weight), version: row.version };
    }
  }
  // Flatten to { factor: weight }
  const out = {};
  for (const k of Object.keys(latest)) out[k] = latest[k].weight;
  return out;
}

async function getNextVersion(sport, statType, factorName) {
  const supabase = getSupabaseServiceClient();
  const { data, error } = await supabase
    .from('engine1_weights')
    .select('version')
    .eq('sport', sport)
    .eq('stat_type', statType)
    .eq('factor_name', factorName)
    .order('version', { ascending: false })
    .limit(1);
  if (error) {
    console.warn('[weightAdjuster] version lookup failed:', error.message);
    return 1;
  }
  const top = data?.[0]?.version;
  return Number.isFinite(Number(top)) ? Number(top) + 1 : 1;
}

async function persistAdjustment(sport, statType, factorName, newWeight, prevWeight, reason, resolvedGradeId) {
  const supabase = getSupabaseServiceClient();
  const version = await getNextVersion(sport, statType, factorName);
  const { error } = await supabase.from('engine1_weights').insert({
    sport,
    stat_type: statType,
    factor_name: factorName,
    weight: newWeight,
    previous_weight: prevWeight,
    adjustment_reason: reason,
    resolved_grade_id: resolvedGradeId || null,
    version,
  });
  if (error) {
    console.warn('[weightAdjuster] insert failed:', error.message);
    return null;
  }
  return version;
}

// Public entry point. resolvedGrade carries the Engine 1 grade, the prop's
// stat_type/sport, the resolved result, and the factors that drove the
// grade (top_factors or all_factors).
async function adjustWeights(resolvedGrade) {
  const { sport, stat_type: statType, grade, result, factors, grade_id: resolvedGradeId } = resolvedGrade || {};
  if (!sport || !statType || !grade || !result || !Array.isArray(factors) || factors.length === 0) {
    return { skipped: true, reason: 'incomplete_input' };
  }
  if (result !== 'hit' && result !== 'miss') {
    return { skipped: true, reason: 'non_decisive_result' };
  }

  const sampleCount = await countResolutions(sport);
  if (sampleCount < MIN_RESOLUTIONS_TO_LEARN) {
    return { skipped: true, reason: 'thin_sample', sampleCount };
  }

  const current = await getCurrentWeights(sport, statType);
  const confidence = GRADE_CONFIDENCE[grade] ?? 0.5;
  const sign = result === 'hit' ? 1 : -1;
  const multiplier = 1 + sign * LEARNING_RATE * confidence;

  const adjustments = [];
  for (const factor of factors) {
    const prev = current[factor] ?? DEFAULT_WEIGHT;
    const next = clamp(prev * multiplier);
    const version = await persistAdjustment(
      sport, statType, factor, next, prev,
      `${result} on grade ${grade}`,
      resolvedGradeId,
    );
    adjustments.push({ factor, previous: prev, next, version });
  }
  return { skipped: false, adjustments, multiplier, confidence };
}

// Restore to a prior version by inserting a NEW row whose weight equals the
// target version's weight. Append-only is the safe primitive — we never
// mutate or delete history.
async function rollbackToVersion(sport, statType, factorName, targetVersion) {
  const supabase = getSupabaseServiceClient();
  const { data, error } = await supabase
    .from('engine1_weights')
    .select('weight')
    .eq('sport', sport)
    .eq('stat_type', statType)
    .eq('factor_name', factorName)
    .eq('version', targetVersion)
    .maybeSingle();
  if (error || !data) {
    console.warn('[weightAdjuster] rollback target not found');
    return false;
  }
  const current = (await getCurrentWeights(sport, statType))[factorName] ?? DEFAULT_WEIGHT;
  const version = await persistAdjustment(
    sport, statType, factorName, Number(data.weight), current,
    `rollback to v${targetVersion}`,
    null,
  );
  return Number.isFinite(version);
}

async function getWeightHistory(sport, statType, factorName, limit = 50) {
  const supabase = getSupabaseServiceClient();
  const { data, error } = await supabase
    .from('engine1_weights')
    .select('weight, previous_weight, adjustment_reason, version, created_at')
    .eq('sport', sport)
    .eq('stat_type', statType)
    .eq('factor_name', factorName)
    .order('version', { ascending: false })
    .limit(limit);
  if (error) return [];
  return data || [];
}

module.exports = {
  adjustWeights,
  getCurrentWeights,
  rollbackToVersion,
  getWeightHistory,
  LEARNING_RATE,
  MIN_WEIGHT,
  MAX_WEIGHT,
  MIN_RESOLUTIONS_TO_LEARN,
  __internals: { clamp, countResolutions, getNextVersion, persistAdjustment, GRADE_CONFIDENCE },
};
