"""
Token 黑名单服务

提供 Token 撤销（黑名单）功能，用于：
- 用户登出时使当前 Token 失效
- 密码重置后使该用户所有 Token 失效
- 管理员强制撤销用户 Token

支持 Redis（生产环境）和内存存储（开发环境）。
"""

import logging
from typing import Optional, Set
from datetime import datetime, timezone

from src.config.settings import REDIS_ENABLED, REDIS_URL
from src.auth.jwt import decode_token_unsafe, get_token_expiry, ACCESS_TOKEN_EXPIRE_MINUTES, REFRESH_TOKEN_EXPIRE_DAYS

logger = logging.getLogger(__name__)

# 内存存储回退（开发环境）
_token_blacklist: Set[str] = set()
_user_revoked_tokens: dict = {}  # {user_id: revoked_at_timestamp}


class TokenBlacklistService:
    """Token 黑名单服务"""

    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def _get_redis_client(self):
        """获取 Redis 客户端"""
        if REDIS_ENABLED:
            try:
                import redis.asyncio as aioredis
                return aioredis.from_url(REDIS_URL, encoding="utf-8", decode_responses=True)
            except Exception as e:
                logger.warning(f"Redis connection failed: {e}")
        return None

    def _get_token_jti(self, token: str) -> Optional[str]:
        """从 token 中提取 jti (JWT ID)"""
        payload = decode_token_unsafe(token)
        if payload:
            return payload.get("jti")
        return None

    def _get_token_user_id(self, token: str) -> Optional[str]:
        """从 token 中提取用户 ID"""
        payload = decode_token_unsafe(token)
        if payload:
            return payload.get("sub")
        return None

    def _get_token_expiry_seconds(self, token: str) -> int:
        """计算 token 剩余有效时间（秒）"""
        expiry = get_token_expiry(token)
        if expiry:
            now = datetime.now(timezone.utc)
            if expiry > now:
                return int((expiry - now).total_seconds())
        # 默认返回配置的过期时间
        return ACCESS_TOKEN_EXPIRE_MINUTES * 60

    async def revoke_token(self, token: str, reason: str = "logout") -> bool:
        """
        撤销单个 Token（加入黑名单）

        Args:
            token: JWT token
            reason: 撤销原因（logout, password_reset, admin_revoke）

        Returns:
            是否成功撤销
        """
        jti = self._get_token_jti(token)
        if not jti:
            logger.warning("Cannot revoke token: jti not found")
            return False

        expiry_seconds = self._get_token_expiry_seconds(token)

        # 尝试使用 Redis
        redis_client = self._get_redis_client()
        if redis_client:
            try:
                key = f"token_blacklist:{jti}"
                await redis_client.setex(key, expiry_seconds, reason)
                await redis_client.close()
                logger.debug(f"Token revoked in Redis: {jti}")
                return True
            except Exception as e:
                logger.warning(f"Redis revoke failed, using memory: {e}")

        # 内存存储回退
        _token_blacklist.add(jti)
        logger.debug(f"Token revoked in memory: {jti}")
        return True

    async def revoke_all_user_tokens(self, user_id: str, reason: str = "password_reset") -> bool:
        """
        撤销用户的所有 Token

        通过记录用户级别撤销时间戳实现。
        签发时间早于该时间戳的 Token 都将被视为无效。

        Args:
            user_id: 用户 ID
            reason: 撤销原因

        Returns:
            是否成功撤销
        """
        revoked_at = datetime.now(timezone.utc).timestamp()

        # 尝试使用 Redis
        redis_client = self._get_redis_client()
        if redis_client:
            try:
                key = f"user_revoked:{user_id}"
                # 存储用户撤销时间戳，有效期与 refresh token 一致
                await redis_client.setex(
                    key,
                    REFRESH_TOKEN_EXPIRE_DAYS * 24 * 3600,
                    str(revoked_at)
                )
                await redis_client.close()
                logger.info(f"All tokens revoked for user {user_id} in Redis")
                return True
            except Exception as e:
                logger.warning(f"Redis user revoke failed, using memory: {e}")

        # 内存存储回退
        _user_revoked_tokens[user_id] = revoked_at
        logger.info(f"All tokens revoked for user {user_id} in memory")
        return True

    async def is_token_revoked(self, token: str) -> bool:
        """
        检查 Token 是否已被撤销

        Args:
            token: JWT token

        Returns:
            是否已被撤销
        """
        jti = self._get_token_jti(token)
        user_id = self._get_token_user_id(token)

        if not jti or not user_id:
            return True  # 无效 token 视为已撤销

        # 尝试使用 Redis
        redis_client = self._get_redis_client()
        if redis_client:
            try:
                # 检查是否在黑名单中
                key = f"token_blacklist:{jti}"
                is_blacklisted = await redis_client.exists(key)

                if is_blacklisted:
                    await redis_client.close()
                    return True

                # 检查用户是否已撤销所有 token
                user_revoked_key = f"user_revoked:{user_id}"
                revoked_at_str = await redis_client.get(user_revoked_key)
                await redis_client.close()

                if revoked_at_str:
                    revoked_at = float(revoked_at_str)
                    # 获取 token 签发时间
                    payload = decode_token_unsafe(token)
                    if payload:
                        iat = payload.get("iat", 0)
                        if iat < revoked_at:
                            return True

                return False

            except Exception as e:
                logger.warning(f"Redis check failed, using memory: {e}")

        # 内存存储回退
        if jti in _token_blacklist:
            return True

        if user_id in _user_revoked_tokens:
            revoked_at = _user_revoked_tokens[user_id]
            payload = decode_token_unsafe(token)
            if payload:
                iat = payload.get("iat", 0)
                if iat < revoked_at:
                    return True

        return False

    async def cleanup_expired_tokens(self) -> int:
        """
        清理过期的黑名单记录（仅内存模式需要）

        Returns:
            清理的记录数量
        """
        if REDIS_ENABLED:
            # Redis 自动过期，无需清理
            return 0

        # 内存模式：清理已过期的记录
        cleaned = 0
        now = datetime.now(timezone.utc).timestamp()

        # 清理用户撤销记录（超过 refresh token 有效期）
        expired_users = []
        max_age = REFRESH_TOKEN_EXPIRE_DAYS * 24 * 3600
        for user_id, revoked_at in _user_revoked_tokens.items():
            if now - revoked_at > max_age:
                expired_users.append(user_id)

        for user_id in expired_users:
            del _user_revoked_tokens[user_id]
            cleaned += 1

        # Note: 单个 token 黑名单无法自动清理（因为没有过期时间信息）
        # 在生产环境应该使用 Redis

        if cleaned > 0:
            logger.info(f"Cleaned {cleaned} expired user revocation records")

        return cleaned

    async def get_revocation_info(self, token: str) -> Optional[dict]:
        """
        获取 Token 撤销信息

        Args:
            token: JWT token

        Returns:
            撤销信息或 None
        """
        jti = self._get_token_jti(token)
        if not jti:
            return None

        # 尝试使用 Redis
        redis_client = self._get_redis_client()
        if redis_client:
            try:
                key = f"token_blacklist:{jti}"
                reason = await redis_client.get(key)
                await redis_client.close()

                if reason:
                    return {
                        "revoked": True,
                        "reason": reason,
                        "jti": jti
                    }
            except Exception:
                pass

        # 检查内存存储
        if jti in _token_blacklist:
            return {
                "revoked": True,
                "reason": "unknown",
                "jti": jti
            }

        return None


# 全局服务实例
token_blacklist_service = TokenBlacklistService()
