"""
Performance monitoring middleware for tracking request metrics.
"""

import logging
import time

from django.conf import settings
from django.db import connection
from django.utils.deprecation import MiddlewareMixin

from apps.core.utils import capture_and_log

performance_logger = logging.getLogger("performance")
logger = logging.getLogger(__name__)


class PerformanceMiddleware(MiddlewareMixin):
    """Middleware to collect performance metrics for each request"""

    def process_request(self, request):
        """Initialize performance tracking for the request"""
        request._performance_start_time = time.time()
        request._performance_initial_queries = len(connection.queries) if hasattr(connection, "queries") else 0

    def process_response(self, request, response):
        """Log performance metrics after response is ready"""
        # Skip performance tracking for certain paths
        skip_paths = [
            "/health/",
            "/admin/jsi18n/",
            "/static/",
            "/media/",
            "/__debug__/",
        ]
        if any(request.path.startswith(path) for path in skip_paths):
            return response

        # Calculate metrics
        end_time = time.time()
        start_time = getattr(request, "_performance_start_time", end_time)
        duration = end_time - start_time

        initial_queries = getattr(request, "_performance_initial_queries", 0)
        total_queries = len(connection.queries) - initial_queries if hasattr(connection, "queries") else 0

        # Get content length
        content_length = 0
        if hasattr(response, "content"):
            content_length = len(response.content)
        elif hasattr(response, "streaming_content"):
            # For streaming responses, we can't easily measure content length
            content_length = -1

        # Build performance data
        performance_data = {
            "path": request.path,
            "method": request.method,
            "status_code": response.status_code,
            "duration_ms": round(duration * 1000, 2),
            "duration_seconds": round(duration, 3),
            "query_count": total_queries,
            "content_length_bytes": content_length,
            "user_id": (
                getattr(request.user, "id", None)
                if hasattr(request, "user") and request.user.is_authenticated
                else None
            ),
            "user_agent": request.META.get("HTTP_USER_AGENT", "")[:100],  # Truncate user agent
            "remote_addr": self._get_client_ip(request),
        }

        # Add query details in debug mode
        if settings.DEBUG and hasattr(connection, "queries") and total_queries > 0:
            recent_queries = connection.queries[-total_queries:]
            performance_data["queries"] = [
                {
                    "sql": (query["sql"][:200] + "..." if len(query["sql"]) > 200 else query["sql"]),
                    "time": float(query["time"]),
                }
                for query in recent_queries[-10:]  # Last 10 queries only
            ]

            # Identify slow queries
            slow_queries = [q for q in recent_queries if float(q["time"]) > 0.1]
            if slow_queries:
                performance_data["slow_query_count"] = len(slow_queries)
                performance_data["slowest_query_time"] = max(float(q["time"]) for q in slow_queries)

        # Determine log level based on performance
        log_level = self._get_log_level(duration, total_queries, response.status_code)

        # Log the performance data
        performance_logger.log(
            log_level,
            f"Request performance: {request.method} {request.path} - "
            f"{duration:.3f}s, {total_queries} queries, {response.status_code}",
            extra=performance_data,
        )

        # Add performance headers for debugging (only in debug mode)
        if settings.DEBUG:
            response["X-Response-Time"] = f"{duration * 1000:.2f}ms"
            response["X-Query-Count"] = str(total_queries)
            if total_queries > 0 and hasattr(connection, "queries"):
                total_query_time = sum(float(q["time"]) for q in connection.queries[-total_queries:])
                response["X-Query-Time"] = f"{total_query_time * 1000:.2f}ms"

        return response

    def process_exception(self, request, exception):
        """Log performance data even when an exception occurs"""
        end_time = time.time()
        start_time = getattr(request, "_performance_start_time", end_time)
        duration = end_time - start_time

        initial_queries = getattr(request, "_performance_initial_queries", 0)
        total_queries = len(connection.queries) - initial_queries if hasattr(connection, "queries") else 0

        performance_data = {
            "path": request.path,
            "method": request.method,
            "status_code": 500,  # Exception occurred
            "duration_ms": round(duration * 1000, 2),
            "query_count": total_queries,
            "exception": str(exception),
            "exception_type": type(exception).__name__,
            "user_id": (
                getattr(request.user, "id", None)
                if hasattr(request, "user") and request.user.is_authenticated
                else None
            ),
        }

        capture_and_log(
            exception,
            f'Request exception: {request.method} {request.path} - {duration:.3f}s, {total_queries} queries',
            source='middleware',
            severity='high',
        )

        # Don't return anything - let the exception propagate normally

    def _get_client_ip(self, request):
        """Extract client IP address from request"""
        x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
        ip = x_forwarded_for.split(",")[0].strip() if x_forwarded_for else request.META.get("REMOTE_ADDR", "")
        return ip

    def _get_log_level(self, duration, query_count, status_code):
        """Determine appropriate log level based on performance metrics"""
        # Error responses
        if status_code >= 500:
            return logging.ERROR
        elif status_code >= 400:
            return logging.WARNING

        # Performance-based log levels
        if duration > 5.0:  # Very slow requests
            return logging.ERROR
        elif duration > 2.0 or query_count > 20:  # Slow requests or high query count
            return logging.WARNING
        elif duration > 1.0 or query_count > 10:  # Moderately slow
            return logging.INFO
        else:
            return logging.DEBUG


class QueryCountMiddleware(MiddlewareMixin):
    """Middleware to track and limit query counts per request"""

    def __init__(self, get_response):
        self.get_response = get_response
        self.query_limit = getattr(settings, "MAX_QUERIES_PER_REQUEST", 50)
        super().__init__(get_response)

    def process_request(self, request):
        """Initialize query tracking"""
        request._query_count_start = len(connection.queries) if hasattr(connection, "queries") else 0

    def process_response(self, request, response):
        """Check query count and warn if excessive"""
        if not hasattr(connection, "queries"):
            return response

        start_count = getattr(request, "_query_count_start", 0)
        current_count = len(connection.queries)
        request_query_count = current_count - start_count

        if request_query_count > self.query_limit:
            logger.warning(
                f"Excessive query count: {request.path} executed {
                    request_query_count
                } queries "
                f"(limit: {self.query_limit})",
                extra={
                    "path": request.path,
                    "method": request.method,
                    "query_count": request_query_count,
                    "query_limit": self.query_limit,
                    "excessive_queries": True,
                },
            )

        return response


class DatabaseConnectionMiddleware(MiddlewareMixin):
    """Middleware to monitor database connection health"""

    def process_request(self, request):
        """Check database connection at start of request (only for health checks)"""
        # Skip per-request connection checks to avoid extra round trips
        # The database connection will be validated lazily by Django when needed
        pass

    def process_response(self, request, response):
        """Close database connections only when pooling is disabled"""
        # Only close connections when CONN_MAX_AGE is 0 (no pooling)
        # When pooling is enabled (CONN_MAX_AGE > 0), let Django manage connections
        conn_max_age = getattr(settings, "CONN_MAX_AGE", None)
        if conn_max_age is None:
            # Check database settings for CONN_MAX_AGE
            db_settings = getattr(settings, "DATABASES", {}).get("default", {})
            conn_max_age = db_settings.get("CONN_MAX_AGE", 0)

        if conn_max_age == 0:
            try:
                from django.db import connection

                connection.close()
            except Exception as e:
                logger.warning(f"Error closing database connection: {e}")

        return response


class CachePerformanceMiddleware(MiddlewareMixin):
    """Middleware to monitor cache performance"""

    def process_request(self, request):
        """Initialize cache performance tracking"""
        request._cache_hits = 0
        request._cache_misses = 0
        request._cache_start_time = time.time()

    def process_response(self, request, response):
        """Log cache performance metrics"""
        cache_duration = time.time() - getattr(request, "_cache_start_time", time.time())
        cache_hits = getattr(request, "_cache_hits", 0)
        cache_misses = getattr(request, "_cache_misses", 0)

        if cache_hits + cache_misses > 0:
            hit_rate = (cache_hits / (cache_hits + cache_misses)) * 100

            cache_data = {
                "path": request.path,
                "cache_hits": cache_hits,
                "cache_misses": cache_misses,
                "cache_hit_rate": round(hit_rate, 2),
                "cache_operations": cache_hits + cache_misses,
                # milliseconds
                "cache_duration": round(cache_duration * 1000, 2),
            }

            # Log cache performance
            if hit_rate < 50 and cache_hits + cache_misses > 5:
                logger.warning(
                    f"Low cache hit rate for {request.path}: {hit_rate:.1f}%",
                    extra=cache_data,
                )
            else:
                logger.debug(
                    f"Cache performance for {request.path}: {hit_rate:.1f}% hit rate",
                    extra=cache_data,
                )

        return response
