"""Correlation analysis between I/O and exceptions.

Correlates captured I/O operations with exceptions to identify
likely root causes.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, List

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


class CorrelationAnalyzer:
    """Correlate I/O operations with exceptions.

    Analyzes the sequence of I/O operations before an exception
    to identify likely root causes.
    """

    def __init__(
        self,
        lookback_count: int = 10,
        slow_threshold_ms: float = 3000,
        lookback_seconds: float | None = None,
    ):
        """Initialize the correlation analyzer.

        Args:
            lookback_count: Number of records to look back before exception
            slow_threshold_ms: Threshold for slow operations (ms)
        """
        if lookback_count < 1:
            raise ValueError(f"lookback_count must be >= 1, got {lookback_count}")
        self.lookback_count = lookback_count
        self.slow_threshold_ms = slow_threshold_ms
        self.lookback_seconds = lookback_seconds

    def correlate(
        self,
        exception: ExceptionRecord,
        records: List[Any],
    ) -> List[TranscriptInsight]:
        """Find I/O operations related to the exception.

        Args:
            exception: The exception that occurred
            records: List of all records (HTTP, SQL, etc.)

        Returns:
            List of correlation insights
        """
        insights: List[TranscriptInsight] = []

        # Get records before exception
        pre_exception = self._records_before_exception(
            records,
            exception_ts=getattr(exception, "timestamp", None),
        )

        # Check if HTTP errors preceded the exception
        insights.extend(self._correlate_http_errors(exception, pre_exception))

        # Check if empty results preceded KeyError/IndexError
        insights.extend(self._correlate_empty_results(exception, pre_exception))

        # Check if auth failures preceded the exception
        insights.extend(self._correlate_auth_failures(exception, pre_exception))

        # Check if SQL errors preceded the exception
        insights.extend(self._correlate_sql_errors(exception, pre_exception))

        # Check if Redis errors preceded the exception
        insights.extend(self._correlate_redis_errors(exception, pre_exception))

        # Check if gRPC errors preceded the exception
        insights.extend(self._correlate_grpc_errors(exception, pre_exception))

        # Check timing correlations
        insights.extend(self._correlate_timing(exception, pre_exception))

        # Check for cross-protocol error chains
        insights.extend(self._correlate_cross_protocol(exception, pre_exception))

        return insights

    def _records_before_exception(
        self,
        records: List[Any],
        exception_ts: float | None = None,
    ) -> List[Any]:
        """Get records that occurred before the exception.

        Args:
            records: All records

        Returns:
            Last N records before exception
        """
        if not records:
            return []
        # Prefer timestamp window if available
        if self.lookback_seconds:
            with_ts = [r for r in records if getattr(r, "timestamp", None) is not None]
            if with_ts:
                upper = exception_ts if exception_ts is not None else max(getattr(r, "timestamp", 0) for r in with_ts)
                cutoff = upper - self.lookback_seconds
                windowed = [
                    r
                    for r in with_ts
                    if cutoff <= getattr(r, "timestamp", 0) <= upper
                ]
                if windowed:
                    return windowed

        # Fallback: sequence number ordering
        sorted_records = sorted(records, key=lambda r: getattr(r, "seq", 0))
        if exception_ts is not None:
            sorted_records = [
                r for r in sorted_records
                if (getattr(r, "timestamp", None) is None or getattr(r, "timestamp", 0) <= exception_ts)
            ]
        return sorted_records[-self.lookback_count:]

    def _correlate_http_errors(
        self,
        exception: ExceptionRecord,
        records: List[Any],
    ) -> List[TranscriptInsight]:
        """Check if HTTP errors preceded the exception.

        Args:
            exception: The exception
            records: Records before exception

        Returns:
            List of HTTP correlation insights
        """
        from autodebug.detection.transcript_analyzer import TranscriptInsight

        # Find HTTP records with error status
        http_errors = [
            r
            for r in records
            if hasattr(r, "status") and r.status >= 400
        ]

        if not http_errors:
            return []

        # Strong correlation for certain exception types
        strong_correlation = exception.type in [
            "KeyError",
            "TypeError",
            "AttributeError",
            "ValueError",
            "JSONDecodeError",
        ]

        # Cap severity when sample size is small
        base_severity = 4 if strong_correlation else 3
        base_severity = self._cap_severity(base_severity, len(http_errors))

        # Return one insight per HTTP error in the lookback window
        results = []
        for error in http_errors:
            severity = base_severity
            if self._has_later_http_success(records, error):
                severity = min(severity, 2)
            results.append(
                TranscriptInsight(
                    category="http_caused_exception",
                    severity=severity,
                    description=f"{exception.type} may be caused by HTTP {error.status}",
                    evidence={
                        "http_status": error.status,
                        "http_url": error.url_canonical,
                        "http_method": getattr(error, "method", "?"),
                        "exception_type": exception.type,
                        "exception_message": exception.message[:200] if exception.message else "",
                    },
                    related_records=[error.seq],
                    suggested_investigation="Check if exception handling addresses HTTP error",
                )
            )
        return results

    def _correlate_empty_results(
        self,
        exception: ExceptionRecord,
        records: List[Any],
    ) -> List[TranscriptInsight]:
        """Check if empty results preceded KeyError/IndexError.

        Args:
            exception: The exception
            records: Records before exception

        Returns:
            List of empty result correlation insights
        """
        from autodebug.detection.transcript_analyzer import TranscriptInsight

        # Only correlate for specific exception types
        if exception.type not in ["KeyError", "IndexError", "StopIteration"]:
            return []

        # Find SQL records with empty results
        empty_sql = [
            r
            for r in records
            if hasattr(r, "rows") and r.rows is not None and len(r.rows) == 0
        ]

        if empty_sql:
            last = empty_sql[-1]
            return [
                TranscriptInsight(
                    category="empty_result_caused_exception",
                    severity=self._cap_severity(4, len(empty_sql)),
                    description=f"{exception.type} may be caused by empty query result",
                    evidence={
                        "exception_type": exception.type,
                        "exception_message": exception.message[:200] if exception.message else "",
                        "query": getattr(last, "sql_canonical", "")[:100],
                    },
                    related_records=[last.seq],
                    suggested_investigation="Check if code handles empty results",
                )
            ]

        # Find HTTP responses with empty data
        empty_http = [
            r
            for r in records
            if hasattr(r, "response_body") and self._is_empty_response(r.response_body)
        ]

        if empty_http:
            last = empty_http[-1]
            return [
                TranscriptInsight(
                    category="empty_response_caused_exception",
                    severity=self._cap_severity(4, len(empty_http)),
                    description=f"{exception.type} may be caused by empty HTTP response",
                    evidence={
                        "exception_type": exception.type,
                        "http_url": last.url_canonical,
                    },
                    related_records=[last.seq],
                    suggested_investigation="Check if code handles empty API response",
                )
            ]

        return []

    def _correlate_auth_failures(
        self,
        exception: ExceptionRecord,
        records: List[Any],
    ) -> List[TranscriptInsight]:
        """Check if auth failures preceded the exception.

        Args:
            exception: The exception
            records: Records before exception

        Returns:
            List of auth correlation insights
        """
        from autodebug.detection.transcript_analyzer import TranscriptInsight

        # Find HTTP 401/403 responses
        auth_failures = [
            r
            for r in records
            if hasattr(r, "status") and r.status in [401, 403]
        ]

        if not auth_failures:
            return []

        last = auth_failures[-1]
        severity = self._cap_severity(4, len(auth_failures))
        status_text = "Unauthorized" if last.status == 401 else "Forbidden"

        return [
            TranscriptInsight(
                category="auth_caused_exception",
                severity=severity,
                description=f"{exception.type} may be caused by {status_text} response",
                evidence={
                    "http_status": last.status,
                    "http_url": last.url_canonical,
                    "exception_type": exception.type,
                },
                related_records=[last.seq],
                suggested_investigation="Check authentication and permissions",
            )
        ]

    def _correlate_sql_errors(
        self,
        exception: ExceptionRecord,
        records: List[Any],
    ) -> List[TranscriptInsight]:
        """Check if SQL errors preceded the exception.

        Args:
            exception: The exception
            records: Records before exception

        Returns:
            List of SQL correlation insights
        """
        from autodebug.detection.transcript_analyzer import TranscriptInsight

        # Find SQL records with exceptions
        sql_errors = [
            r
            for r in records
            if hasattr(r, "exception") and r.exception is not None
        ]

        if not sql_errors:
            return []

        last = sql_errors[-1]
        severity = self._cap_severity(4, len(sql_errors))
        if self._has_later_sql_success(records, last):
            severity = min(severity, 2)

        return [
            TranscriptInsight(
                category="sql_caused_exception",
                severity=severity,
                description=f"{exception.type} may be caused by SQL error",
                evidence={
                    "sql_error": last.exception.get("message", "")[:200] if last.exception else "",
                    "query": getattr(last, "sql_canonical", "")[:100],
                    "exception_type": exception.type,
                },
                related_records=[last.seq],
                suggested_investigation="Check if SQL error handling is correct",
            )
        ]

    def _correlate_redis_errors(
        self,
        exception: ExceptionRecord,
        records: List[Any],
    ) -> List[TranscriptInsight]:
        """Check if Redis errors preceded the exception.

        Args:
            exception: The exception
            records: Records before exception

        Returns:
            List of Redis correlation insights
        """
        from autodebug.detection.transcript_analyzer import RedisRecord, TranscriptInsight

        redis_errors = [
            r
            for r in records
            if isinstance(r, RedisRecord) and r.error is not None
        ]

        if not redis_errors:
            return []

        results = []
        base_severity = self._cap_severity(4, len(redis_errors))
        for error in redis_errors:
            severity = base_severity
            if self._has_later_redis_success(records, error):
                severity = min(severity, 2)
            results.append(
                TranscriptInsight(
                    category="redis_caused_exception",
                    severity=severity,
                    description=f"{exception.type} may be caused by Redis error: {error.error}",
                    evidence={
                        "redis_command": error.command,
                        "redis_key": error.key,
                        "redis_error": error.error,
                        "exception_type": exception.type,
                        "exception_message": exception.message[:200] if exception.message else "",
                    },
                    related_records=[error.seq],
                    suggested_investigation="Check Redis connection and error handling",
                )
            )
        return results

    def _correlate_grpc_errors(
        self,
        exception: ExceptionRecord,
        records: List[Any],
    ) -> List[TranscriptInsight]:
        """Check if gRPC errors preceded the exception.

        Args:
            exception: The exception
            records: Records before exception

        Returns:
            List of gRPC correlation insights
        """
        from autodebug.detection.transcript_analyzer import GRPCRecord, TranscriptInsight

        grpc_errors = [
            r
            for r in records
            if isinstance(r, GRPCRecord) and r.status_code != 0
        ]

        if not grpc_errors:
            return []

        results = []
        base_severity = self._cap_severity(4, len(grpc_errors))
        for error in grpc_errors:
            severity = base_severity
            if self._has_later_grpc_success(records, error):
                severity = min(severity, 2)
            results.append(
                TranscriptInsight(
                    category="grpc_caused_exception",
                    severity=severity,
                    description=f"{exception.type} may be caused by gRPC error (code {error.status_code})",
                    evidence={
                        "grpc_service": error.service,
                        "grpc_method": error.method,
                        "grpc_status_code": error.status_code,
                        "grpc_status_message": error.status_message,
                        "exception_type": exception.type,
                        "exception_message": exception.message[:200] if exception.message else "",
                    },
                    related_records=[error.seq],
                    suggested_investigation="Check gRPC service availability and error handling",
                )
            )
        return results

    def _correlate_cross_protocol(
        self,
        exception: ExceptionRecord,
        records: List[Any],
    ) -> List[TranscriptInsight]:
        """Detect cross-protocol error chains.

        When errors from multiple different protocol types (HTTP, SQL, Redis,
        gRPC) occur before an exception, this may indicate a cascading failure
        across services.

        Args:
            exception: The exception
            records: Records before exception

        Returns:
            List of cross-protocol chain insights
        """
        from autodebug.detection.transcript_analyzer import (
            GRPCRecord,
            HTTPRecord,
            RedisRecord,
            SQLRecord,
            TranscriptInsight,
        )

        error_protocols = set()

        for r in records:
            if isinstance(r, HTTPRecord) and r.status >= 400:
                error_protocols.add("http")
            elif isinstance(r, SQLRecord) and r.exception is not None:
                error_protocols.add("sql")
            elif isinstance(r, RedisRecord) and r.error is not None:
                error_protocols.add("redis")
            elif isinstance(r, GRPCRecord) and r.status_code != 0:
                error_protocols.add("grpc")

        if len(error_protocols) < 2:
            return []

        # Scale severity by number of protocols involved
        severity = min(5, 2 + len(error_protocols))

        return [
            TranscriptInsight(
                category="cross_protocol_chain",
                severity=severity,
                description=(
                    f"{exception.type} preceded by errors in multiple protocols: "
                    f"{', '.join(sorted(error_protocols))}"
                ),
                evidence={
                    "error_protocols": sorted(error_protocols),
                    "protocol_count": len(error_protocols),
                    "exception_type": exception.type,
                    "exception_message": exception.message[:200] if exception.message else "",
                },
                related_records=[r.seq for r in records[-50:]],
                confidence=min(1.0, 0.4 + (0.1 * len(error_protocols))),
                suggested_investigation="Investigate cascading failure across services",
            )
        ]

    def _correlate_timing(
        self,
        exception: ExceptionRecord,
        records: List[Any],
    ) -> List[TranscriptInsight]:
        """Check for timing-related correlations.

        Args:
            exception: The exception
            records: Records before exception

        Returns:
            List of timing correlation insights
        """
        from autodebug.detection.transcript_analyzer import TranscriptInsight

        # Only for timeout-related exceptions
        timeout_exceptions = [
            "TimeoutError",
            "ConnectionTimeout",
            "ReadTimeout",
            "asyncio.TimeoutError",
        ]

        if exception.type not in timeout_exceptions:
            return []

        # Find slow operations
        slow_ops = []
        for r in records:
            duration = getattr(r, "duration_ms", 0)
            if duration > self.slow_threshold_ms:
                slow_ops.append(r)

        if not slow_ops:
            return []

        last = slow_ops[-1]
        duration = getattr(last, "duration_ms", 0)

        return [
            TranscriptInsight(
                category="slow_operation_caused_timeout",
                severity=self._cap_severity(4, len(slow_ops)),
                description=f"Timeout may be caused by slow operation ({duration:.0f}ms)",
                evidence={
                    "duration_ms": duration,
                    "exception_type": exception.type,
                },
                related_records=[last.seq],
                suggested_investigation="Consider increasing timeout or optimizing operation",
            )
        ]

    def _is_empty_response(self, response_body: str) -> bool:
        """Check if response body indicates empty data.

        Args:
            response_body: HTTP response body

        Returns:
            True if response indicates empty data
        """
        if not response_body:
            return True

        try:
            import json
            data = json.loads(response_body)

            # Check common patterns for empty data
            if isinstance(data, dict):
                for key in ["data", "items", "results", "records"]:
                    if key in data:
                        value = data[key]
                        if value is None:
                            return True
                        if isinstance(value, (list, dict, str)) and len(value) == 0:
                            return True
            elif isinstance(data, list) and len(data) == 0:
                return True

        except (json.JSONDecodeError, TypeError):
            pass

        return False

    @staticmethod
    def _cap_severity(severity: int, sample_size: int) -> int:
        """Reduce severity when evidence is sparse."""
        if sample_size < 3:
            return min(severity, 2)
        if sample_size < 5:
            return min(severity, 3)
        return severity

    @staticmethod
    def _has_later_http_success(records: List[Any], error_record: Any) -> bool:
        """Check if a successful HTTP response occurred after the error."""
        err_seq = getattr(error_record, "seq", -1)
        err_url = getattr(error_record, "url_canonical", None)
        for r in records:
            if getattr(r, "seq", -1) <= err_seq:
                continue
            if hasattr(r, "status") and getattr(r, "status", 0) < 400:
                if err_url is None or getattr(r, "url_canonical", None) == err_url:
                    return True
        return False

    @staticmethod
    def _has_later_sql_success(records: List[Any], error_record: Any) -> bool:
        err_seq = getattr(error_record, "seq", -1)
        for r in records:
            if getattr(r, "seq", -1) <= err_seq:
                continue
            if hasattr(r, "sql_canonical") and getattr(r, "exception", None) is None:
                return True
        return False

    @staticmethod
    def _has_later_redis_success(records: List[Any], error_record: Any) -> bool:
        err_seq = getattr(error_record, "seq", -1)
        for r in records:
            if getattr(r, "seq", -1) <= err_seq:
                continue
            if hasattr(r, "command") and getattr(r, "error", None) is None:
                return True
        return False

    @staticmethod
    def _has_later_grpc_success(records: List[Any], error_record: Any) -> bool:
        err_seq = getattr(error_record, "seq", -1)
        for r in records:
            if getattr(r, "seq", -1) <= err_seq:
                continue
            if hasattr(r, "status_code") and getattr(r, "status_code", 1) == 0:
                return True
        return False
