"""
Contract Validation Middleware for ThrillWiki API

This middleware catches contract violations between the Django backend and frontend
TypeScript interfaces, providing immediate feedback during development.
"""

import json
import logging
from typing import Any

from django.conf import settings
from django.http import JsonResponse
from django.utils.deprecation import MiddlewareMixin
from rest_framework.response import Response

from apps.core.utils import capture_and_log

logger = logging.getLogger(__name__)


class ContractValidationMiddleware(MiddlewareMixin):
    """
    Development-only middleware that validates API responses against expected contracts.

    This middleware:
    1. Checks all API responses for contract compliance
    2. Logs warnings when responses don't match expected TypeScript interfaces
    3. Specifically validates filter metadata structure
    4. Alerts when categorical filters are strings instead of objects

    Only active when DEBUG=True to avoid performance impact in production.
    """

    def __init__(self, get_response):
        super().__init__(get_response)
        self.get_response = get_response
        self.enabled = getattr(settings, "DEBUG", False)

        if self.enabled:
            logger.info("Contract validation middleware enabled (DEBUG mode)")

    def process_response(self, request, response):
        """Process API responses to check for contract violations."""

        if not self.enabled:
            return response

        # Only validate API endpoints
        if not request.path.startswith("/api/"):
            return response

        # Only validate JSON responses
        if not isinstance(response, JsonResponse | Response):
            return response

        # Only validate successful responses (2xx status codes)
        if not (200 <= response.status_code < 300):
            return response

        try:
            # Get response data
            data = response.data if isinstance(response, Response) else json.loads(response.content.decode("utf-8"))

            # Validate the response
            self._validate_response_contract(request.path, data)

        except Exception as e:
            # Log validation errors but don't break the response
            logger.warning(
                f"Contract validation error for {request.path}: {str(e)}",
                extra={
                    "path": request.path,
                    "method": request.method,
                    "status_code": response.status_code,
                    "validation_error": str(e),
                },
            )

        return response

    def _validate_response_contract(self, path: str, data: Any) -> None:
        """Validate response data against expected contracts."""

        # Check for filter metadata endpoints
        if "filter-options" in path or "filter_options" in path:
            self._validate_filter_metadata(path, data)

        # Check for hybrid filtering endpoints
        if "hybrid" in path:
            self._validate_hybrid_response(path, data)

        # Check for pagination responses
        if isinstance(data, dict) and "results" in data:
            self._validate_pagination_response(path, data)

        # Check for common contract violations
        self._validate_common_patterns(path, data)

    def _validate_filter_metadata(self, path: str, data: Any) -> None:
        """Validate filter metadata structure."""

        if not isinstance(data, dict):
            self._log_contract_violation(
                path, "FILTER_METADATA_NOT_DICT", f"Filter metadata should be a dictionary, got {type(data).__name__}"
            )
            return

        # Check for categorical filters
        if "categorical" in data:
            categorical = data["categorical"]
            if isinstance(categorical, dict):
                for filter_name, filter_options in categorical.items():
                    self._validate_categorical_filter(path, filter_name, filter_options)

        # Check for ranges
        if "ranges" in data:
            ranges = data["ranges"]
            if isinstance(ranges, dict):
                for range_name, range_data in ranges.items():
                    self._validate_range_filter(path, range_name, range_data)

    def _validate_categorical_filter(self, path: str, filter_name: str, filter_options: Any) -> None:
        """Validate categorical filter options format."""

        if not isinstance(filter_options, list):
            self._log_contract_violation(
                path,
                "CATEGORICAL_FILTER_NOT_ARRAY",
                f"Categorical filter '{filter_name}' should be an array, got {type(filter_options).__name__}",
            )
            return

        for i, option in enumerate(filter_options):
            if isinstance(option, str):
                # CRITICAL: This is the main contract violation we're trying to catch
                self._log_contract_violation(
                    path,
                    "CATEGORICAL_OPTION_IS_STRING",
                    f"Categorical filter '{filter_name}' option {i} is a string '{option}' but should be an object with value/label/count properties",
                    severity="ERROR",
                )
            elif isinstance(option, dict):
                # Validate object structure
                if "value" not in option:
                    self._log_contract_violation(
                        path,
                        "MISSING_VALUE_PROPERTY",
                        f"Categorical filter '{filter_name}' option {i} missing 'value' property",
                    )
                if "label" not in option:
                    self._log_contract_violation(
                        path,
                        "MISSING_LABEL_PROPERTY",
                        f"Categorical filter '{filter_name}' option {i} missing 'label' property",
                    )
                # Count is optional but should be number if present
                if "count" in option and option["count"] is not None and not isinstance(option["count"], int | float):
                    self._log_contract_violation(
                        path,
                        "INVALID_COUNT_TYPE",
                        f"Categorical filter '{filter_name}' option {i} 'count' should be a number, got {type(option['count']).__name__}",
                    )

    def _validate_range_filter(self, path: str, range_name: str, range_data: Any) -> None:
        """Validate range filter format."""

        if not isinstance(range_data, dict):
            self._log_contract_violation(
                path,
                "RANGE_FILTER_NOT_OBJECT",
                f"Range filter '{range_name}' should be an object, got {type(range_data).__name__}",
            )
            return

        # Check required properties
        required_props = ["min", "max"]
        for prop in required_props:
            if prop not in range_data:
                self._log_contract_violation(
                    path, "MISSING_RANGE_PROPERTY", f"Range filter '{range_name}' missing required property '{prop}'"
                )

        # Check step property
        if "step" in range_data and not isinstance(range_data["step"], int | float):
            self._log_contract_violation(
                path,
                "INVALID_STEP_TYPE",
                f"Range filter '{range_name}' 'step' should be a number, got {type(range_data['step']).__name__}",
            )

    def _validate_hybrid_response(self, path: str, data: Any) -> None:
        """Validate hybrid filtering response structure."""

        if not isinstance(data, dict):
            return

        # Check for strategy field
        if "strategy" in data:
            strategy = data["strategy"]
            if strategy not in ["client_side", "server_side"]:
                self._log_contract_violation(
                    path,
                    "INVALID_STRATEGY_VALUE",
                    f"Hybrid response strategy should be 'client_side' or 'server_side', got '{strategy}'",
                )

        # Check filter_metadata structure
        if "filter_metadata" in data:
            self._validate_filter_metadata(path, data["filter_metadata"])

    def _validate_pagination_response(self, path: str, data: dict[str, Any]) -> None:
        """Validate pagination response structure."""

        # Check for required pagination fields
        required_fields = ["count", "results"]
        for field in required_fields:
            if field not in data:
                self._log_contract_violation(
                    path, "MISSING_PAGINATION_FIELD", f"Pagination response missing required field '{field}'"
                )

        # Check results is array
        if "results" in data and not isinstance(data["results"], list):
            self._log_contract_violation(
                path,
                "RESULTS_NOT_ARRAY",
                f"Pagination 'results' should be an array, got {type(data['results']).__name__}",
            )

    def _validate_common_patterns(self, path: str, data: Any) -> None:
        """Validate common API response patterns."""

        if isinstance(data, dict):
            # Check for null vs undefined issues
            for key, value in data.items():
                if value is None and key.endswith("_id"):
                    # ID fields should probably be null, not undefined
                    continue

                # Check for numeric fields that might be strings
                if key.endswith("_count") and isinstance(value, str):
                    try:
                        int(value)
                        self._log_contract_violation(
                            path,
                            "NUMERIC_FIELD_AS_STRING",
                            f"Field '{key}' appears to be numeric but is a string: '{value}'",
                        )
                    except ValueError:
                        pass

    def _log_contract_violation(self, path: str, violation_type: str, message: str, severity: str = "WARNING") -> None:
        """Log a contract violation with structured data."""

        log_data = {
            "contract_violation": True,
            "violation_type": violation_type,
            "api_path": path,
            "severity": severity,
            "message": message,
            "suggestion": self._get_violation_suggestion(violation_type),
        }

        if severity == "ERROR":
            # Contract violations are development issues - capture for visibility
            capture_and_log(
                ValueError(message),
                f'Contract violation [{violation_type}] on {path}',
                source='middleware',
                severity='medium',
            )
        else:
            logger.warning(f"CONTRACT VIOLATION [{violation_type}]: {message}", extra=log_data)

    def _get_violation_suggestion(self, violation_type: str) -> str:
        """Get suggestion for fixing a contract violation."""

        suggestions = {
            "CATEGORICAL_OPTION_IS_STRING": (
                "Convert string arrays to object arrays with {value, label, count} structure. "
                "Use the ensure_filter_option_format() utility function from apps.api.v1.serializers.shared"
            ),
            "MISSING_VALUE_PROPERTY": (
                "Add 'value' property to filter option objects. "
                "Use FilterOptionSerializer from apps.api.v1.serializers.shared"
            ),
            "MISSING_LABEL_PROPERTY": (
                "Add 'label' property to filter option objects. "
                "Use FilterOptionSerializer from apps.api.v1.serializers.shared"
            ),
            "RANGE_FILTER_NOT_OBJECT": (
                "Convert range data to object with min/max/step/unit properties. "
                "Use FilterRangeSerializer from apps.api.v1.serializers.shared"
            ),
            "NUMERIC_FIELD_AS_STRING": (
                "Ensure numeric fields are returned as numbers, not strings. "
                "Check serializer field types and database field types."
            ),
            "RESULTS_NOT_ARRAY": (
                "Ensure pagination 'results' field is always an array. " "Check serializer implementation."
            ),
        }

        return suggestions.get(violation_type, "Check the API response format against frontend TypeScript interfaces.")


class ContractValidationSettings:
    """Settings for contract validation middleware."""

    # Enable/disable specific validation checks
    VALIDATE_FILTER_METADATA = True
    VALIDATE_PAGINATION = True
    VALIDATE_HYBRID_RESPONSES = True
    VALIDATE_COMMON_PATTERNS = True

    # Severity levels for different violations
    CATEGORICAL_STRING_SEVERITY = "ERROR"  # This is the critical issue
    MISSING_PROPERTY_SEVERITY = "WARNING"
    TYPE_MISMATCH_SEVERITY = "WARNING"

    # Paths to exclude from validation
    EXCLUDED_PATHS = [
        "/api/docs/",
        "/api/schema/",
        "/api/v1/auth/",  # Auth endpoints might have different structures
    ]

    @classmethod
    def should_validate_path(cls, path: str) -> bool:
        """Check if a path should be validated."""
        return not any(excluded in path for excluded in cls.EXCLUDED_PATHS)
