"""Timing analysis for performance issue detection.

Analyzes captured I/O timing data to detect slow operations,
timeout risks, and performance-related failures.
"""

from __future__ import annotations

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

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


@dataclass
class TimingIssue:
    """A detected timing/performance issue."""

    category: str  # "slow_query", "slow_request", "timeout_risk", "latency_spike"
    severity: int  # 1-5
    operation_type: str  # "http", "sql", "redis"
    duration_ms: float
    threshold_ms: float
    description: str
    suggestion: str
    record: Optional[Any] = None
    evidence: Dict[str, Any] = field(default_factory=dict)

    def __str__(self) -> str:
        return f"[{self.category}] {self.description} ({self.duration_ms:.0f}ms)"


@dataclass
class TimingThresholds:
    """Configurable thresholds for timing analysis."""

    slow: float  # Threshold for "slow" classification
    very_slow: float  # Threshold for "very slow" classification
    timeout_risk: float  # Threshold for timeout risk warning

    @classmethod
    def for_http(cls) -> TimingThresholds:
        """Default thresholds for HTTP requests."""
        return cls(slow=1000, very_slow=5000, timeout_risk=10000)

    @classmethod
    def for_sql(cls) -> TimingThresholds:
        """Default thresholds for SQL queries."""
        return cls(slow=100, very_slow=500, timeout_risk=5000)

    @classmethod
    def for_redis(cls) -> TimingThresholds:
        """Default thresholds for Redis commands."""
        return cls(slow=10, very_slow=50, timeout_risk=1000)


class TimingAnalyzer:
    """Analyze timing data from I/O transcripts.

    Detects:
    - Slow HTTP requests
    - Slow SQL queries
    - Slow Redis commands
    - Latency spikes
    - Cumulative slowness
    - Timeout risks
    """

    # Default thresholds (in milliseconds)
    DEFAULT_THRESHOLDS = {
        "http": TimingThresholds.for_http(),
        "sql": TimingThresholds.for_sql(),
        "redis": TimingThresholds.for_redis(),
    }

    def __init__(
        self,
        thresholds: Optional[Dict[str, TimingThresholds]] = None,
        cumulative_http_threshold: float = 10000,
        cumulative_sql_threshold: float = 5000,
        spike_multiplier: float = 3.0,
        min_spike_samples: int = 4,
    ):
        """Initialize the timing analyzer.

        Args:
            thresholds: Custom thresholds by operation type
            cumulative_http_threshold: Threshold for total HTTP time (ms)
            cumulative_sql_threshold: Threshold for total SQL time (ms)
            spike_multiplier: Multiplier for detecting latency spikes
            min_spike_samples: Minimum samples required for spike detection
        """
        self.thresholds = thresholds or self.DEFAULT_THRESHOLDS.copy()
        self.cumulative_http_threshold = cumulative_http_threshold
        self.cumulative_sql_threshold = cumulative_sql_threshold
        self.spike_multiplier = spike_multiplier
        # Guard against pathological values while keeping detection useful.
        self.min_spike_samples = max(3, min_spike_samples)

    def analyze(self, transcript: Transcript) -> List[TimingIssue]:
        """Analyze transcript for timing issues.

        Args:
            transcript: I/O transcript with timing data

        Returns:
            List of detected timing issues
        """
        issues: List[TimingIssue] = []

        # Analyze HTTP timing
        for record in transcript.http_records:
            duration = getattr(record, "duration_ms", None)
            if duration is not None and duration > 0:
                issues.extend(self._check_http_timing(record))

        # Analyze SQL timing
        for record in transcript.sql_records:
            duration = getattr(record, "duration_ms", None)
            if duration is not None and duration > 0:
                issues.extend(self._check_sql_timing(record))

        # Analyze Redis timing if present
        redis_records = getattr(transcript, "redis_records", [])
        for record in redis_records:
            duration = getattr(record, "duration_ms", None)
            if duration is not None and duration > 0:
                issues.extend(self._check_redis_timing(record))

        # Detect patterns
        issues.extend(self._detect_latency_spikes(transcript))
        issues.extend(self._detect_cumulative_slowness(transcript))

        return issues

    def _check_http_timing(self, record: HTTPRecord) -> List[TimingIssue]:
        """Check HTTP request timing.

        Args:
            record: HTTP record with duration_ms

        Returns:
            List of timing issues for this request
        """
        thresholds = self.thresholds.get("http", TimingThresholds.for_http())
        duration = record.duration_ms
        url = getattr(record, "url_canonical", "unknown")
        issues: List[TimingIssue] = []

        if duration >= thresholds.timeout_risk:
            issues.append(
                TimingIssue(
                    category="timeout_risk",
                    severity=5,
                    operation_type="http",
                    duration_ms=duration,
                    threshold_ms=thresholds.timeout_risk,
                    description=f"HTTP request to {url} took {duration:.0f}ms - high timeout risk",
                    suggestion="Consider increasing timeout or adding retry logic with backoff",
                    record=record,
                    evidence={"url": url, "status": getattr(record, "status", None)},
                )
            )
        elif duration >= thresholds.very_slow:
            issues.append(
                TimingIssue(
                    category="very_slow_request",
                    severity=4,
                    operation_type="http",
                    duration_ms=duration,
                    threshold_ms=thresholds.very_slow,
                    description=f"HTTP request to {url} took {duration:.0f}ms",
                    suggestion="Investigate API performance or add caching",
                    record=record,
                    evidence={"url": url},
                )
            )
        elif duration >= thresholds.slow:
            issues.append(
                TimingIssue(
                    category="slow_request",
                    severity=2,
                    operation_type="http",
                    duration_ms=duration,
                    threshold_ms=thresholds.slow,
                    description=f"HTTP request to {url} took {duration:.0f}ms",
                    suggestion="Monitor this endpoint for performance degradation",
                    record=record,
                    evidence={"url": url},
                )
            )

        return issues

    def _check_sql_timing(self, record: SQLRecord) -> List[TimingIssue]:
        """Check SQL query timing.

        Args:
            record: SQL record with duration_ms

        Returns:
            List of timing issues for this query
        """
        thresholds = self.thresholds.get("sql", TimingThresholds.for_sql())
        duration = record.duration_ms
        query = getattr(record, "sql_canonical", "")
        query_preview = query[:100] + "..." if len(query) > 100 else query
        issues: List[TimingIssue] = []

        if duration >= thresholds.timeout_risk:
            issues.append(
                TimingIssue(
                    category="timeout_risk",
                    severity=5,
                    operation_type="sql",
                    duration_ms=duration,
                    threshold_ms=thresholds.timeout_risk,
                    description=f"SQL query took {duration:.0f}ms - timeout risk",
                    suggestion="Consider query timeout or breaking into smaller queries",
                    record=record,
                    evidence={"query": query_preview},
                )
            )
        elif duration >= thresholds.very_slow:
            issues.append(
                TimingIssue(
                    category="slow_query",
                    severity=4,
                    operation_type="sql",
                    duration_ms=duration,
                    threshold_ms=thresholds.very_slow,
                    description=f"SQL query took {duration:.0f}ms: {query_preview}",
                    suggestion="Add index or optimize query. Run EXPLAIN to analyze.",
                    record=record,
                    evidence={"query": query_preview},
                )
            )
        elif duration >= thresholds.slow:
            issues.append(
                TimingIssue(
                    category="slow_query",
                    severity=2,
                    operation_type="sql",
                    duration_ms=duration,
                    threshold_ms=thresholds.slow,
                    description=f"SQL query took {duration:.0f}ms",
                    suggestion="Consider adding an index if this query runs frequently",
                    record=record,
                    evidence={"query": query_preview},
                )
            )

        return issues

    def _check_redis_timing(self, record: Any) -> List[TimingIssue]:
        """Check Redis command timing.

        Args:
            record: Redis record with duration_ms

        Returns:
            List of timing issues for this command
        """
        thresholds = self.thresholds.get("redis", TimingThresholds.for_redis())
        duration = getattr(record, "duration_ms", 0)
        command = getattr(record, "command", "unknown")
        issues: List[TimingIssue] = []

        if duration >= thresholds.timeout_risk:
            issues.append(
                TimingIssue(
                    category="timeout_risk",
                    severity=5,
                    operation_type="redis",
                    duration_ms=duration,
                    threshold_ms=thresholds.timeout_risk,
                    description=f"Redis {command} took {duration:.0f}ms - timeout risk",
                    suggestion="Check Redis server or consider timeout/circuit breaker",
                    record=record,
                    evidence={"command": command},
                )
            )
        elif duration >= thresholds.very_slow:
            issues.append(
                TimingIssue(
                    category="slow_redis",
                    severity=4,
                    operation_type="redis",
                    duration_ms=duration,
                    threshold_ms=thresholds.very_slow,
                    description=f"Redis {command} took {duration:.0f}ms",
                    suggestion="Check Redis server load or network latency",
                    record=record,
                    evidence={"command": command},
                )
            )
        elif duration >= thresholds.slow:
            issues.append(
                TimingIssue(
                    category="slow_redis",
                    severity=2,
                    operation_type="redis",
                    duration_ms=duration,
                    threshold_ms=thresholds.slow,
                    description=f"Redis {command} took {duration:.0f}ms",
                    suggestion="Monitor this command for performance degradation",
                    record=record,
                    evidence={"command": command},
                )
            )

        return issues

    def _detect_latency_spikes(self, transcript: Transcript) -> List[TimingIssue]:
        """Detect sudden latency increases.

        Args:
            transcript: I/O transcript

        Returns:
            List of latency spike issues
        """
        issues: List[TimingIssue] = []

        # Group HTTP requests by URL
        http_by_url: Dict[str, List[float]] = {}
        for record in transcript.http_records:
            duration = getattr(record, "duration_ms", None)
            if duration is not None and duration > 0:
                url = getattr(record, "url_canonical", "unknown")
                if url not in http_by_url:
                    http_by_url[url] = []
                http_by_url[url].append(duration)

        # Check for spikes (> spike_multiplier x median baseline)
        for url, durations in http_by_url.items():
            if len(durations) >= self.min_spike_samples:
                sorted_d = sorted(durations)
                max_dur = sorted_d[-1]
                median = self._median(sorted_d)
                mad = self._median_abs_deviation(sorted_d, median)
                if median <= 0:
                    continue
                robust_threshold = median + (3 * mad if mad > 0 else 0)
                if max_dur > median * self.spike_multiplier and max_dur > robust_threshold:
                    issues.append(
                        TimingIssue(
                            category="latency_spike",
                            severity=3,
                            operation_type="http",
                            duration_ms=max_dur,
                            threshold_ms=median * self.spike_multiplier,
                            description=f"Latency spike for {url}: {max_dur:.0f}ms vs median {median:.0f}ms",
                            suggestion="Investigate intermittent performance issues or network problems",
                            record=None,
                            evidence={
                                "url": url,
                                "max_ms": max_dur,
                                "median_ms": median,
                                "mad_ms": mad,
                                "spike_ratio": max_dur / median,
                            },
                        )
                    )

        # Also check SQL queries
        sql_by_pattern: Dict[str, List[float]] = {}
        for record in transcript.sql_records:
            duration = getattr(record, "duration_ms", None)
            if duration is not None and duration > 0:
                # Normalize query for grouping
                query = getattr(record, "sql_canonical", "")
                pattern = self._normalize_query_pattern(query)
                if pattern not in sql_by_pattern:
                    sql_by_pattern[pattern] = []
                sql_by_pattern[pattern].append(duration)

        for pattern, durations in sql_by_pattern.items():
            if len(durations) >= self.min_spike_samples:
                sorted_d = sorted(durations)
                max_dur = sorted_d[-1]
                median = self._median(sorted_d)
                mad = self._median_abs_deviation(sorted_d, median)
                if median <= 0:
                    continue
                robust_threshold = median + (3 * mad if mad > 0 else 0)
                if max_dur > median * self.spike_multiplier and max_dur > robust_threshold:
                    issues.append(
                        TimingIssue(
                            category="latency_spike",
                            severity=3,
                            operation_type="sql",
                            duration_ms=max_dur,
                            threshold_ms=median * self.spike_multiplier,
                            description=f"SQL latency spike: {max_dur:.0f}ms vs median {median:.0f}ms",
                            suggestion="Check for lock contention or data growth",
                            record=None,
                            evidence={
                                "query_pattern": pattern[:50],
                                "max_ms": max_dur,
                                "median_ms": median,
                                "mad_ms": mad,
                                "spike_ratio": max_dur / median if median > 0 else 0,
                            },
                        )
                    )

        return issues

    def _detect_cumulative_slowness(self, transcript: Transcript) -> List[TimingIssue]:
        """Detect when many operations add up to significant time.

        Args:
            transcript: I/O transcript

        Returns:
            List of cumulative slowness issues
        """
        issues: List[TimingIssue] = []

        # Calculate total HTTP time
        total_http = sum(
            getattr(r, "duration_ms", 0) or 0
            for r in transcript.http_records
        )

        # Calculate total SQL time
        total_sql = sum(
            getattr(r, "duration_ms", 0) or 0
            for r in transcript.sql_records
        )

        if total_http > self.cumulative_http_threshold:
            issues.append(
                TimingIssue(
                    category="cumulative_http_time",
                    severity=3,
                    operation_type="http",
                    duration_ms=total_http,
                    threshold_ms=self.cumulative_http_threshold,
                    description=f"Total HTTP time: {total_http:.0f}ms across {len(transcript.http_records)} requests",
                    suggestion="Consider parallelizing requests or reducing API calls",
                    record=None,
                    evidence={
                        "total_ms": total_http,
                        "request_count": len(transcript.http_records),
                        "avg_ms": total_http / len(transcript.http_records) if transcript.http_records else 0,
                    },
                )
            )

        if total_sql > self.cumulative_sql_threshold:
            issues.append(
                TimingIssue(
                    category="cumulative_sql_time",
                    severity=3,
                    operation_type="sql",
                    duration_ms=total_sql,
                    threshold_ms=self.cumulative_sql_threshold,
                    description=f"Total SQL time: {total_sql:.0f}ms across {len(transcript.sql_records)} queries",
                    suggestion="Consider query optimization, caching, or reducing query count",
                    record=None,
                    evidence={
                        "total_ms": total_sql,
                        "query_count": len(transcript.sql_records),
                        "avg_ms": total_sql / len(transcript.sql_records) if transcript.sql_records else 0,
                    },
                )
            )

        return issues

    def _normalize_query_pattern(self, query: str) -> str:
        """Normalize SQL query for pattern matching.

        Args:
            query: SQL query

        Returns:
            Normalized pattern
        """
        # Remove string literals
        normalized = re.sub(r"'[^']*'", "'?'", query)
        # Remove numbers
        normalized = re.sub(r"\b\d+\b", "?", normalized)
        # Normalize whitespace
        normalized = re.sub(r"\s+", " ", normalized)
        return normalized.strip()[:200]

    @staticmethod
    def _median(values: List[float]) -> float:
        if not values:
            return 0.0
        n = len(values)
        mid = n // 2
        if n % 2 == 1:
            return values[mid]
        return (values[mid - 1] + values[mid]) / 2

    @classmethod
    def _median_abs_deviation(cls, values: List[float], median: Optional[float] = None) -> float:
        if not values:
            return 0.0
        m = median if median is not None else cls._median(sorted(values))
        deviations = sorted(abs(v - m) for v in values)
        return cls._median(deviations)

    def format_report(self, issues: List[TimingIssue]) -> str:
        """Format timing issues as a readable report.

        Args:
            issues: List of timing issues

        Returns:
            Formatted report string
        """
        if not issues:
            return "No timing issues detected."

        # Sort by severity
        sorted_issues = sorted(issues, key=lambda i: -i.severity)

        lines = [
            "Timing Analysis Report",
            "=" * 50,
            f"Found {len(issues)} timing issue(s):",
            "",
        ]

        # Group by category
        by_category: Dict[str, List[TimingIssue]] = {}
        for issue in sorted_issues:
            if issue.category not in by_category:
                by_category[issue.category] = []
            by_category[issue.category].append(issue)

        for category, cat_issues in by_category.items():
            lines.append(f"\n{category.upper().replace('_', ' ')} ({len(cat_issues)}):")
            for issue in cat_issues[:5]:  # Limit to 5 per category
                sev_str = {5: "CRIT", 4: "HIGH", 3: "MED", 2: "LOW", 1: "INFO"}.get(
                    issue.severity, "?"
                )
                lines.append(f"  [{sev_str}] {issue.description}")
                lines.append(f"         Suggestion: {issue.suggestion}")

            if len(cat_issues) > 5:
                lines.append(f"  ... and {len(cat_issues) - 5} more")

        return "\n".join(lines)


# Convenience function
def analyze_timing(transcript: Transcript) -> List[TimingIssue]:
    """Analyze transcript for timing issues.

    Args:
        transcript: I/O transcript

    Returns:
        List of timing issues
    """
    analyzer = TimingAnalyzer()
    return analyzer.analyze(transcript)
