/**
 * Confidence Calibration Engine
 *
 * Compares AI predictions vs actual human scores.
 * Builds a calibration curve that maps raw AI confidence → actual approval rates.
 *
 * From §6.5 of CONTRACTS.md:
 * - Bucket raw confidence into 10% ranges
 * - Compute actual approval rate per bucket
 * - Calculate correction delta (actual - predicted)
 * - Track Expected Calibration Error (ECE)
 */

import type {
  EnrichedFeedbackEvent,
  CalibrationCurve,
  CalibrationBucket,
  CalibrationEntry,
} from "../types.js";

// ═══════════════════════════════════════════════════════════════
// CALIBRATION COMPUTATION
// ═══════════════════════════════════════════════════════════════

/**
 * Compute the calibration curve from feedback events.
 */
export async function computeCalibration(
  events: EnrichedFeedbackEvent[]
): Promise<CalibrationCurve> {
  // Filter to events that have both a prediction and a decision
  const calibrationData = events
    .filter(
      (e) =>
        e.workProduct.bubaConfidence !== undefined &&
        e.feedback.decision?.decision !== undefined
    )
    .map((e) => ({
      rawConfidence: e.workProduct.bubaConfidence,
      approved: e.feedback.decision!.decision === "approved",
      predictedScore: e.workProduct.bubaScorePrediction,
      actualScore:
        e.feedback.scores && e.feedback.scores.length > 0
          ? e.feedback.scores.reduce((sum, s) => sum + s.score, 0) /
            e.feedback.scores.length
          : undefined,
    }));

  if (calibrationData.length === 0) {
    return {
      lastUpdated: new Date().toISOString(),
      totalDataPoints: 0,
      buckets: [],
      expectedCalibrationError: 0,
    };
  }

  // Create 10 buckets: [0.0-0.1), [0.1-0.2), ..., [0.9-1.0]
  const buckets: CalibrationBucket[] = [];

  for (let i = 0; i < 10; i++) {
    const low = i / 10;
    const high = (i + 1) / 10;

    const inBucket = calibrationData.filter(
      (d) =>
        d.rawConfidence >= low &&
        (i === 9 ? d.rawConfidence <= high : d.rawConfidence < high)
    );

    if (inBucket.length === 0) continue;

    const avgPredicted =
      inBucket.reduce((sum, d) => sum + d.rawConfidence, 0) / inBucket.length;
    const avgActual =
      inBucket.filter((d) => d.approved).length / inBucket.length;
    const correctionDelta = avgActual - avgPredicted;

    buckets.push({
      rawConfidenceLow: low,
      rawConfidenceHigh: high,
      avgPredictedConfidence: Math.round(avgPredicted * 1000) / 1000,
      avgActualApprovalRate: Math.round(avgActual * 1000) / 1000,
      sampleCount: inBucket.length,
      correctionDelta: Math.round(correctionDelta * 1000) / 1000,
      calibratedConfidence: Math.round(
        Math.max(0, Math.min(1, avgPredicted + correctionDelta)) * 1000
      ) / 1000,
    });
  }

  // Compute Expected Calibration Error (ECE)
  const totalSamples = calibrationData.length;
  const ece = buckets.reduce((sum, bucket) => {
    const weight = bucket.sampleCount / totalSamples;
    const error = Math.abs(
      bucket.avgPredictedConfidence - bucket.avgActualApprovalRate
    );
    return sum + weight * error;
  }, 0);

  return {
    lastUpdated: new Date().toISOString(),
    totalDataPoints: calibrationData.length,
    buckets,
    expectedCalibrationError: Math.round(ece * 1000) / 1000,
  };
}

// ═══════════════════════════════════════════════════════════════
// CALIBRATION APPLICATION
// ═══════════════════════════════════════════════════════════════

/**
 * Apply calibration correction to a raw confidence value.
 */
export function applyCalibration(
  rawConfidence: number,
  curve: CalibrationCurve
): number {
  if (curve.buckets.length === 0) return rawConfidence;

  // Find the matching bucket
  const bucket = curve.buckets.find(
    (b) =>
      rawConfidence >= b.rawConfidenceLow &&
      rawConfidence <= b.rawConfidenceHigh
  );

  if (!bucket) {
    // No matching bucket — find nearest
    const nearest = curve.buckets.reduce((best, b) => {
      const midpoint = (b.rawConfidenceLow + b.rawConfidenceHigh) / 2;
      const bestMidpoint = (best.rawConfidenceLow + best.rawConfidenceHigh) / 2;
      return Math.abs(rawConfidence - midpoint) <
        Math.abs(rawConfidence - bestMidpoint)
        ? b
        : best;
    });
    return Math.max(0, Math.min(1, rawConfidence + nearest.correctionDelta));
  }

  return Math.max(0, Math.min(1, rawConfidence + bucket.correctionDelta));
}

/**
 * Extract calibration entries for tracking prediction accuracy over time.
 */
export function extractCalibrationEntries(
  events: EnrichedFeedbackEvent[]
): CalibrationEntry[] {
  return events
    .filter(
      (e) =>
        e.workProduct.bubaScorePrediction !== undefined &&
        e.feedback.decision !== undefined &&
        e.feedback.scores &&
        e.feedback.scores.length > 0
    )
    .map((e) => {
      const actualScore =
        e.feedback.scores!.reduce((sum, s) => sum + s.score, 0) /
        e.feedback.scores!.length;
      return {
        taskType: e.mcpServerType ?? e.workProduct.type,
        predicted: {
          score: e.workProduct.bubaScorePrediction!,
          confidence: e.workProduct.bubaConfidence,
        },
        actual: {
          score: actualScore,
          approved: e.feedback.decision!.decision === "approved",
        },
        delta: actualScore - e.workProduct.bubaScorePrediction!,
        timestamp: e.timestamp,
      };
    });
}

/**
 * Compute prediction accuracy (mean absolute error of score predictions).
 */
export function computePredictionAccuracy(entries: CalibrationEntry[]): number {
  if (entries.length === 0) return 0;
  const totalAbsDelta = entries.reduce(
    (sum, e) => sum + Math.abs(e.delta),
    0
  );
  return Math.round((totalAbsDelta / entries.length) * 100) / 100;
}
