"""
Rate Limiting Middleware for ThrillWiki.

This middleware provides rate limiting for authentication endpoints to prevent
brute force attacks, credential stuffing, and account enumeration.

Security Note:
    Rate limiting is applied at the IP level and user level (if authenticated).
    Limits are configurable and should be adjusted based on actual usage patterns.

Usage:
    Add 'apps.core.middleware.rate_limiting.AuthRateLimitMiddleware'
    to MIDDLEWARE in settings.py.
"""

import logging
from collections.abc import Callable

from django.core.cache import cache
from django.http import HttpRequest, HttpResponse, JsonResponse

from apps.core.utils import capture_and_log

logger = logging.getLogger(__name__)


class AuthRateLimitMiddleware:
    """
    Middleware that rate limits authentication-related endpoints.

    Protects against:
    - Brute force login attacks
    - Password reset abuse
    - Account enumeration through timing attacks
    """

    # Endpoints to rate limit
    RATE_LIMITED_PATHS = {
        # Login endpoints
        "/api/v1/auth/login/": {"per_minute": 5, "per_hour": 30, "per_day": 100},
        "/accounts/login/": {"per_minute": 5, "per_hour": 30, "per_day": 100},
        # Signup endpoints
        "/api/v1/auth/signup/": {"per_minute": 3, "per_hour": 10, "per_day": 20},
        "/accounts/signup/": {"per_minute": 3, "per_hour": 10, "per_day": 20},
        # Password reset endpoints
        "/api/v1/auth/password-reset/": {"per_minute": 2, "per_hour": 5, "per_day": 10},
        "/accounts/password/reset/": {"per_minute": 2, "per_hour": 5, "per_day": 10},
        # Token endpoints
        "/api/v1/auth/token/": {"per_minute": 10, "per_hour": 60, "per_day": 200},
        "/api/v1/auth/token/refresh/": {"per_minute": 20, "per_hour": 120, "per_day": 500},
    }

    def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
        self.get_response = get_response

    def __call__(self, request: HttpRequest) -> HttpResponse:
        # Only rate limit POST requests to auth endpoints
        if request.method != "POST":
            return self.get_response(request)

        # Check if this path should be rate limited
        limits = self._get_rate_limits(request.path)
        if not limits:
            return self.get_response(request)

        # Get client identifier (IP address)
        client_ip = self._get_client_ip(request)

        # Check rate limits
        is_allowed, message = self._check_rate_limits(client_ip, request.path, limits)

        if not is_allowed:
            logger.warning(f"Rate limit exceeded for {client_ip} on {request.path}")
            return self._rate_limit_response(message)

        # Process request
        response = self.get_response(request)

        # Only increment counter for failed auth attempts (non-2xx responses)
        if response.status_code >= 400:
            self._increment_counters(client_ip, request.path)

        return response

    def _get_rate_limits(self, path: str) -> dict | None:
        """Get rate limits for a path, if any."""
        # Exact match
        if path in self.RATE_LIMITED_PATHS:
            return self.RATE_LIMITED_PATHS[path]

        # Prefix match (for paths with trailing slashes)
        path_without_slash = path.rstrip("/")
        for limited_path, limits in self.RATE_LIMITED_PATHS.items():
            if path_without_slash == limited_path.rstrip("/"):
                return limits

        return None

    def _get_client_ip(self, request: HttpRequest) -> str:
        """
        Get the client's IP address from the request.

        Handles common proxy headers (X-Forwarded-For, X-Real-IP).
        """
        # Check for forwarded headers (set by reverse proxies)
        x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
        if x_forwarded_for:
            # Take the first IP in the chain (client IP)
            return x_forwarded_for.split(",")[0].strip()

        x_real_ip = request.META.get("HTTP_X_REAL_IP")
        if x_real_ip:
            return x_real_ip

        return request.META.get("REMOTE_ADDR", "unknown")

    def _check_rate_limits(self, client_ip: str, path: str, limits: dict) -> tuple[bool, str]:
        """
        Check if the client has exceeded rate limits.

        Returns:
            Tuple of (is_allowed, reason_if_blocked)
        """
        # Create a safe cache key from path
        path_key = path.replace("/", "_").strip("_")

        # Check per-minute limit
        minute_key = f"auth_rate:{client_ip}:{path_key}:minute"
        minute_count = cache.get(minute_key, 0)
        if minute_count >= limits.get("per_minute", 10):
            return False, "Too many requests. Please wait a minute before trying again."

        # Check per-hour limit
        hour_key = f"auth_rate:{client_ip}:{path_key}:hour"
        hour_count = cache.get(hour_key, 0)
        if hour_count >= limits.get("per_hour", 60):
            return False, "Too many requests. Please try again later."

        # Check per-day limit
        day_key = f"auth_rate:{client_ip}:{path_key}:day"
        day_count = cache.get(day_key, 0)
        if day_count >= limits.get("per_day", 200):
            return False, "Daily limit exceeded. Please try again tomorrow."

        return True, ""

    def _increment_counters(self, client_ip: str, path: str) -> None:
        """Increment rate limit counters."""
        path_key = path.replace("/", "_").strip("_")

        # Increment per-minute counter
        minute_key = f"auth_rate:{client_ip}:{path_key}:minute"
        try:
            cache.incr(minute_key)
        except ValueError:
            cache.set(minute_key, 1, 60)

        # Increment per-hour counter
        hour_key = f"auth_rate:{client_ip}:{path_key}:hour"
        try:
            cache.incr(hour_key)
        except ValueError:
            cache.set(hour_key, 1, 3600)

        # Increment per-day counter
        day_key = f"auth_rate:{client_ip}:{path_key}:day"
        try:
            cache.incr(day_key)
        except ValueError:
            cache.set(day_key, 1, 86400)

    def _rate_limit_response(self, message: str) -> JsonResponse:
        """Generate a rate limit exceeded response."""
        return JsonResponse(
            {
                "error": message,
                "code": "RATE_LIMIT_EXCEEDED",
            },
            status=429,  # Too Many Requests
        )


class SecurityEventLogger:
    """
    Utility class for logging security-relevant events.

    Use this to log:
    - Failed authentication attempts
    - Permission denied events
    - Suspicious activity
    """

    @staticmethod
    def log_failed_login(request: HttpRequest, username: str, reason: str = "Invalid credentials") -> None:
        """Log a failed login attempt."""
        client_ip = AuthRateLimitMiddleware._get_client_ip(AuthRateLimitMiddleware, request)
        logger.warning(
            f"Failed login attempt - IP: {client_ip}, Username: {username}, "
            f"Reason: {reason}, User-Agent: {request.META.get('HTTP_USER_AGENT', 'unknown')}"
        )

    @staticmethod
    def log_permission_denied(request: HttpRequest, resource: str, action: str = "access") -> None:
        """Log a permission denied event."""
        client_ip = AuthRateLimitMiddleware._get_client_ip(AuthRateLimitMiddleware, request)
        user = getattr(request, "user", None)
        username = user.username if user and user.is_authenticated else "anonymous"

        logger.warning(
            f"Permission denied - IP: {client_ip}, User: {username}, " f"Resource: {resource}, Action: {action}"
        )

    @staticmethod
    def log_suspicious_activity(request: HttpRequest, activity_type: str, details: str = "") -> None:
        """Log suspicious activity."""
        client_ip = AuthRateLimitMiddleware._get_client_ip(AuthRateLimitMiddleware, request)
        user = getattr(request, "user", None)
        username = user.username if user and user.is_authenticated else "anonymous"

        capture_and_log(
            RuntimeError(f'Suspicious activity detected - Type: {activity_type}'),
            f'Suspicious activity - IP: {client_ip}, User: {username}, Details: {details}',
            source='security',
            severity='high',
        )
