"""Adaptive baseline computation for timing analysis.

Instead of fixed thresholds, computes per-endpoint P50/P95/P99
from historical data and flags operations that exceed their own baselines.
"""

from __future__ import annotations

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

if TYPE_CHECKING:
    from autodebug.detection.transcript_analyzer import (
        Transcript,
        TranscriptInsight,
    )


@dataclass
class EndpointBaseline:
    """Statistical baseline for a single endpoint."""
    endpoint: str
    operation_type: str  # "http", "sql", "redis", "grpc"
    sample_count: int = 0
    p50: float = 0.0
    p95: float = 0.0
    p99: float = 0.0
    mean: float = 0.0
    std_dev: float = 0.0

    def to_dict(self) -> Dict[str, Any]:
        """Serialize to dict for persistence."""
        return {
            "endpoint": self.endpoint,
            "operation_type": self.operation_type,
            "sample_count": self.sample_count,
            "p50": self.p50,
            "p95": self.p95,
            "p99": self.p99,
            "mean": self.mean,
            "std_dev": self.std_dev,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> EndpointBaseline:
        """Deserialize from dict."""
        return cls(
            endpoint=data["endpoint"],
            operation_type=data["operation_type"],
            sample_count=data.get("sample_count", 0),
            p50=data.get("p50", 0.0),
            p95=data.get("p95", 0.0),
            p99=data.get("p99", 0.0),
            mean=data.get("mean", 0.0),
            std_dev=data.get("std_dev", 0.0),
        )


@dataclass
class BaselineStore:
    """Collection of historical baselines keyed by endpoint."""
    baselines: Dict[str, EndpointBaseline] = field(default_factory=dict)

    def get(self, endpoint: str) -> Optional[EndpointBaseline]:
        return self.baselines.get(endpoint)

    def add(self, endpoint: str, baseline: EndpointBaseline) -> None:
        self.baselines[endpoint] = baseline

    def to_dict(self) -> Dict[str, Any]:
        """Serialize all baselines to dict for persistence."""
        return {k: v.to_dict() for k, v in self.baselines.items()}

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> BaselineStore:
        """Deserialize from dict."""
        store = cls()
        for key, bl_data in data.items():
            store.baselines[key] = EndpointBaseline.from_dict(bl_data)
        return store


def compute_percentile(sorted_values: List[float], percentile: float) -> float:
    """Compute percentile from a sorted list of values."""
    if not sorted_values:
        return 0.0
    n = len(sorted_values)
    idx = (percentile / 100.0) * (n - 1)
    lower = int(math.floor(idx))
    upper = min(lower + 1, n - 1)
    frac = idx - lower
    return sorted_values[lower] * (1 - frac) + sorted_values[upper] * frac


def compute_baseline(durations: List[float], endpoint: str, operation_type: str) -> EndpointBaseline:
    """Compute statistical baseline from a list of durations."""
    if not durations:
        return EndpointBaseline(endpoint=endpoint, operation_type=operation_type)

    sorted_d = sorted(durations)
    n = len(sorted_d)
    mean = sum(sorted_d) / n
    variance = sum((x - mean) ** 2 for x in sorted_d) / (n - 1) if n > 1 else 0.0

    return EndpointBaseline(
        endpoint=endpoint,
        operation_type=operation_type,
        sample_count=n,
        p50=compute_percentile(sorted_d, 50),
        p95=compute_percentile(sorted_d, 95),
        p99=compute_percentile(sorted_d, 99),
        mean=mean,
        std_dev=math.sqrt(variance),
    )


def build_baselines_from_transcripts(
    transcripts: List[Transcript],
) -> BaselineStore:
    """Build baselines from a list of historical transcripts."""

    durations_by_endpoint: Dict[str, List[float]] = {}
    types_by_endpoint: Dict[str, str] = {}

    for transcript in transcripts:
        for r in transcript.http_records:
            key = f"http:{r.url_canonical}"
            durations_by_endpoint.setdefault(key, []).append(r.duration_ms)
            types_by_endpoint[key] = "http"

        for r in transcript.sql_records:
            key = f"sql:{r.sql_canonical[:100]}"
            durations_by_endpoint.setdefault(key, []).append(r.duration_ms)
            types_by_endpoint[key] = "sql"

        for r in getattr(transcript, "redis_records", []):
            key = f"redis:{r.command}:{r.key}"
            durations_by_endpoint.setdefault(key, []).append(r.duration_ms)
            types_by_endpoint[key] = "redis"

        for r in getattr(transcript, "grpc_records", []):
            key = f"grpc:{r.service}/{r.method}"
            durations_by_endpoint.setdefault(key, []).append(r.duration_ms)
            types_by_endpoint[key] = "grpc"

    store = BaselineStore()
    for endpoint, durations in durations_by_endpoint.items():
        if len(durations) >= 3:  # Need at least 3 samples for meaningful baseline
            baseline = compute_baseline(durations, endpoint, types_by_endpoint[endpoint])
            store.add(endpoint, baseline)

    return store


class AdaptiveTimingAnalyzer:
    """Analyze timing using per-endpoint adaptive baselines.

    Falls back to default thresholds when no baseline is available.
    """

    def __init__(
        self,
        baseline_store: Optional[BaselineStore] = None,
        z_score_threshold: float = 2.0,
    ):
        self.baseline_store = baseline_store or BaselineStore()
        self.z_score_threshold = z_score_threshold

    def analyze(self, transcript: Transcript) -> List[TranscriptInsight]:

        insights: List[TranscriptInsight] = []

        for r in transcript.http_records:
            key = f"http:{r.url_canonical}"
            baseline = self.baseline_store.get(key)
            if baseline and baseline.sample_count >= 3:
                insights.extend(self._check_against_baseline(
                    r, baseline, r.duration_ms, r.url_canonical
                ))

        for r in transcript.sql_records:
            key = f"sql:{r.sql_canonical[:100]}"
            baseline = self.baseline_store.get(key)
            if baseline and baseline.sample_count >= 3:
                insights.extend(self._check_against_baseline(
                    r, baseline, r.duration_ms, r.sql_canonical[:80]
                ))

        for r in getattr(transcript, "redis_records", []):
            key = f"redis:{r.command}:{r.key}"
            baseline = self.baseline_store.get(key)
            if baseline and baseline.sample_count >= 3:
                insights.extend(self._check_against_baseline(
                    r, baseline, r.duration_ms, f"{r.command} {r.key}"
                ))

        for r in getattr(transcript, "grpc_records", []):
            key = f"grpc:{r.service}/{r.method}"
            baseline = self.baseline_store.get(key)
            if baseline and baseline.sample_count >= 3:
                insights.extend(self._check_against_baseline(
                    r, baseline, r.duration_ms, f"{r.service}/{r.method}"
                ))

        return insights

    def _check_against_baseline(
        self,
        record: Any,
        baseline: EndpointBaseline,
        duration_ms: float,
        label: str,
    ) -> List[TranscriptInsight]:
        from autodebug.detection.transcript_analyzer import TranscriptInsight

        if duration_ms <= baseline.p95:
            return []

        # Compute z-score
        z_score = 0.0
        if baseline.std_dev > 0:
            z_score = (duration_ms - baseline.mean) / baseline.std_dev

        if z_score < self.z_score_threshold and duration_ms <= baseline.p99:
            return []

        if duration_ms > baseline.p99:
            severity = 4
            desc = f"{baseline.operation_type.upper()} anomaly: {label} took {duration_ms:.0f}ms (P99={baseline.p99:.0f}ms, z={z_score:.1f})"
        else:
            severity = 3
            desc = f"{baseline.operation_type.upper()} slow: {label} took {duration_ms:.0f}ms (P95={baseline.p95:.0f}ms, z={z_score:.1f})"

        return [
            TranscriptInsight(
                category=f"adaptive_{baseline.operation_type}_anomaly",
                severity=severity,
                description=desc,
                evidence={
                    "duration_ms": duration_ms,
                    "p50": round(baseline.p50, 1),
                    "p95": round(baseline.p95, 1),
                    "p99": round(baseline.p99, 1),
                    "z_score": round(z_score, 2),
                    "sample_count": baseline.sample_count,
                    "operation_type": baseline.operation_type,
                },
                related_records=[getattr(record, "seq", 0)],
                suggested_investigation=f"This {baseline.operation_type} operation is significantly slower than its historical baseline",
            )
        ]
