"""Brute-force protection for login endpoint."""

from typing import Any, Dict, Tuple

import structlog

from .rate_limiter import redis_rate_limiter

logger = structlog.get_logger(__name__)

# Backoff constants
MAX_BACKOFF_SECONDS = 300
MIN_FAILED_ATTEMPTS_FOR_BACKOFF = 2
EXPONENTIAL_BACKOFF_BASE = 2

# Default limits
DEFAULT_MAX_LOGIN_ATTEMPTS_PER_IP = 5
DEFAULT_MAX_LOGIN_ATTEMPTS_PER_USER = 10
DEFAULT_LOGIN_WINDOW_MINUTES = 15
DEFAULT_USER_LOCKOUT_MINUTES = 60

# Block types
BLOCK_TYPE_RATE_LIMIT = "rate_limit"
BLOCK_TYPE_IP_BLOCKED = "ip_blocked"
BLOCK_TYPE_USER_LOCKED = "user_locked"
BLOCK_TYPE_EXPONENTIAL_BACKOFF = "exponential_backoff"
BLOCK_TYPE_ALLOWED = "allowed"
BLOCK_TYPE_ERROR_FALLBACK = "error_fallback"

# Response messages
MSG_RATE_LIMIT_EXCEEDED = "Rate limit exceeded"
MSG_IP_BLOCKED = "Too many failed attempts from this IP"
MSG_USER_LOCKED = "User account temporarily locked"
MSG_LOGIN_ALLOWED = "Login allowed"
MSG_LOGIN_ALLOWED_ERROR = "Login allowed (protection error)"

# Default failure reason
DEFAULT_FAILURE_REASON = "invalid_credentials"

# Empty string for clearing
EMPTY_USERNAME = ""
EMPTY_IP = ""


class BruteForceProtection:
    """Protection against brute-force attacks on login endpoint."""

    def __init__(self) -> None:
        """Initialize brute-force protection."""
        self.max_login_attempts_per_ip = DEFAULT_MAX_LOGIN_ATTEMPTS_PER_IP
        self.max_login_attempts_per_user = DEFAULT_MAX_LOGIN_ATTEMPTS_PER_USER
        self.login_window_minutes = DEFAULT_LOGIN_WINDOW_MINUTES
        self.user_lockout_minutes = DEFAULT_USER_LOCKOUT_MINUTES
        self.exponential_backoff_base = EXPONENTIAL_BACKOFF_BASE

    def check_login_allowed(
        self, client_ip: str, username: str
    ) -> Tuple[bool, str, Dict[str, Any]]:
        """
        Check if login is allowed for given IP and user.

        Args:
            client_ip: Client IP address.
            username: Username.

        Returns:
            Tuple of (allowed: bool, reason: str, details: Dict[str, Any]).
        """
        try:
            allowed, headers = redis_rate_limiter.check_login_rate_limit(
                client_ip, username
            )

            if not allowed:
                return (
                    False,
                    MSG_RATE_LIMIT_EXCEEDED,
                    {
                        "type": BLOCK_TYPE_RATE_LIMIT,
                        "client_ip": client_ip,
                        "username": username,
                        "headers": headers,
                    },
                )

            failed_counts = redis_rate_limiter.get_failed_login_count(
                client_ip, username, self.user_lockout_minutes
            )

            if failed_counts["ip_failed_count"] >= self.max_login_attempts_per_ip:
                return (
                    False,
                    MSG_IP_BLOCKED,
                    {
                        "type": BLOCK_TYPE_IP_BLOCKED,
                        "client_ip": client_ip,
                        "failed_count": failed_counts["ip_failed_count"],
                        "max_attempts": self.max_login_attempts_per_ip,
                        "window_minutes": self.login_window_minutes,
                    },
                )

            if failed_counts["user_failed_count"] >= self.max_login_attempts_per_user:
                return (
                    False,
                    MSG_USER_LOCKED,
                    {
                        "type": BLOCK_TYPE_USER_LOCKED,
                        "username": username,
                        "failed_count": failed_counts["user_failed_count"],
                        "max_attempts": self.max_login_attempts_per_user,
                        "lockout_minutes": self.user_lockout_minutes,
                    },
                )

            backoff_seconds = self._calculate_backoff_time(
                client_ip, username, failed_counts
            )
            if backoff_seconds > 0:
                return (
                    False,
                    f"Please wait {backoff_seconds} seconds before next attempt",
                    {
                        "type": BLOCK_TYPE_EXPONENTIAL_BACKOFF,
                        "wait_seconds": backoff_seconds,
                        "client_ip": client_ip,
                        "username": username,
                    },
                )

            return (
                True,
                MSG_LOGIN_ALLOWED,
                {
                    "type": BLOCK_TYPE_ALLOWED,
                    "client_ip": client_ip,
                    "username": username,
                    "failed_counts": failed_counts,
                },
            )

        except Exception as e:
            logger.error(
                "Error checking login permission",
                client_ip=client_ip,
                username=username,
                error=str(e),
            )
            return (
                True,
                MSG_LOGIN_ALLOWED_ERROR,
                {"type": BLOCK_TYPE_ERROR_FALLBACK, "error": str(e)},
            )

    def record_failed_login(
        self,
        client_ip: str,
        username: str,
        failure_reason: str = DEFAULT_FAILURE_REASON,
    ) -> None:
        """
        Record failed login attempt.

        Args:
            client_ip: Client IP address.
            username: Username.
            failure_reason: Failure reason.
        """
        try:
            redis_rate_limiter.record_failed_login(client_ip, username)

            logger.warning(
                "Failed login attempt recorded",
                client_ip=client_ip,
                username=username,
                failure_reason=failure_reason,
            )

        except Exception as e:
            logger.error(
                "Failed to record failed login attempt",
                client_ip=client_ip,
                username=username,
                error=str(e),
            )

    def record_successful_login(self, client_ip: str, username: str) -> None:
        """
        Record successful login (clear failed attempts).

        Args:
            client_ip: Client IP address.
            username: Username.
        """
        try:
            redis_rate_limiter.clear_failed_logins(client_ip, username)

            logger.info(
                "Successful login recorded, failed attempts cleared",
                client_ip=client_ip,
                username=username,
            )

        except Exception as e:
            logger.error(
                "Failed to record successful login",
                client_ip=client_ip,
                username=username,
                error=str(e),
            )

    def _calculate_backoff_time(
        self, client_ip: str, username: str, failed_counts: Dict[str, int]
    ) -> int:
        """
        Calculate wait time for exponential backoff.

        Args:
            client_ip: Client IP address.
            username: Username.
            failed_counts: Failed attempt counts.

        Returns:
            Wait time in seconds.
        """
        try:
            max_failed = max(
                failed_counts["ip_failed_count"],
                failed_counts["user_failed_count"],
            )

            if max_failed <= MIN_FAILED_ATTEMPTS_FOR_BACKOFF:
                return 0

            backoff_seconds = min(
                self.exponential_backoff_base
                ** (max_failed - MIN_FAILED_ATTEMPTS_FOR_BACKOFF),
                MAX_BACKOFF_SECONDS,
            )

            return backoff_seconds

        except Exception as e:
            logger.error("Error calculating backoff time", error=str(e))
            return 0

    def get_protection_stats(self) -> Dict[str, Any]:
        """
        Get brute-force protection statistics.

        Returns:
            Protection statistics dictionary.
        """
        try:
            rate_limit_stats = redis_rate_limiter.get_rate_limit_stats()

            return {
                "max_login_attempts_per_ip": self.max_login_attempts_per_ip,
                "max_login_attempts_per_user": self.max_login_attempts_per_user,
                "login_window_minutes": self.login_window_minutes,
                "user_lockout_minutes": self.user_lockout_minutes,
                "exponential_backoff_base": self.exponential_backoff_base,
                "rate_limit_stats": rate_limit_stats,
            }

        except Exception as e:
            logger.error("Failed to get protection stats", error=str(e))
            return {"error": str(e)}

    def force_unlock_user(self, username: str, unlocked_by: str) -> bool:
        """
        Force unlock user (for administrators).

        Args:
            username: Username to unlock.
            unlocked_by: Who unlocked the user.

        Returns:
            True if unlock successful.
        """
        try:
            redis_rate_limiter.clear_failed_logins(EMPTY_IP, username)

            logger.info("User force unlocked", username=username, unlocked_by=unlocked_by)

            return True

        except Exception as e:
            logger.error(
                "Failed to force unlock user",
                username=username,
                unlocked_by=unlocked_by,
                error=str(e),
            )
            return False

    def force_unlock_ip(self, client_ip: str, unlocked_by: str) -> bool:
        """
        Force unlock IP (for administrators).

        Args:
            client_ip: IP address to unlock.
            unlocked_by: Who unlocked the IP.

        Returns:
            True if unlock successful.
        """
        try:
            redis_rate_limiter.clear_failed_logins(client_ip, EMPTY_USERNAME)

            logger.info(
                "IP force unlocked", client_ip=client_ip, unlocked_by=unlocked_by
            )

            return True

        except Exception as e:
            logger.error(
                "Failed to force unlock IP",
                client_ip=client_ip,
                unlocked_by=unlocked_by,
                error=str(e),
            )
            return False


brute_force_protection = BruteForceProtection()
