/**
 * Walk-forward validation: time-stratified only, no look-ahead bias.
 * @param {Array<{predicted: number, timestamp: string}>} predictions
 * @param {Array<{actual: number, timestamp: string}>} actuals
 * @returns {object} Accuracy metrics
 */
function walkForwardValidate(predictions, actuals) {
  if (!predictions || !actuals || predictions.length === 0 || actuals.length === 0) {
    return { accuracy: 0, mae: 0, rmse: 0, n: 0, hit_rate: 0 };
  }

  const paired = predictions.map((pred, i) => {
    const actual = actuals[i];
    if (!actual) return null;
    return { predicted: pred.predicted, actual: actual.actual, timestamp: pred.timestamp };
  }).filter(Boolean);

  // Sort by timestamp to enforce time-stratification
  paired.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));

  const n = paired.length;
  if (n === 0) return { accuracy: 0, mae: 0, rmse: 0, n: 0, hit_rate: 0 };

  let totalError = 0;
  let totalSquaredError = 0;
  let hits = 0;

  for (const p of paired) {
    const error = Math.abs(p.predicted - p.actual);
    totalError += error;
    totalSquaredError += error * error;
    // Hit = within 10% of actual or within 1 unit
    if (error <= Math.max(Math.abs(p.actual) * 0.1, 1)) hits++;
  }

  return {
    accuracy: Math.round((hits / n) * 1000) / 1000,
    mae: Math.round((totalError / n) * 100) / 100,
    rmse: Math.round(Math.sqrt(totalSquaredError / n) * 100) / 100,
    n,
    hit_rate: Math.round((hits / n) * 1000) / 1000,
  };
}

/**
 * Calculate Closing Line Value at multiple checkpoints.
 * @param {number} predictionLine - Our predicted line at time of prediction
 * @param {number} lineAt24h - Market line 24 hours before tip
 * @param {number} lineAtTip - Market line at tip-off
 * @returns {object} { clv_at_prediction, clv_at_24hr, clv_at_tip }
 */
function calculateCLV(predictionLine, lineAt24h, lineAtTip) {
  return {
    clv_at_prediction: Math.round((lineAtTip - predictionLine) * 100) / 100,
    clv_at_24hr: Math.round((lineAtTip - lineAt24h) * 100) / 100,
    clv_at_tip: 0, // By definition, CLV at tip is 0 (reference point)
  };
}

/**
 * Check for model drift: 10 consecutive CLV below 0 triggers alert.
 * @param {Array<number>} clvHistory - Array of CLV values, most recent last
 * @returns {object} { drift_detected, consecutive_negative, alert }
 */
function checkDrift(clvHistory) {
  if (!clvHistory || clvHistory.length === 0) {
    return { drift_detected: false, consecutive_negative: 0, alert: false };
  }

  let consecutiveNeg = 0;
  // Count from the end
  for (let i = clvHistory.length - 1; i >= 0; i--) {
    if (clvHistory[i] < 0) {
      consecutiveNeg++;
    } else {
      break;
    }
  }

  return {
    drift_detected: consecutiveNeg >= 10,
    consecutive_negative: consecutiveNeg,
    alert: consecutiveNeg >= 10,
  };
}

/**
 * Cap weight changes to prevent overfitting.
 * @param {number} currentWeight
 * @param {number} proposedWeight
 * @param {number} maxDelta - Maximum allowed change per cycle (default 0.05)
 * @returns {number} Capped weight
 */
function applyLearningRateCap(currentWeight, proposedWeight, maxDelta = 0.05) {
  const delta = proposedWeight - currentWeight;
  const clampedDelta = Math.max(-maxDelta, Math.min(maxDelta, delta));
  return Math.round((currentWeight + clampedDelta) * 10000) / 10000;
}

module.exports = {
  walkForwardValidate,
  calculateCLV,
  checkDrift,
  applyLearningRateCap,
};
