"""
Analytics data models for releaseops.

Defines immutable structures for behavioral metrics, metric changes,
and version comparison results.
"""

from __future__ import annotations

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


@dataclass(frozen=True)
class BehaviorMetrics:
    """Aggregated metrics for a bundle version in an environment."""

    bundle_id: str
    bundle_version: str
    environment: str
    sample_size: int
    time_range: Tuple[str, str] = ("", "")  # (start, end) ISO timestamps
    error_rate: float = 0.0
    avg_latency_ms: float = 0.0
    p50_latency_ms: float = 0.0
    p95_latency_ms: float = 0.0
    p99_latency_ms: float = 0.0
    total_token_usage: int = 0
    avg_token_usage: float = 0.0
    tool_usage: Dict[str, int] = field(default_factory=dict)
    tool_distribution: Dict[str, float] = field(default_factory=dict)
    success_rate: float = 1.0

    def __post_init__(self) -> None:
        if self.sample_size < 0:
            raise ValueError(f"sample_size must be >= 0, got {self.sample_size}")
        if not 0.0 <= self.error_rate <= 1.0:
            raise ValueError(f"error_rate must be 0.0-1.0, got {self.error_rate}")
        if not 0.0 <= self.success_rate <= 1.0:
            raise ValueError(f"success_rate must be 0.0-1.0, got {self.success_rate}")

    def to_dict(self) -> dict:
        return {
            "bundle_id": self.bundle_id,
            "bundle_version": self.bundle_version,
            "environment": self.environment,
            "sample_size": self.sample_size,
            "time_range": list(self.time_range),
            "error_rate": self.error_rate,
            "avg_latency_ms": self.avg_latency_ms,
            "p50_latency_ms": self.p50_latency_ms,
            "p95_latency_ms": self.p95_latency_ms,
            "p99_latency_ms": self.p99_latency_ms,
            "total_token_usage": self.total_token_usage,
            "avg_token_usage": self.avg_token_usage,
            "tool_usage": dict(self.tool_usage),
            "tool_distribution": dict(self.tool_distribution),
            "success_rate": self.success_rate,
        }

    @classmethod
    def from_dict(cls, data: dict) -> BehaviorMetrics:
        time_range = data.get("time_range", ["", ""])
        return cls(
            bundle_id=data["bundle_id"],
            bundle_version=data["bundle_version"],
            environment=data["environment"],
            sample_size=data["sample_size"],
            time_range=tuple(time_range),
            error_rate=data.get("error_rate", 0.0),
            avg_latency_ms=data.get("avg_latency_ms", 0.0),
            p50_latency_ms=data.get("p50_latency_ms", 0.0),
            p95_latency_ms=data.get("p95_latency_ms", 0.0),
            p99_latency_ms=data.get("p99_latency_ms", 0.0),
            total_token_usage=data.get("total_token_usage", 0),
            avg_token_usage=data.get("avg_token_usage", 0.0),
            tool_usage=data.get("tool_usage", {}),
            tool_distribution=data.get("tool_distribution", {}),
            success_rate=data.get("success_rate", 1.0),
        )


@dataclass(frozen=True)
class MetricChange:
    """Change in a single metric between two versions."""

    metric_name: str
    baseline_value: float
    candidate_value: float
    absolute_change: float
    percent_change: float
    direction: str  # "increase" | "decrease" | "no_change"
    significance: str  # "major" | "moderate" | "minor" | "negligible"

    def __post_init__(self) -> None:
        if self.direction not in ("increase", "decrease", "no_change"):
            raise ValueError(f"Invalid direction: {self.direction}")
        if self.significance not in ("major", "moderate", "minor", "negligible"):
            raise ValueError(f"Invalid significance: {self.significance}")

    def to_dict(self) -> dict:
        return {
            "metric_name": self.metric_name,
            "baseline_value": self.baseline_value,
            "candidate_value": self.candidate_value,
            "absolute_change": self.absolute_change,
            "percent_change": self.percent_change,
            "direction": self.direction,
            "significance": self.significance,
        }

    @classmethod
    def from_dict(cls, data: dict) -> MetricChange:
        return cls(
            metric_name=data["metric_name"],
            baseline_value=data["baseline_value"],
            candidate_value=data["candidate_value"],
            absolute_change=data["absolute_change"],
            percent_change=data["percent_change"],
            direction=data["direction"],
            significance=data["significance"],
        )


@dataclass(frozen=True)
class VersionComparison:
    """Comparison between two bundle versions."""

    baseline: BehaviorMetrics
    candidate: BehaviorMetrics
    metric_changes: List[MetricChange] = field(default_factory=list)
    significant_changes: List[MetricChange] = field(default_factory=list)
    overall_assessment: str = "neutral"  # "improvement" | "regression" | "neutral" | "mixed"
    recommendation: str = ""

    def __post_init__(self) -> None:
        if self.overall_assessment not in ("improvement", "regression", "neutral", "mixed"):
            raise ValueError(f"Invalid assessment: {self.overall_assessment}")

    def get_regressions(self) -> List[MetricChange]:
        """Return metric changes that represent regressions."""
        regression_metrics = {"error_rate", "avg_latency_ms", "p95_latency_ms", "p99_latency_ms"}
        improvement_metrics = {"success_rate"}
        regressions = []
        for change in self.metric_changes:
            if change.significance == "negligible":
                continue
            if change.metric_name in regression_metrics and change.direction == "increase":
                regressions.append(change)
            elif change.metric_name in improvement_metrics and change.direction == "decrease":
                regressions.append(change)
        return regressions

    def get_improvements(self) -> List[MetricChange]:
        """Return metric changes that represent improvements."""
        regression_metrics = {"error_rate", "avg_latency_ms", "p95_latency_ms", "p99_latency_ms"}
        improvement_metrics = {"success_rate"}
        improvements = []
        for change in self.metric_changes:
            if change.significance == "negligible":
                continue
            if change.metric_name in regression_metrics and change.direction == "decrease":
                improvements.append(change)
            elif change.metric_name in improvement_metrics and change.direction == "increase":
                improvements.append(change)
        return improvements

    def to_dict(self) -> dict:
        return {
            "baseline": self.baseline.to_dict(),
            "candidate": self.candidate.to_dict(),
            "metric_changes": [c.to_dict() for c in self.metric_changes],
            "significant_changes": [c.to_dict() for c in self.significant_changes],
            "overall_assessment": self.overall_assessment,
            "recommendation": self.recommendation,
        }

    @classmethod
    def from_dict(cls, data: dict) -> VersionComparison:
        return cls(
            baseline=BehaviorMetrics.from_dict(data["baseline"]),
            candidate=BehaviorMetrics.from_dict(data["candidate"]),
            metric_changes=[MetricChange.from_dict(c) for c in data.get("metric_changes", [])],
            significant_changes=[MetricChange.from_dict(c) for c in data.get("significant_changes", [])],
            overall_assessment=data.get("overall_assessment", "neutral"),
            recommendation=data.get("recommendation", ""),
        )
