"""Redis-based token blacklist for JWT token revocation."""

# Standard library imports
import hashlib
import json
import os
from datetime import datetime, timezone
from typing import Any, Dict, Optional

# Third-party imports
import redis
import structlog

# Local imports
from .session_storage import session_storage

logger = structlog.get_logger(__name__)

# Redis configuration constants
REDIS_SOCKET_TIMEOUT = 5
REDIS_DEFAULT_HOST = "localhost"
REDIS_DEFAULT_PORT = "6379"
REDIS_DEFAULT_DB = "0"

# Blacklist constants
BLACKLIST_KEY_PREFIX = "blacklist:"
TOKEN_HASH_PREVIEW_LENGTH = 16
DEFAULT_REVOCATION_REASON = "logout"
DEFAULT_FORCE_LOGOUT_REASON = "force_logout"


class TokenBlacklist:
    """Redis-based blacklist for JWT token revocation."""

    def __init__(self) -> None:
        """Initialize token blacklist with Redis connection."""
        self.redis_client = redis.Redis(
            host=os.getenv("REDIS_HOST", REDIS_DEFAULT_HOST),
            port=int(os.getenv("REDIS_PORT", REDIS_DEFAULT_PORT)),
            password=os.getenv("REDIS_PASSWORD"),
            db=int(os.getenv("REDIS_DB", REDIS_DEFAULT_DB)),
            decode_responses=True,
            socket_connect_timeout=REDIS_SOCKET_TIMEOUT,
            socket_timeout=REDIS_SOCKET_TIMEOUT,
            retry_on_timeout=True,
        )

        try:
            self.redis_client.ping()
            logger.info("Token blacklist Redis connection established")
        except redis.ConnectionError as e:
            logger.error(
                "Failed to connect to Redis for token blacklist", error=str(e)
            )
            raise
    
    def _get_token_hash(self, token: str) -> str:
        """Get token hash for use as Redis key.

        Args:
            token: JWT token

        Returns:
            SHA-256 hash of token
        """
        return hashlib.sha256(token.encode("utf-8")).hexdigest()
    
    def revoke_token(
        self,
        token: str,
        reason: str = DEFAULT_REVOCATION_REASON,
        revoked_by: Optional[str] = None,
    ) -> bool:
        """Revoke token (add to blacklist).

        Args:
            token: JWT token to revoke
            reason: Revocation reason
            revoked_by: Username who revoked the token

        Returns:
            True if token successfully revoked
        """
        try:
            from .utils import get_token_expiry_info

            expiry_info = get_token_expiry_info(token)

            if not expiry_info:
                logger.warning(
                    "Cannot revoke token: invalid or expired", reason=reason
                )
                return False

            token_hash = self._get_token_hash(token)

            now = datetime.now(timezone.utc)
            blacklist_data = {
                "token_hash": token_hash,
                "reason": reason,
                "revoked_at": now.isoformat(),
                "revoked_by": revoked_by,
                "expires_at": expiry_info["expires_at"],
                "username": expiry_info.get("username"),
                "token_type": expiry_info.get("token_type", "access"),
            }

            expires_at = datetime.fromisoformat(expiry_info["expires_at"])
            if expires_at.tzinfo is None:
                expires_at = expires_at.replace(tzinfo=timezone.utc)
            ttl_seconds = int((expires_at - now).total_seconds())

            if ttl_seconds <= 0:
                logger.debug(
                    "Token already expired, no need to blacklist",
                    username=expiry_info.get("username"),
                )
                return True

            self.redis_client.setex(
                f"{BLACKLIST_KEY_PREFIX}{token_hash}",
                ttl_seconds,
                json.dumps(blacklist_data),
            )

            logger.info(
                "Token revoked successfully",
                token_hash=token_hash[:TOKEN_HASH_PREVIEW_LENGTH] + "...",
                username=expiry_info.get("username"),
                reason=reason,
                revoked_by=revoked_by,
                ttl_seconds=ttl_seconds,
            )

            return True

        except Exception as e:
            logger.error("Failed to revoke token", error=str(e), reason=reason)
            return False
    
    def is_token_revoked(self, token: str) -> bool:
        """Check if token is revoked.

        Args:
            token: JWT token to check

        Returns:
            True if token is revoked
        """
        try:
            token_hash = self._get_token_hash(token)
            blacklist_data = self.redis_client.get(f"{BLACKLIST_KEY_PREFIX}{token_hash}")

            if blacklist_data:
                data = json.loads(blacklist_data)
                logger.debug(
                    "Token is revoked",
                    token_hash=token_hash[:TOKEN_HASH_PREVIEW_LENGTH] + "...",
                    reason=data.get("reason"),
                    revoked_at=data.get("revoked_at"),
                )
                return True

            return False

        except Exception as e:
            logger.error(
                "Failed to check token revocation status", error=str(e)
            )
            return False
    
    def revoke_user_tokens(
        self,
        username: str,
        reason: str = DEFAULT_FORCE_LOGOUT_REASON,
        revoked_by: Optional[str] = None,
    ) -> int:
        """Revoke all user tokens.

        Args:
            username: Username
            reason: Revocation reason
            revoked_by: Who revoked the tokens

        Returns:
            Number of revoked tokens
        """
        try:
            session = session_storage.get_session_by_username(username)

            if not session:
                logger.debug(
                    "No active session found for user", username=username
                )
                return 0

            session_storage.delete_user_sessions(username)

            logger.info(
                "User tokens revoked",
                username=username,
                reason=reason,
                revoked_by=revoked_by,
            )

            return 1

        except Exception as e:
            logger.error(
                "Failed to revoke user tokens", username=username, error=str(e)
            )
            return 0
    
    def get_blacklist_stats(self) -> Dict[str, Any]:
        """Get blacklist statistics.

        Returns:
            Statistics about revoked tokens
        """
        try:
            blacklist_keys = self.redis_client.keys(f"{BLACKLIST_KEY_PREFIX}*")

            reasons_count: Dict[str, int] = {}
            for key in blacklist_keys:
                data = self.redis_client.get(key)
                if data:
                    blacklist_data = json.loads(data)
                    reason = blacklist_data.get("reason", "unknown")
                    reasons_count[reason] = reasons_count.get(reason, 0) + 1

            return {
                "revoked_tokens": len(blacklist_keys),
                "reasons": reasons_count,
                "redis_memory_usage": (
                    self.redis_client.memory_usage(f"{BLACKLIST_KEY_PREFIX}*")
                    if blacklist_keys
                    else 0
                ),
            }

        except Exception as e:
            logger.error("Failed to get blacklist stats", error=str(e))
            return {
                "revoked_tokens": 0,
                "reasons": {},
                "redis_memory_usage": 0,
                "error": str(e),
            }

    def cleanup_expired_blacklist(self) -> int:
        """Clean up expired blacklist entries.

        Returns:
            Number of cleaned entries (always 0, Redis handles this automatically)
        """
        logger.debug(
            "Expired blacklist cleanup completed (Redis TTL handles this automatically)"
        )
        return 0


# Global instance for use in API
token_blacklist = TokenBlacklist()
