"""
ListenKey 缓存管理：以账号为单位缓存 listenKey，支持有效期管理和自动更新。

⚠️ 重要说明：
- 每个账号（account_id）对应一套独立的 API Key，listenKey 与 API Key 绑定
- 不同账号之间不能共用 listenKey（每个账号有独立的 API Key）
- 同一个账号的多个进程/实例可以共用 listenKey（因为它们使用相同的 API Key）
- Redis 键格式：listen_key:{account_id}，确保不同账号的 listenKey 完全隔离
"""
import asyncio
import json
import logging
import time
from typing import Optional, Dict, Any

logger = logging.getLogger(__name__)

# Redis 键格式：listen_key:{account_id}
# ⚠️ 每个 account_id 对应一套独立的 API Key，listenKey 与 API Key 绑定，不同账号之间完全隔离
KEY_PREFIX = "listen_key:"
# listenKey 有效期：50 分钟（币安文档：60 分钟无 keepalive 会失效，我们提前 10 分钟更新）
LISTEN_KEY_TTL_SEC = 50 * 60  # 50 分钟
# 提前更新时间：在到期前 5 分钟更新
RENEW_BEFORE_EXPIRE_SEC = 5 * 60  # 5 分钟


class ListenKeyCache:
    """
    ListenKey 缓存管理器：以账号为单位缓存 listenKey，支持有效期管理和自动更新。
    
    ⚠️ 重要：
    - 每个账号（account_id）有独立的 listenKey 缓存
    - 不同账号之间不会共用 listenKey（每个账号对应独立的 API Key）
    - 同一个账号的多个进程/实例可以共用 listenKey（使用相同的 API Key）
    """
    
    def __init__(self, redis_cache: Any):
        """
        初始化 ListenKey 缓存管理器
        
        Args:
            redis_cache: Redis 缓存实例
        """
        self.redis_cache = redis_cache
        self._local_cache: Dict[int, Dict[str, Any]] = {}  # 本地缓存：{account_id: {listen_key, expires_at}}
        self._locks: Dict[int, asyncio.Lock] = {}  # 每个账号的锁，避免并发创建
    
    def _get_lock(self, account_id: int) -> asyncio.Lock:
        """获取账号的锁（用于避免并发创建 listenKey）"""
        if account_id not in self._locks:
            self._locks[account_id] = asyncio.Lock()
        return self._locks[account_id]
    
    def _get_redis_key(self, account_id: int) -> str:
        """
        获取 Redis 键
        
        ⚠️ 重要：每个 account_id 对应独立的 Redis 键，确保不同账号的 listenKey 完全隔离
        格式：listen_key:{account_id}
        
        Args:
            account_id: 账号 ID（每个账号对应一套独立的 API Key）
        
        Returns:
            Redis 键字符串
        """
        return f"{KEY_PREFIX}{account_id}"
    
    async def get_listen_key(self, account_id: int, client: Any) -> Optional[str]:
        """
        获取账号的 listenKey（从缓存读取或创建新的）
        
        ⚠️ 重要：
        - 每个 account_id 对应一套独立的 API Key，listenKey 与 API Key 绑定
        - 不同账号之间不会共用 listenKey（每个账号有独立的缓存键）
        - 同一个账号的多个进程/实例可以共用 listenKey（因为它们使用相同的 API Key）
        
        Args:
            account_id: 账号 ID（每个账号对应一套独立的 API Key）
            client: BinanceClient 实例（用于创建 listenKey，client 的 API Key 必须与 account_id 匹配）
        
        Returns:
            listenKey，失败返回 None
        """
        account_id = int(account_id)
        
        # ⚠️ 验证：确保 client 的 API Key 与 account_id 匹配（通过 account_id 隔离，不同账号不会共用）
        # 注意：这里不直接验证 API Key，而是通过 account_id 来隔离，因为：
        # 1. account_id 在系统启动时就已经确定（通过环境变量或配置）
        # 2. 每个 account_id 对应一套独立的 API Key（在数据库 accounts 表中）
        # 3. BinanceClient 在创建时已经使用了对应 account_id 的 API Key
        
        # 1. 先检查本地缓存
        cached = self._local_cache.get(account_id)
        if cached:
            expires_at = cached.get('expires_at', 0)
            if time.time() < expires_at:
                listen_key = cached.get('listen_key')
                if listen_key:
                    logger.debug(f"ListenKeyCache: 从本地缓存获取账号 {account_id} 的 listenKey")
                    return listen_key
            else:
                # 本地缓存已过期，清除
                self._local_cache.pop(account_id, None)
        
        # 2. 从 Redis 读取（多进程共享）
        if self.redis_cache:
            try:
                redis_key = self._get_redis_key(account_id)
                cached_data = await self.redis_cache.get(redis_key)
                if cached_data:
                    # RedisCache.get() 已经自动 JSON 解析，直接使用
                    if isinstance(cached_data, dict):
                        listen_key = cached_data.get('listen_key')
                        expires_at = cached_data.get('expires_at', 0)
                        if listen_key and time.time() < expires_at:
                            # 更新本地缓存
                            self._local_cache[account_id] = {
                                'listen_key': listen_key,
                                'expires_at': expires_at
                            }
                            logger.debug(f"ListenKeyCache: 从 Redis 缓存获取账号 {account_id} 的 listenKey")
                            return listen_key
            except Exception as e:
                logger.debug(f"ListenKeyCache: 从 Redis 读取失败: {e}")
        
        # 3. 缓存未命中或已过期，需要创建新的
        async with self._get_lock(account_id):
            # 双重检查：可能其他协程已经创建了
            cached = self._local_cache.get(account_id)
            if cached and time.time() < cached.get('expires_at', 0):
                return cached.get('listen_key')
            
            # 创建新的 listenKey（每个账号独立，client 的 API Key 必须与 account_id 匹配）
            logger.info(f"ListenKeyCache: 为账号 {account_id} 创建新的 listenKey...")
            listen_key = await client.create_futures_listen_key(prefer_ws=True, max_retries=3)
            if not listen_key:
                logger.warning(f"ListenKeyCache: 账号 {account_id} 创建 listenKey 失败")
                return None
            
            # 计算过期时间（50 分钟后）
            expires_at = time.time() + LISTEN_KEY_TTL_SEC
            
            # 更新本地缓存
            self._local_cache[account_id] = {
                'listen_key': listen_key,
                'expires_at': expires_at
            }
            
            # 写入 Redis（多进程共享）
            if self.redis_cache:
                try:
                    redis_key = self._get_redis_key(account_id)
                    cache_data = {
                        'listen_key': listen_key,
                        'expires_at': expires_at,
                        'created_at': time.time(),
                        'account_id': account_id
                    }
                    # RedisCache.set() 会自动 JSON 序列化，直接传入 dict
                    # Redis TTL 设置为 55 分钟（略长于我们的有效期，确保数据不会提前被删除）
                    await self.redis_cache.set(redis_key, cache_data, ttl=55 * 60)
                    logger.info(f"ListenKeyCache: 账号 {account_id} 的 listenKey 已缓存（有效期至 {expires_at:.0f}）")
                except Exception as e:
                    logger.warning(f"ListenKeyCache: 写入 Redis 失败: {e}")
            
            return listen_key
    
    async def renew_listen_key(self, account_id: int, client: Any, listen_key: str) -> Optional[str]:
        """
        更新 listenKey（keepalive 或创建新的）
        
        ⚠️ 重要：
        - 每个 account_id 的 listenKey 独立更新，不会影响其他账号
        - listenKey 与 API Key 绑定，不同账号的 listenKey 不能混用
        
        Args:
            account_id: 账号 ID（每个账号对应一套独立的 API Key）
            client: BinanceClient 实例（API Key 必须与 account_id 匹配）
            listen_key: 当前的 listenKey（必须属于该 account_id）
        
        Returns:
            更新后的 listenKey（可能是同一个或新的），失败返回 None
        """
        account_id = int(account_id)
        
        # 先尝试 keepalive
        ok, code_1125 = await client.keepalive_futures_listen_key(listen_key, prefer_ws=True)
        if ok:
            # keepalive 成功，更新缓存有效期
            expires_at = time.time() + LISTEN_KEY_TTL_SEC
            self._local_cache[account_id] = {
                'listen_key': listen_key,
                'expires_at': expires_at
            }
            
            # 更新 Redis
            if self.redis_cache:
                try:
                    redis_key = self._get_redis_key(account_id)
                    cache_data = {
                        'listen_key': listen_key,
                        'expires_at': expires_at,
                        'created_at': time.time(),
                        'account_id': account_id
                    }
                    # RedisCache.set() 会自动 JSON 序列化
                    await self.redis_cache.set(redis_key, cache_data, ttl=55 * 60)
                    logger.debug(f"ListenKeyCache: 账号 {account_id} 的 listenKey keepalive 成功，已更新缓存")
                except Exception as e:
                    logger.debug(f"ListenKeyCache: 更新 Redis 失败: {e}")
            
            return listen_key
        
        # keepalive 失败（-1125 或网络错误），需要创建新的
        logger.info(f"ListenKeyCache: 账号 {account_id} 的 listenKey keepalive 失败，创建新的...")
        return await self.get_listen_key(account_id, client)
    
    async def should_renew(self, account_id: int) -> bool:
        """
        检查是否需要更新 listenKey（在到期前 5 分钟）
        
        Args:
            account_id: 账号 ID
        
        Returns:
            如果需要更新返回 True，否则返回 False
        """
        account_id = int(account_id)
        
        # 检查本地缓存
        cached = self._local_cache.get(account_id)
        if cached:
            expires_at = cached.get('expires_at', 0)
            if time.time() >= expires_at - RENEW_BEFORE_EXPIRE_SEC:
                return True
        
        # 检查 Redis 缓存
        if self.redis_cache:
            try:
                redis_key = self._get_redis_key(account_id)
                cached_data = await self.redis_cache.get(redis_key)
                if cached_data and isinstance(cached_data, dict):
                    expires_at = cached_data.get('expires_at', 0)
                    if time.time() >= expires_at - RENEW_BEFORE_EXPIRE_SEC:
                        return True
            except Exception:
                pass
        
        return False
    
    async def clear_cache(self, account_id: int):
        """清除账号的 listenKey 缓存"""
        account_id = int(account_id)
        self._local_cache.pop(account_id, None)
        if self.redis_cache:
            try:
                redis_key = self._get_redis_key(account_id)
                # RedisCache 可能没有 delete 方法，尝试直接调用 redis
                if hasattr(self.redis_cache, 'redis') and self.redis_cache.redis:
                    await self.redis_cache.redis.delete(redis_key)
                elif hasattr(self.redis_cache, 'delete'):
                    await self.redis_cache.delete(redis_key)
                logger.debug(f"ListenKeyCache: 已清除账号 {account_id} 的 listenKey 缓存")
            except Exception as e:
                logger.debug(f"ListenKeyCache: 清除 Redis 缓存失败: {e}")


# 全局 ListenKeyCache 实例
_listen_key_cache: Optional[ListenKeyCache] = None


def get_listen_key_cache(redis_cache: Any = None) -> Optional[ListenKeyCache]:
    """获取全局 ListenKeyCache 实例"""
    global _listen_key_cache
    if _listen_key_cache is None and redis_cache:
        _listen_key_cache = ListenKeyCache(redis_cache)
    return _listen_key_cache


def set_listen_key_cache(cache: ListenKeyCache):
    """设置全局 ListenKeyCache 实例"""
    global _listen_key_cache
    _listen_key_cache = cache
