"""
Rate Limiting and Account Lockout Module.

Provides protection against brute force attacks by:
1. Tracking failed authentication attempts per user/IP
2. Implementing temporary account lockout
3. Automatic reset after lockout period
4. Configurable thresholds and durations
"""

import logging
from datetime import timedelta
from typing import Dict, Optional, Tuple

from django.core.cache import cache
from django.utils import timezone

logger = logging.getLogger(__name__)


class RateLimitConfig:
    """Configuration for rate limiting behavior."""

    def __init__(
        self,
        max_attempts: int = 5,
        lockout_duration: int = 900,  # 15 minutes
        reset_after: int = 3600,  # 1 hour
    ):
        """
        Initialize rate limit configuration.

        Args:
            max_attempts: Maximum failed attempts before lockout
            lockout_duration: How long to lock account (seconds)
            reset_after: When to reset counter if no attempts (seconds)
        """
        self.max_attempts = max_attempts
        self.lockout_duration = lockout_duration
        self.reset_after = reset_after


class RateLimiter:
    """
    Rate limiting implementation for authentication attempts.

    Implements per-identifier (username/IP) rate limiting with:
    - Automatic lockout after N failures
    - Time-based automatic unlock
    - Different action tracking (login, certificate upload, etc.)
    """

    def __init__(self, config: Optional[RateLimitConfig] = None):
        """
        Initialize rate limiter.

        Args:
            config: RateLimitConfig instance or use defaults
        """
        self.config = config or RateLimitConfig()

    def _get_cache_key(self, identifier: str, action: str) -> str:
        """
        Generate cache key for rate limit tracking.

        Args:
            identifier: Username or IP address
            action: Action type (e.g., 'eds_auth', 'cert_upload')

        Returns:
            Cache key string
        """
        return f"rate_limit_{action}_{identifier}"

    def check_rate_limit(
        self, identifier: str, action: str
    ) -> Tuple[bool, Dict[str, any]]:
        """
        Check if identifier is rate-limited for action.

        Args:
            identifier: Username or IP address
            action: Action type

        Returns:
            (is_allowed, info_dict) where info_dict contains:
            - locked: bool - is identifier currently locked
            - attempts: int - current attempt count
            - remaining_seconds: int - seconds until unlock (if locked)
            - can_retry: bool - can retry immediately
        """
        cache_key = self._get_cache_key(identifier, action)
        data = cache.get(
            cache_key, {"attempts": 0, "locked_until": None, "first_attempted": None}
        )

        now = timezone.now()

        # Check if lockout has expired
        if data["locked_until"] and now >= data["locked_until"]:
            logger.info(
                f"Rate limit lockout expired for {identifier} (action: {action})"
            )
            cache.delete(cache_key)
            return True, {
                "locked": False,
                "attempts": 0,
                "remaining_seconds": 0,
                "can_retry": True,
            }

        # Check if still locked
        if data["locked_until"] and now < data["locked_until"]:
            remaining = (data["locked_until"] - now).total_seconds()
            logger.warning(
                f"Rate limited: {identifier} (action: {action}) - "
                f"locked for {int(remaining)}s"
            )
            return False, {
                "locked": True,
                "attempts": data["attempts"],
                "remaining_seconds": int(remaining),
                "can_retry": False,
            }

        return True, {
            "locked": False,
            "attempts": data["attempts"],
            "remaining_seconds": 0,
            "can_retry": True,
        }

    def record_failed_attempt(self, identifier: str, action: str) -> Dict[str, any]:
        """
        Record a failed authentication/action attempt.

        Args:
            identifier: Username or IP address
            action: Action type

        Returns:
            Dict with current state: locked, attempts, locked_until
        """
        cache_key = self._get_cache_key(identifier, action)
        data = cache.get(
            cache_key, {"attempts": 0, "locked_until": None, "first_attempted": None}
        )

        data["attempts"] += 1
        now = timezone.now()

        if data["first_attempted"] is None:
            data["first_attempted"] = now

        logger.warning(
            f"Failed attempt {data['attempts']}/{self.config.max_attempts} "
            f"for {identifier} (action: {action})"
        )

        # Lock if exceeded max attempts
        if data["attempts"] >= self.config.max_attempts:
            data["locked_until"] = now + timedelta(seconds=self.config.lockout_duration)
            logger.error(
                f"Rate limit LOCKED: {identifier} (action: {action}) "
                f"until {data['locked_until']}"
            )

        cache.set(cache_key, data, self.config.reset_after)

        return {
            "locked": data["locked_until"] is not None,
            "attempts": data["attempts"],
            "locked_until": data["locked_until"],
        }

    def clear_failed_attempts(self, identifier: str, action: str) -> None:
        """
        Clear failed attempt counter (on successful action).

        Args:
            identifier: Username or IP address
            action: Action type
        """
        cache_key = self._get_cache_key(identifier, action)
        cache.delete(cache_key)
        logger.info(f"Rate limit cleared for {identifier} (action: {action})")

    def get_status(self, identifier: str, action: str) -> Dict[str, any]:
        """
        Get current rate limit status for identifier.

        Args:
            identifier: Username or IP address
            action: Action type

        Returns:
            Dict with status information
        """
        is_allowed, info = self.check_rate_limit(identifier, action)

        status = {
            "identifier": identifier,
            "action": action,
            "is_allowed": is_allowed,
            "locked": info["locked"],
            "attempts": info["attempts"],
            "max_attempts": self.config.max_attempts,
            "remaining_seconds": info.get("remaining_seconds", 0),
            "lockout_duration": self.config.lockout_duration,
        }

        return status

    def reset_all_for_identifier(self, identifier: str) -> int:
        """
        Reset rate limits for identifier across all actions.
        Useful for admin unlock operations.

        Args:
            identifier: Username or IP address

        Returns:
            Number of rate limit records cleared
        """
        actions = ["eds_auth", "cert_upload", "password_change", "email_verify"]
        cleared = 0

        for action in actions:
            cache_key = self._get_cache_key(identifier, action)
            if cache.get(cache_key):
                cache.delete(cache_key)
                cleared += 1
                logger.info(f"Rate limit reset for {identifier} (action: {action})")

        return cleared


def get_client_ip(request) -> str:
    """
    Extract client IP from Django request.

    Args:
        request: Django HttpRequest

    Returns:
        IP address string
    """
    x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
    if x_forwarded_for:
        ip = x_forwarded_for.split(",")[0].strip()
    else:
        ip = request.META.get("REMOTE_ADDR")
    return ip
