"""HTTP response analysis with fastjsonschema.

Analyzes HTTP responses for errors, schema violations,
and patterns that may indicate issues.
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import TYPE_CHECKING, Any, Dict, List

if TYPE_CHECKING:
    from autodebug.detection.transcript_analyzer import HTTPRecord, TranscriptInsight


class HTTPAnalyzer:
    """Analyze HTTP responses using fastjsonschema for fast validation.

    Detects:
    - HTTP error status codes
    - Invalid JSON responses
    - Schema violations
    - Error patterns in response bodies
    - Authentication issues
    """

    def __init__(self):
        """Initialize the HTTP analyzer."""
        self.schema_validator = SchemaValidator()
        self.error_patterns = ErrorPatternDetector()

    def analyze(self, records: List[HTTPRecord]) -> List[TranscriptInsight]:
        """Analyze HTTP records for issues.

        Args:
            records: List of HTTP records to analyze

        Returns:
            List of insights found
        """

        insights: List[TranscriptInsight] = []

        for record in records:
            # Check for error status codes
            insights.extend(self._analyze_status_code(record))

            # Analyze response body
            insights.extend(self._analyze_response_body(record))

            # Check for auth issues
            insights.extend(self._analyze_auth(record))

            # Check for slow responses
            insights.extend(self._analyze_latency(record))

            # Check content-length mismatches
            insights.extend(self._analyze_content_length(record))

            # Correlate request vs response body for basic consistency
            insights.extend(self._analyze_request_response(record))

        # Detect redirect chains across records
        insights.extend(self._analyze_redirect_chains(records))

        return insights

    def _analyze_status_code(self, record: HTTPRecord) -> List[TranscriptInsight]:
        """Detect HTTP error patterns.

        Args:
            record: HTTP record to analyze

        Returns:
            List of insights for status code issues
        """
        from autodebug.detection.transcript_analyzer import TranscriptInsight

        if record.status < 400:
            return []

        # Determine severity based on status code
        if record.status >= 500:
            severity = 4  # Server errors are more severe
        elif record.status == 401 or record.status == 403:
            severity = 4  # Auth errors are important
        else:
            severity = 3  # Client errors

        response_preview = self._to_text(record.response_body)[:500]
        return [
            TranscriptInsight(
                category="http_error",
                severity=severity,
                description=f"HTTP {record.status} from {record.url_canonical}",
                evidence={
                    "status": record.status,
                    "method": record.method,
                    "url": record.url_canonical,
                    "response_preview": response_preview,
                },
                related_records=[record.seq],
                suggested_investigation=self._suggest_for_status(record.status),
            )
        ]

    def _analyze_response_body(self, record: HTTPRecord) -> List[TranscriptInsight]:
        """Analyze response body using fastjsonschema.

        Args:
            record: HTTP record to analyze

        Returns:
            List of insights from response body analysis
        """
        from autodebug.detection.transcript_analyzer import TranscriptInsight

        body = record.response_body
        if not body:
            return []

        # Skip binary/non-JSON responses and very large bodies
        content_type = ""
        if record.headers:
            content_type = next(
                (v for k, v in record.headers.items() if k.lower() == "content-type"),
                "",
            ).lower()
        if content_type and "json" not in content_type:
            # Only parse if the body looks like JSON
            if not self._looks_like_json(body):
                return []
        if len(body) > 1_000_000:
            return []

        insights: List[TranscriptInsight] = []

        # Try to parse as JSON
        try:
            data = json.loads(body)
        except (json.JSONDecodeError, ValueError, TypeError) as e:
            # Invalid JSON
            insights.append(
                TranscriptInsight(
                    category="invalid_json",
                    severity=3,
                    description=f"Invalid JSON from {record.url_canonical}",
                    evidence={
                        "raw_preview": self._to_text(body)[:200],
                        "error": str(e),
                    },
                    related_records=[record.seq],
                    suggested_investigation="Check if API is returning malformed JSON",
                )
            )
            return insights

        # Validate against known schemas (if available)
        schema_errors = self.schema_validator.validate(record.url_canonical, data)
        for error in schema_errors:
            insights.append(
                TranscriptInsight(
                    category="schema_violation",
                    severity=3,
                    description=f"Schema violation: {error}",
                    evidence={"url": record.url_canonical, "error": error},
                    related_records=[record.seq],
                    suggested_investigation="Check if API response format changed",
                )
            )

        # Detect error patterns in JSON
        if isinstance(data, dict):
            insights.extend(self.error_patterns.detect(record, data))
        elif isinstance(data, list):
            for item in data[:20]:
                if isinstance(item, dict):
                    insights.extend(self.error_patterns.detect(record, item))

        return insights

    def _analyze_auth(self, record: HTTPRecord) -> List[TranscriptInsight]:
        """Detect authentication issues.

        Args:
            record: HTTP record to analyze

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

        insights: List[TranscriptInsight] = []

        if record.status == 401:
            insights.append(
                TranscriptInsight(
                    category="auth_failure",
                    severity=4,
                    description="Authentication failed (401 Unauthorized)",
                    evidence={
                        "url": record.url_canonical,
                        "method": record.method,
                    },
                    related_records=[record.seq],
                    suggested_investigation="Check if auth token is valid and not expired",
                )
            )

        if record.status == 403:
            insights.append(
                TranscriptInsight(
                    category="permission_denied",
                    severity=4,
                    description="Permission denied (403 Forbidden)",
                    evidence={
                        "url": record.url_canonical,
                        "method": record.method,
                    },
                    related_records=[record.seq],
                    suggested_investigation="Check if user has required permissions",
                )
            )

        return insights

    def _analyze_latency(self, record: HTTPRecord) -> List[TranscriptInsight]:
        """Detect slow HTTP responses.

        Args:
            record: HTTP record to analyze

        Returns:
            List of latency-related insights
        """
        from autodebug.detection.transcript_analyzer import TranscriptInsight

        # Consider responses over 5 seconds as slow
        if record.duration_ms > 5000:
            return [
                TranscriptInsight(
                    category="slow_http",
                    severity=2,
                    description=f"Slow HTTP response ({record.duration_ms:.0f}ms) from {record.url_canonical}",
                    evidence={
                        "url": record.url_canonical,
                        "duration_ms": record.duration_ms,
                    },
                    related_records=[record.seq],
                    suggested_investigation="Check if timeout handling is needed",
                )
            ]
        return []

    def _analyze_content_length(self, record: HTTPRecord) -> List[TranscriptInsight]:
        """Detect response Content-Length mismatches."""
        from autodebug.detection.transcript_analyzer import TranscriptInsight

        if not record.headers or record.response_body is None:
            return []

        content_length = None
        for k, v in record.headers.items():
            if k.lower() == "content-length":
                try:
                    content_length = int(v)
                except (TypeError, ValueError):
                    content_length = None
                break

        if content_length is None:
            return []

        body_len = self._body_len(record.response_body)
        if body_len is None:
            return []

        if body_len != content_length:
            return [
                TranscriptInsight(
                    category="content_length_mismatch",
                    severity=2,
                    description="HTTP Content-Length mismatch",
                    evidence={
                        "url": record.url_canonical,
                        "method": record.method,
                        "header_length": content_length,
                        "actual_length": body_len,
                    },
                    related_records=[record.seq],
                    suggested_investigation="Check response body integrity or proxy rewriting",
                )
            ]
        return []

    def _analyze_request_response(self, record: HTTPRecord) -> List[TranscriptInsight]:
        """Basic request/response consistency checks."""
        from autodebug.detection.transcript_analyzer import TranscriptInsight

        if not record.request_body:
            return []

        method = (record.method or "").upper()
        if method not in {"POST", "PUT", "PATCH"}:
            return []

        if not (200 <= record.status < 300) or record.status == 204:
            return []

        response_text = self._to_text(record.response_body).strip() if record.response_body is not None else ""
        if response_text in ("", "null", "{}", "[]"):
            return [
                TranscriptInsight(
                    category="request_response_mismatch",
                    severity=2,
                    description="Non-empty request with empty success response",
                    evidence={
                        "url": record.url_canonical,
                        "method": record.method,
                        "status": record.status,
                    },
                    related_records=[record.seq],
                    suggested_investigation="Check if response body is expected for this endpoint",
                )
            ]
        return []

    def _analyze_redirect_chains(self, records: List[HTTPRecord]) -> List[TranscriptInsight]:
        """Detect multiple consecutive redirects."""
        from autodebug.detection.transcript_analyzer import TranscriptInsight

        if not records:
            return []

        insights: List[TranscriptInsight] = []
        chain: List[HTTPRecord] = []

        def flush_chain():
            nonlocal chain
            if len(chain) >= 2:
                urls = [r.url_canonical for r in chain]
                insights.append(
                    TranscriptInsight(
                        category="redirect_chain",
                        severity=2,
                        description=f"Redirect chain length {len(chain)}",
                        evidence={"urls": urls},
                        related_records=[r.seq for r in chain],
                        suggested_investigation="Check for misconfigured redirects or loops",
                    )
                )
            chain = []

        for record in sorted(records, key=lambda r: r.seq):
            is_redirect = 300 <= record.status < 400
            has_location = False
            if record.headers:
                for k, v in record.headers.items():
                    if k.lower() == "location" and v:
                        has_location = True
                        break

            if is_redirect and has_location:
                chain.append(record)
            else:
                flush_chain()

        flush_chain()
        return insights

    def _suggest_for_status(self, status: int) -> str:
        """Get investigation suggestion for HTTP status code.

        Args:
            status: HTTP status code

        Returns:
            Suggestion string
        """
        suggestions = {
            400: "Check request parameters and body format",
            401: "Check authentication credentials",
            403: "Check user permissions",
            404: "Check if resource exists and URL is correct",
            405: "Check if HTTP method is correct",
            409: "Check for conflict with existing resource",
            422: "Check request validation errors",
            429: "Implement rate limiting/backoff",
            500: "Check server logs for internal error",
            502: "Check upstream service availability",
            503: "Service may be temporarily unavailable, add retry",
            504: "Gateway timeout, check service latency",
        }
        return suggestions.get(status, f"Investigate HTTP {status} error")

    @staticmethod
    def _to_text(body: Any) -> str:
        if body is None:
            return ""
        if isinstance(body, bytes):
            return body.decode("utf-8", errors="replace")
        return str(body)

    @classmethod
    def _body_len(cls, body: Any) -> int | None:
        if body is None:
            return None
        if isinstance(body, bytes):
            return len(body)
        if isinstance(body, str):
            return len(body.encode("utf-8"))
        return len(str(body).encode("utf-8"))

    @classmethod
    def _looks_like_json(cls, body: Any) -> bool:
        if not body:
            return False
        s = cls._to_text(body).lstrip()
        return s.startswith("{") or s.startswith("[")


class SchemaValidator:
    """Fast JSON schema validation using fastjsonschema.

    Validates HTTP responses against registered JSON schemas
    for fast error detection.
    """

    def __init__(self):
        """Initialize the schema validator."""
        import fastjsonschema
        self._validators: Dict[str, Any] = {}
        self._fastjsonschema = fastjsonschema
        self._load_schema_dir()

    def _load_schema_dir(self) -> None:
        """Load schemas from schemas/http/*.json if present."""
        try:
            root = Path(__file__).resolve().parents[2]
            schema_dir = root / "schemas" / "http"
            if not schema_dir.exists():
                return
            for path in schema_dir.glob("*.json"):
                try:
                    schema = json.loads(path.read_text(encoding="utf-8"))
                    pattern = path.stem
                    self.register_schema(pattern, schema)
                except Exception:
                    continue
        except Exception:
            return

    def register_schema(self, url_pattern: str, schema: Dict):
        """Register a schema for a URL pattern.

        Args:
            url_pattern: URL pattern to match (substring match)
            schema: JSON schema dict
        """
        try:
            self._validators[url_pattern] = self._fastjsonschema.compile(schema)
        except Exception:
            pass  # Invalid schema

    def validate(self, url: str, data: Any) -> List[str]:
        """Validate data against registered schemas.

        Args:
            url: URL to match against patterns
            data: Data to validate

        Returns:
            List of validation error messages
        """
        errors = []
        for pattern, validator in self._validators.items():
            if pattern in url:
                try:
                    validator(data)
                except self._fastjsonschema.JsonSchemaValueException as e:
                    errors.append(str(e.message))
        return errors


class ErrorPatternDetector:
    """Detect common error patterns in JSON responses."""

    # Common fields that indicate errors
    ERROR_FIELDS = [
        "error",
        "errors",
        "message",
        "error_message",
        "errorMessage",
        "detail",
        "details",
        "fault",
        "exception",
    ]

    # Fields that commonly contain data
    DATA_FIELDS = ["data", "items", "results", "records", "rows", "response"]

    def detect(self, record: HTTPRecord, data: Dict) -> List[TranscriptInsight]:
        """Detect error patterns in JSON response.

        Args:
            record: HTTP record
            data: Parsed JSON data

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

        insights: List[TranscriptInsight] = []

        # Check for error fields (including nested)
        for field, value, path in self._find_error_fields(data):
            if value:
                # Skip if this is a success response with error field set to False/None
                if value is False or value is None:
                    continue

                insights.append(
                    TranscriptInsight(
                        category="api_error_response",
                        severity=3,
                        description=f"API error: {self._truncate(str(value), 200)}",
                        evidence={
                            "error_field": field,
                            "error_path": path,
                            "error_value": self._truncate(str(value), 500),
                            "url": record.url_canonical,
                        },
                        related_records=[record.seq],
                        suggested_investigation="Check if this error is the root cause",
                    )
                )
                break  # Only report first error field

        # Check for null data
        for field in self.DATA_FIELDS:
            if field in data and data[field] is None:
                insights.append(
                    TranscriptInsight(
                        category="null_data_response",
                        severity=2,
                        description=f"API returned null {field}",
                        evidence={
                            "url": record.url_canonical,
                            "field": field,
                        },
                        related_records=[record.seq],
                        suggested_investigation="Check if code handles null data",
                    )
                )
                break  # Only report first null data field

        # Check for empty arrays
        for field in self.DATA_FIELDS:
            if field in data:
                value = data[field]
                if isinstance(value, list) and len(value) == 0:
                    insights.append(
                        TranscriptInsight(
                            category="empty_results",
                            severity=2,
                            description=f"API returned empty {field}",
                            evidence={
                                "url": record.url_canonical,
                                "field": field,
                            },
                            related_records=[record.seq],
                            suggested_investigation="Check if empty results are handled",
                        )
                    )
                    break  # Only report first empty array

        return insights

    def _find_error_fields(self, data: Dict, max_depth: int = 2):
        """Yield (field, value, path) for known error fields up to max_depth."""
        stack = [(data, "", 0)]
        while stack:
            current, prefix, depth = stack.pop()
            if not isinstance(current, dict):
                continue
            for k, v in current.items():
                path = f"{prefix}.{k}" if prefix else k
                if k in self.ERROR_FIELDS:
                    yield k, v, path
                if depth < max_depth and isinstance(v, dict):
                    stack.append((v, path, depth + 1))

    def _truncate(self, s: str, length: int) -> str:
        """Truncate string to max length.

        Args:
            s: String to truncate
            length: Maximum length

        Returns:
            Truncated string
        """
        if len(s) <= length:
            return s
        return s[: length - 3] + "..."
