# core/cache.py
import json as _stdlib_json
from contextlib import asynccontextmanager
from typing import Any, AsyncGenerator, Optional, Union

import logging
logger = logging.getLogger(__name__)

from redis.asyncio import Redis

# ===== 尝试使用你的 fff，否则回退到 stdlib =====
try:
    import orjson as json  
except ImportError:
    json = _stdlib_json  # type: ignore


# ===== Redis 客户端单例（可替换为你自己的）=====
class RedisClient:
    _instance: Optional["RedisClient"] = None
    _redis: Optional[Redis] = None

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

    async def init_redis(self, url: str = "redis://localhost:6379/0") -> None:
        if self._redis is None:
            self._redis = Redis.from_url(url, decode_responses=False)

    @property
    def client(self) -> Redis:
        if self._redis is None:
            raise RuntimeError("Redis not initialized. Call init_redis() first.")
        return self._redis

    async def close(self) -> None:
        if self._redis:
            await self._redis.close()
            self._redis = None


redis_client = RedisClient()


# ===== FastAPI 依赖注入 =====
async def get_redis() -> Redis:
    """FastAPI 依赖：获取 Redis 客户端"""
    return redis_client.client

async def invalidate_cache(key: str) -> bool:
    """
    主动删除缓存键。
    返回是否成功删除（Redis delete 返回被删除的 key 数量）。
    """
    try:
        result = await redis_client.client.delete(key)
        return result > 0
    except Exception as e:
        logger.exception(f"Failed to invalidate cache key: {key}", exc_info=e)
        return False

# ===== 缓存上下文管理器 =====
class CacheResult:
    __slots__ = ("key", "hit", "value", "_to_set", "_set_called")

    def __init__(self, key: str) -> None:
        self.key = key
        self.hit = False
        self.value: Any = None
        self._to_set: Any = None
        self._set_called = False

    def set(self, value: Any) -> None:
        """标记要缓存的值（可为 None）"""
        self._to_set = value
        self._set_called = True


@asynccontextmanager
async def cache_if(
    key: str,
    ttl: int = 3600,
    redis: Optional[Redis] = None,
) -> AsyncGenerator[CacheResult, None]:
    """
    异步缓存上下文管理器，支持 None 值缓存（防穿透）。

    Args:
        key: 缓存键
        ttl: 正常值缓存时间（秒）
        redis: 可选 Redis 客户端（用于测试或自定义）

    Usage:
        async with cache_if("report:123") as cache:
            if cache.hit:
                return cache.value
            result = await compute()
            cache.set(result)  # result 可为 None
    """
    result = CacheResult(key)
    client = redis or redis_client.client

    # 尝试读缓存
    try:
        cached_val = await client.get(key)
        if cached_val is not None:
            # 解码
            if cached_val == b"__NULL__":
                result.hit = True
                result.value = None
            else:
                result.hit = True
                result.value = json.loads(cached_val)
    except Exception:
        # Redis 不可用，降级（不中断主流程）
        logger.exception(f"Redis get error for key: {key}")
        pass

    yield result

    # 写缓存（仅当调用了 set()）
    if result._set_called:
        try:
            if result._to_set is None:
                val = b"__NULL__"
                ex = 60  # 空值短 TTL
            else:
                # 注意：orjson.dumps 返回 bytes，stdlib 返回 str → 统一转 bytes
                serialized = json.dumps(result._to_set)
                val = serialized if isinstance(serialized, bytes) else serialized.encode("utf-8")
                ex = ttl
            await client.setex(key, ex, val)
        except Exception:
            # 写缓存失败，不影响主流程
            pass


# ===== 装饰器版（可选补充）=====
from functools import wraps
import asyncio
import hashlib

def cached(ttl: int = 3600):
    """函数缓存装饰器（使用 cache_if）"""
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            # 生成 key（简单版，可替换为更 robust 的）
            key_data = str(args) + str(sorted(kwargs.items()))
            key = f"cached:{func.__name__}:{hashlib.md5(key_data.encode()).hexdigest()}"
            logger.debug(f"Cache key: {key}")
            
            async with cache_if(key, ttl) as cache:
                if cache.hit:
                    logger.debug(f"Cache hit for key: {key}")
                    return cache.value
                result = await func(*args, **kwargs)
                cache.set(result)
                return result
        return wrapper
    return decorator