/**
 * Dashboard Metrics API — Key Performance Indicators
 *
 * Calculates and exposes learning metrics:
 * - Approval rate trend
 * - Average quality score trend
 * - Time-to-decision trend
 * - Prediction accuracy (calibration score)
 * - Top improvement areas
 * - Feedback volume over time
 * - Per-dimension score distributions
 */

import type {
  EnrichedFeedbackEvent,
  FeedbackStats,
  KPISnapshot,
  QualityDimension,
} from "../types.js";
import { loadFeedbackEvents } from "../storage/feedback-store.js";
import { computeStatsFromEvents } from "../storage/stats.js";
import {
  extractCalibrationEntries,
  computePredictionAccuracy,
} from "../analysis/calibration.js";
import { getDimensionDistributions } from "../analysis/dimensions.js";
import {
  loadAutonomyLevels,
  getAutonomySummary,
} from "../improvement/diminishing-review.js";
import { loadCalibrationHistory } from "../improvement/prediction.js";

// ═══════════════════════════════════════════════════════════════
// MAIN KPI COMPUTATION
// ═══════════════════════════════════════════════════════════════

/**
 * Compute all KPIs for the dashboard.
 */
export async function computeKPIs(
  dataDir: string
): Promise<KPISnapshot> {
  const events = await loadFeedbackEvents(dataDir, 30);
  const stats = computeStatsFromEvents(events);
  const calibrationEntries = extractCalibrationEntries(events);
  const predictionAccuracy = computePredictionAccuracy(calibrationEntries);
  const dimensionDistributions = getDimensionDistributions(events);

  // Top improvement areas: dimensions that are declining or below threshold
  const topImprovementAreas: string[] = [];
  for (const [dim, data] of Object.entries(stats.byDimension)) {
    if (data.trend < -0.3) {
      topImprovementAreas.push(`${dim} (declining: ${data.trend > 0 ? "+" : ""}${data.trend})`);
    } else if (data.avg < 6) {
      topImprovementAreas.push(`${dim} (avg: ${data.avg})`);
    }
  }

  return {
    timestamp: new Date().toISOString(),
    approvalRate: stats.approvalRate,
    avgQualityScore: stats.avgQualityScore,
    avgTimeToDecisionMs: stats.avgTimeToDecisionMs,
    predictionAccuracy,
    topImprovementAreas,
    feedbackVolume: stats.totalEvents,
    dimensionDistributions,
  };
}

/**
 * Compute full feedback stats for the API (§6.4 FeedbackStats).
 */
export async function computeFeedbackStats(
  dataDir: string
): Promise<FeedbackStats> {
  const events30d = await loadFeedbackEvents(dataDir, 30);
  const events60d = await loadFeedbackEvents(dataDir, 60);
  const stats30d = computeStatsFromEvents(events30d);

  // Compute trends by comparing 30d to previous 30d
  const previous30d = events60d.filter((e) => {
    const t = new Date(e.timestamp).getTime();
    const now = Date.now();
    return t < now - 30 * 24 * 60 * 60 * 1000;
  });
  const prevStats = computeStatsFromEvents(previous30d);

  // Calibration
  const calibrationEntries = extractCalibrationEntries(events30d);
  const predictionAccuracy = computePredictionAccuracy(calibrationEntries);

  // Auto-approve rate (events processed without human review)
  // For now, this is tracked via autonomy levels
  const autonomyLevels = loadAutonomyLevels(dataDir);
  const autoApprovable = Object.values(autonomyLevels).filter(
    (l) => l.currentTier >= 3
  );
  const autoApproveRate =
    Object.keys(autonomyLevels).length > 0
      ? autoApprovable.length / Object.keys(autonomyLevels).length
      : 0;

  // Determine trends
  const approvalTrend = computeTrend(
    prevStats.approvalRate,
    stats30d.approvalRate
  );
  const qualityTrend = computeTrend(
    prevStats.avgQualityScore,
    stats30d.avgQualityScore
  );
  const speedTrend = computeInverseTrend(
    prevStats.avgTimeToDecisionMs,
    stats30d.avgTimeToDecisionMs
  ); // Lower is better

  return {
    totalEvents: stats30d.totalEvents + prevStats.totalEvents,
    last30Days: {
      approvalRate: stats30d.approvalRate,
      avgQualityScore: stats30d.avgQualityScore,
      avgTimeToDecisionMs: stats30d.avgTimeToDecisionMs,
      predictionAccuracy,
      autoApproveRate,
      feedbackVolume: stats30d.totalEvents,
    },
    trends: {
      approvalRate: approvalTrend,
      qualityScore: qualityTrend,
      decisionSpeed: speedTrend,
    },
    activeRules: 0, // Would be loaded from feedback-patterns.md
    autonomyLevels: Object.fromEntries(
      Object.entries(autonomyLevels).map(([k, v]) => [k, v.currentTier])
    ),
  };
}

// ═══════════════════════════════════════════════════════════════
// TREND COMPUTATION
// ═══════════════════════════════════════════════════════════════

/**
 * Compute trend from historical weekly data.
 */
export function computeWeeklyTrends(
  dataDir: string,
  events: EnrichedFeedbackEvent[]
): {
  approvalRateByWeek: Array<{ week: string; rate: number }>;
  avgScoreByWeek: Array<{ week: string; score: number }>;
  volumeByWeek: Array<{ week: string; count: number }>;
  timeToDecisionByWeek: Array<{ week: string; avgMs: number }>;
} {
  // Group by week
  const weeks = new Map<
    string,
    {
      events: EnrichedFeedbackEvent[];
      approved: number;
      total: number;
      scores: number[];
      times: number[];
    }
  >();

  for (const event of events) {
    const weekKey = getISOWeek(new Date(event.timestamp));
    if (!weeks.has(weekKey)) {
      weeks.set(weekKey, {
        events: [],
        approved: 0,
        total: 0,
        scores: [],
        times: [],
      });
    }
    const week = weeks.get(weekKey)!;
    week.events.push(event);

    if (event.feedback.decision) {
      week.total++;
      if (event.feedback.decision.decision === "approved") {
        week.approved++;
      }
    }

    for (const s of event.feedback.scores ?? []) {
      week.scores.push(s.score);
    }

    if (event.meta.timeToDecisionMs > 0) {
      week.times.push(event.meta.timeToDecisionMs);
    }
  }

  const sortedWeeks = [...weeks.entries()].sort(([a], [b]) =>
    a.localeCompare(b)
  );

  return {
    approvalRateByWeek: sortedWeeks.map(([week, data]) => ({
      week,
      rate: data.total > 0 ? data.approved / data.total : 0,
    })),
    avgScoreByWeek: sortedWeeks.map(([week, data]) => ({
      week,
      score:
        data.scores.length > 0
          ? data.scores.reduce((a, b) => a + b, 0) / data.scores.length
          : 0,
    })),
    volumeByWeek: sortedWeeks.map(([week, data]) => ({
      week,
      count: data.events.length,
    })),
    timeToDecisionByWeek: sortedWeeks.map(([week, data]) => ({
      week,
      avgMs:
        data.times.length > 0
          ? data.times.reduce((a, b) => a + b, 0) / data.times.length
          : 0,
    })),
  };
}

// ═══════════════════════════════════════════════════════════════
// ALERTING
// ═══════════════════════════════════════════════════════════════

export interface Alert {
  level: "info" | "warning" | "critical";
  metric: string;
  message: string;
  value: number;
  threshold: number;
}

/**
 * Check KPIs against alerting thresholds.
 */
export function checkAlerts(snapshot: KPISnapshot): Alert[] {
  const alerts: Alert[] = [];

  // Approval rate below 70%
  if (snapshot.approvalRate < 0.7) {
    alerts.push({
      level: snapshot.approvalRate < 0.5 ? "critical" : "warning",
      metric: "approvalRate",
      message: `Approval rate is ${(snapshot.approvalRate * 100).toFixed(0)}%`,
      value: snapshot.approvalRate,
      threshold: 0.7,
    });
  }

  // Prediction accuracy above ±2.0
  if (snapshot.predictionAccuracy > 2.0) {
    alerts.push({
      level: "warning",
      metric: "predictionAccuracy",
      message: `Prediction accuracy is ±${snapshot.predictionAccuracy.toFixed(1)} — recalibration recommended`,
      value: snapshot.predictionAccuracy,
      threshold: 2.0,
    });
  }

  // Average quality score below 6.0
  if (snapshot.avgQualityScore < 6.0 && snapshot.avgQualityScore > 0) {
    alerts.push({
      level: "warning",
      metric: "avgQualityScore",
      message: `Average quality score is ${snapshot.avgQualityScore.toFixed(1)}`,
      value: snapshot.avgQualityScore,
      threshold: 6.0,
    });
  }

  // No feedback for 48+ hours
  if (snapshot.feedbackVolume === 0) {
    alerts.push({
      level: "info",
      metric: "feedbackVolume",
      message: "No feedback events in the last 30 days",
      value: 0,
      threshold: 1,
    });
  }

  return alerts;
}

// ═══════════════════════════════════════════════════════════════
// HELPERS
// ═══════════════════════════════════════════════════════════════

function computeTrend(
  previous: number,
  current: number
): "improving" | "stable" | "declining" {
  if (previous === 0 && current === 0) return "stable";
  const delta = current - previous;
  if (delta > 0.05) return "improving";
  if (delta < -0.05) return "declining";
  return "stable";
}

function computeInverseTrend(
  previous: number,
  current: number
): "improving" | "stable" | "declining" {
  // For metrics where lower is better (e.g., time-to-decision)
  if (previous === 0 && current === 0) return "stable";
  const delta = current - previous;
  if (delta < -previous * 0.1) return "improving"; // 10%+ decrease is good
  if (delta > previous * 0.1) return "declining"; // 10%+ increase is bad
  return "stable";
}

function getISOWeek(date: Date): string {
  const d = new Date(date);
  d.setHours(0, 0, 0, 0);
  d.setDate(d.getDate() + 3 - ((d.getDay() + 6) % 7));
  const week1 = new Date(d.getFullYear(), 0, 4);
  const weekNum =
    1 +
    Math.round(
      ((d.getTime() - week1.getTime()) / 86400000 -
        3 +
        ((week1.getDay() + 6) % 7)) /
        7
    );
  return `${d.getFullYear()}-W${String(weekNum).padStart(2, "0")}`;
}
