"""Learning metrics and dashboard for monitoring fix quality.

Tracks acceptance rates, strategy performance, and learning progress.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Dict, List, Optional

if TYPE_CHECKING:
    from autodebug.learning.active_learner import ActiveLearner
    from autodebug.learning.feedback_tracker import FeedbackTracker


@dataclass
class StrategyMetrics:
    """Metrics for a fix strategy."""

    strategy: str
    exception_type: str
    acceptance_rate: float
    total_uses: int
    successes: int
    failures: int
    learned_weight: float
    confidence: float
    trend: str  # "improving", "declining", "stable"

    @property
    def is_effective(self) -> bool:
        """Whether this strategy is considered effective."""
        return self.acceptance_rate >= 0.5 and self.confidence >= 0.3


@dataclass
class ExceptionTypeMetrics:
    """Metrics for an exception type."""

    exception_type: str
    total_fixes: int
    accepted: int
    rejected: int
    modified: int
    acceptance_rate: float
    top_strategy: Optional[str]
    top_strategy_rate: float


@dataclass
class CalibrationBucket:
    """A single bucket in calibration curve."""

    predicted_avg: float
    actual_avg: float
    count: int


@dataclass
class CalibrationMetrics:
    """Model calibration tracking using Expected Calibration Error."""

    num_bins: int = 10
    predictions: List[float] = field(default_factory=list)
    actuals: List[bool] = field(default_factory=list)

    def record(self, predicted: float, actual: bool) -> None:
        """Record a prediction-outcome pair."""
        self.predictions.append(predicted)
        self.actuals.append(actual)
        # Keep bounded
        if len(self.predictions) > 5000:
            self.predictions = self.predictions[-2500:]
            self.actuals = self.actuals[-2500:]

    def get_ece(self) -> float:
        """Compute Expected Calibration Error.

        Returns:
            ECE value (0 = perfectly calibrated, 1 = worst)
        """
        if len(self.predictions) < 10:
            return 0.0

        n = len(self.predictions)
        ece = 0.0

        for bucket in self.get_calibration_curve():
            weight = bucket.count / n
            ece += weight * abs(bucket.actual_avg - bucket.predicted_avg)

        return ece

    def get_calibration_curve(self) -> List[CalibrationBucket]:
        """Get calibration curve as list of buckets.

        Returns:
            List of (predicted_avg, actual_avg, count) per bin
        """
        if not self.predictions:
            return []

        buckets: List[CalibrationBucket] = []
        bin_size = 1.0 / self.num_bins

        for i in range(self.num_bins):
            lo = i * bin_size
            hi = (i + 1) * bin_size

            bin_preds = []
            bin_actuals = []
            for p, a in zip(self.predictions, self.actuals):
                if lo <= p < hi or (i == self.num_bins - 1 and p == hi):
                    bin_preds.append(p)
                    bin_actuals.append(float(a))

            if bin_preds:
                buckets.append(CalibrationBucket(
                    predicted_avg=sum(bin_preds) / len(bin_preds),
                    actual_avg=sum(bin_actuals) / len(bin_actuals),
                    count=len(bin_preds),
                ))

        return buckets


class LearningMetrics:
    """Track and visualize learning metrics.

    Provides insights into fix acceptance rates, strategy
    performance, and learning progress over time.
    """

    def __init__(
        self,
        learner: Optional[ActiveLearner] = None,
        tracker: Optional[FeedbackTracker] = None,
    ):
        """Initialize the metrics dashboard.

        Args:
            learner: ActiveLearner for model metrics
            tracker: FeedbackTracker for event metrics
        """
        self._learner = learner
        self._tracker = tracker
        self.calibration = CalibrationMetrics()

    @property
    def learner(self) -> Optional[ActiveLearner]:
        """Get or create the active learner."""
        if self._learner is None:
            try:
                from autodebug.learning.active_learner import ActiveLearner

                self._learner = ActiveLearner()
            except ImportError:
                pass
        return self._learner

    @property
    def tracker(self) -> Optional[FeedbackTracker]:
        """Get or create the feedback tracker."""
        if self._tracker is None:
            try:
                from autodebug.learning.feedback_tracker import FeedbackTracker

                self._tracker = FeedbackTracker(self.learner)
            except ImportError:
                pass
        return self._tracker

    def get_overall_acceptance_rate(self) -> float:
        """Get overall fix acceptance rate.

        Returns:
            Acceptance rate (0-1)
        """
        if not self.learner:
            return 0.0

        counts = self.learner.feedback_counts
        total_decided = counts.get("accepted", 0) + counts.get("rejected", 0)
        if total_decided == 0:
            return 0.0

        return counts.get("accepted", 0) / total_decided

    def get_acceptance_by_exception_type(self) -> Dict[str, float]:
        """Get acceptance rate by exception type.

        Returns:
            Dict mapping exception type to acceptance rate
        """
        if not self.learner or not self.learner.thompson:
            return {}

        all_stats = self.learner.thompson.get_all_stats()

        # Group by exception type
        by_type: Dict[str, Dict[str, int]] = {}
        for key, stats in all_stats.items():
            exc_type = key.split(":")[0] if ":" in key else key
            if exc_type not in by_type:
                by_type[exc_type] = {"successes": 0, "failures": 0}
            by_type[exc_type]["successes"] += stats.get("successes", 0)
            by_type[exc_type]["failures"] += stats.get("failures", 0)

        # Calculate rates
        rates = {}
        for exc_type, counts in by_type.items():
            total = counts["successes"] + counts["failures"]
            if total > 0:
                rates[exc_type] = counts["successes"] / total

        return rates

    def get_acceptance_by_strategy(self) -> Dict[str, float]:
        """Get acceptance rate by strategy.

        Returns:
            Dict mapping strategy to acceptance rate
        """
        if not self.learner or not self.learner.thompson:
            return {}

        all_stats = self.learner.thompson.get_all_stats()

        # Group by strategy
        by_strategy: Dict[str, Dict[str, int]] = {}
        for key, stats in all_stats.items():
            parts = key.split(":")
            strategy = parts[1] if len(parts) > 1 else key
            if strategy not in by_strategy:
                by_strategy[strategy] = {"successes": 0, "failures": 0}
            by_strategy[strategy]["successes"] += stats.get("successes", 0)
            by_strategy[strategy]["failures"] += stats.get("failures", 0)

        # Calculate rates
        rates = {}
        for strategy, counts in by_strategy.items():
            total = counts["successes"] + counts["failures"]
            if total > 0:
                rates[strategy] = counts["successes"] / total

        return rates

    def get_strategy_metrics(
        self,
        exception_type: Optional[str] = None,
    ) -> List[StrategyMetrics]:
        """Get comprehensive strategy metrics.

        Args:
            exception_type: Filter by exception type

        Returns:
            List of StrategyMetrics
        """
        if not self.learner or not self.learner.thompson:
            return []

        all_stats = self.learner.thompson.get_all_stats()
        metrics = []

        for key, stats in all_stats.items():
            parts = key.split(":")
            if len(parts) != 2:
                continue

            exc_type, strategy = parts

            # Apply filter
            if exception_type and exc_type != exception_type:
                continue

            successes = stats.get("successes", 0)
            failures = stats.get("failures", 0)
            total = successes + failures

            if total == 0:
                continue

            acceptance_rate = successes / total
            confidence = stats.get("confidence", 0.0)

            # Get learned weight from Thompson Sampling
            learned_weight = self.learner.get_expected_success_rate(exc_type, strategy)

            # Determine trend (simplified - would need historical data)
            if confidence < 0.3:
                trend = "uncertain"
            elif acceptance_rate >= 0.7:
                trend = "stable"  # Good and stable
            elif acceptance_rate >= 0.5:
                trend = "improving"
            else:
                trend = "declining"

            metrics.append(
                StrategyMetrics(
                    strategy=strategy,
                    exception_type=exc_type,
                    acceptance_rate=acceptance_rate,
                    total_uses=total,
                    successes=successes,
                    failures=failures,
                    learned_weight=learned_weight,
                    confidence=confidence,
                    trend=trend,
                )
            )

        # Sort by acceptance rate
        return sorted(metrics, key=lambda m: m.acceptance_rate, reverse=True)

    def get_exception_type_metrics(self) -> List[ExceptionTypeMetrics]:
        """Get metrics grouped by exception type.

        Returns:
            List of ExceptionTypeMetrics
        """
        if not self.learner or not self.learner.thompson:
            return []

        all_stats = self.learner.thompson.get_all_stats()

        # Group by exception type
        by_type: Dict[str, Dict[str, Any]] = {}
        for key, stats in all_stats.items():
            parts = key.split(":")
            if len(parts) != 2:
                continue

            exc_type, strategy = parts
            successes = stats.get("successes", 0)
            failures = stats.get("failures", 0)

            if exc_type not in by_type:
                by_type[exc_type] = {
                    "total": 0,
                    "accepted": 0,
                    "rejected": 0,
                    "strategies": {},
                }

            by_type[exc_type]["total"] += successes + failures
            by_type[exc_type]["accepted"] += successes
            by_type[exc_type]["rejected"] += failures

            # Track strategy performance
            by_type[exc_type]["strategies"][strategy] = {
                "successes": successes,
                "failures": failures,
            }

        # Build metrics
        metrics = []
        for exc_type, data in by_type.items():
            total = data["total"]
            if total == 0:
                continue

            acceptance_rate = data["accepted"] / total

            # Find top strategy
            top_strategy = None
            top_rate = 0.0
            for strategy, s_data in data["strategies"].items():
                s_total = s_data["successes"] + s_data["failures"]
                if s_total > 0:
                    s_rate = s_data["successes"] / s_total
                    if s_rate > top_rate:
                        top_rate = s_rate
                        top_strategy = strategy

            metrics.append(
                ExceptionTypeMetrics(
                    exception_type=exc_type,
                    total_fixes=total,
                    accepted=data["accepted"],
                    rejected=data["rejected"],
                    modified=0,  # Would need separate tracking
                    acceptance_rate=acceptance_rate,
                    top_strategy=top_strategy,
                    top_strategy_rate=top_rate,
                )
            )

        return sorted(metrics, key=lambda m: m.total_fixes, reverse=True)

    def get_model_performance(self) -> Dict[str, Any]:
        """Get online model performance metrics.

        Returns:
            Dict with model performance stats
        """
        result: Dict[str, Any] = {}

        if self.learner:
            result["predictor_accuracy"] = self.learner.predictor.get_accuracy()
            result["predictor_samples"] = self.learner.predictor.sample_count
            result["predictor_available"] = True
            result["exploration_rate"] = self._get_exploration_rate()

        return result

    def _get_exploration_rate(self) -> float:
        """Calculate how much Thompson Sampling is exploring.

        Returns:
            Exploration rate (0-1)
        """
        if not self.learner or not self.learner.thompson:
            return 1.0

        all_stats = self.learner.thompson.get_all_stats()
        if not all_stats:
            return 1.0

        # Count strategies with low confidence
        low_confidence = sum(
            1 for stats in all_stats.values() if stats.get("confidence", 0) < 0.3
        )

        return low_confidence / len(all_stats)

    def _compute_trend(
        self,
        recent_window: int = 50,
        historical_window: int = 200,
    ) -> str:
        """Compute trend by comparing recent vs historical acceptance rate.

        Args:
            recent_window: Number of recent outcomes to consider
            historical_window: Number of historical outcomes

        Returns:
            "improving", "declining", or "stable"
        """
        if not self.learner:
            return "stable"

        monitor = getattr(self.learner, 'monitor', None)
        if not monitor or len(monitor._recent_outcomes) < 10:
            return "stable"

        outcomes = monitor._recent_outcomes
        recent = outcomes[-recent_window:]
        historical = outcomes[-historical_window:]

        if not recent or not historical:
            return "stable"

        recent_rate = sum(recent) / len(recent)
        historical_rate = sum(historical) / len(historical)

        if recent_rate > historical_rate + 0.05:
            return "improving"
        elif recent_rate < historical_rate - 0.05:
            return "declining"
        return "stable"

    def get_calibration_metrics(self) -> Dict[str, Any]:
        """Get model calibration metrics.

        Returns:
            Dict with ECE and calibration curve
        """
        return {
            "ece": self.calibration.get_ece(),
            "curve": [
                {
                    "predicted": b.predicted_avg,
                    "actual": b.actual_avg,
                    "count": b.count,
                }
                for b in self.calibration.get_calibration_curve()
            ],
            "sample_count": len(self.calibration.predictions),
        }

    def get_cross_org_impact(self) -> Dict[str, Any]:
        """Measure impact of cross-org pattern knowledge.

        Returns:
            Dict with cross-org impact metrics
        """
        if not self.learner or not self.learner.cross_org:
            return {"enabled": False}

        stats = self.learner.get_stats()
        return {
            "enabled": True,
            "org_id": stats.get("cross_org_org_id"),
            "monitor_acceptance_rate": stats.get("monitor_acceptance_rate", 0.0),
            "predictor_accuracy": stats.get("predictor_accuracy", 0.0),
            "predictor_rolling_accuracy": stats.get("predictor_rolling_accuracy", 0.0),
        }

    def get_dashboard_summary(self) -> Dict[str, Any]:
        """Get complete dashboard summary.

        Returns:
            Dict with all dashboard metrics (API-ready)
        """
        summary: Dict[str, Any] = {
            "overall_acceptance_rate": self.get_overall_acceptance_rate(),
            "trend": self._compute_trend(),
        }

        if self.learner:
            summary["learner_stats"] = self.learner.get_stats()

        if self.tracker:
            summary["tracker_stats"] = self.tracker.get_stats()

        summary["model_performance"] = self.get_model_performance()
        summary["calibration"] = self.get_calibration_metrics()
        summary["cross_org_impact"] = self.get_cross_org_impact()
        summary["top_strategies"] = [
            {
                "strategy": m.strategy,
                "exception_type": m.exception_type,
                "acceptance_rate": m.acceptance_rate,
                "total_uses": m.total_uses,
                "trend": m.trend,
            }
            for m in self.get_strategy_metrics()[:10]
        ]

        return summary

    def format_report(self) -> str:
        """Format metrics as a human-readable report.

        Returns:
            Formatted report string
        """
        lines = [
            "Learning Metrics Report",
            "=" * 50,
            "",
        ]

        # Overall stats
        overall_rate = self.get_overall_acceptance_rate()
        lines.append(f"Overall Acceptance Rate: {overall_rate:.1%}")

        if self.learner:
            stats = self.learner.get_stats()
            lines.append(f"Total Feedback Signals: {sum(stats.get('feedback_counts', {}).values())}")
            lines.append(f"Stored Examples: {stats.get('stored_examples', 0)}")
            lines.append(f"Active Alerts: {stats.get('active_alerts', 0)}")

        # Model performance
        model_perf = self.get_model_performance()
        if model_perf:
            lines.append("")
            lines.append("Model Performance:")
            if model_perf.get("predictor_available"):
                lines.append(f"  Predictor Accuracy: {model_perf.get('predictor_accuracy', 0):.1%}")
                lines.append(f"  Predictor Samples: {model_perf.get('predictor_samples', 0)}")
            else:
                lines.append("  Predictor: Not available (River not installed)")
            lines.append(f"  Exploration Rate: {model_perf.get('exploration_rate', 1.0):.1%}")

        # Top strategies
        strategies = self.get_strategy_metrics()[:5]
        if strategies:
            lines.append("")
            lines.append("Top Strategies:")
            for m in strategies:
                lines.append(
                    f"  {m.strategy} ({m.exception_type}): "
                    f"{m.acceptance_rate:.1%} ({m.total_uses} uses)"
                )

        # Exception types
        exc_metrics = self.get_exception_type_metrics()[:5]
        if exc_metrics:
            lines.append("")
            lines.append("By Exception Type:")
            for m in exc_metrics:
                lines.append(
                    f"  {m.exception_type}: {m.acceptance_rate:.1%} "
                    f"({m.total_fixes} fixes, best: {m.top_strategy})"
                )

        return "\n".join(lines)
