"""لایه Cache مبتنی بر Redis (async).

اگر Redis در دسترس نباشد (مثلاً در تست‌ها)، عملیات cache به‌صورت
graceful نادیده گرفته می‌شوند تا برنامه از کار نیفتد.

کلیدهای پیشنهادی:
    tenant:{tenant_id}:metadata
    tenant:{tenant_id}:features
    feature_access:{tenant_id}:{feature_key}
"""
from __future__ import annotations

from typing import Any

import redis.asyncio as aioredis

from app.core.config import settings
from app.core.logging import get_logger

logger = get_logger(__name__)


class CacheClient:
    """پوشش نازک روی redis.asyncio با تحمل خطا (fail-open)."""

    def __init__(self, url: str) -> None:
        self._url = url
        self._client: aioredis.Redis | None = None
        self._enabled = True

    @property
    def client(self) -> aioredis.Redis | None:
        if not self._enabled:
            return None
        if self._client is None:
            self._client = aioredis.from_url(
                self._url, encoding="utf-8", decode_responses=True
            )
        return self._client

    async def get(self, key: str) -> str | None:
        client = self.client
        if client is None:
            return None
        try:
            return await client.get(key)
        except Exception as exc:  # pragma: no cover - وابسته به محیط
            logger.warning("cache_get_failed", extra={"key": key, "error": str(exc)})
            return None

    async def set(self, key: str, value: str, ttl: int | None = None) -> None:
        client = self.client
        if client is None:
            return
        try:
            await client.set(key, value, ex=ttl)
        except Exception as exc:  # pragma: no cover
            logger.warning("cache_set_failed", extra={"key": key, "error": str(exc)})

    async def ttl(self, key: str) -> int | None:
        """ثانیه باقی‌مانده TTL — None اگر کلید نیست یا Redis غیرفعال."""
        client = self.client
        if client is None:
            return None
        try:
            remaining = await client.ttl(key)
            if remaining is None or remaining < 0:
                return None
            return int(remaining)
        except Exception as exc:  # pragma: no cover
            logger.warning("cache_ttl_failed", extra={"key": key, "error": str(exc)})
            return None

    async def delete(self, *keys: str) -> None:
        client = self.client
        if client is None or not keys:
            return
        try:
            await client.delete(*keys)
        except Exception as exc:  # pragma: no cover
            logger.warning("cache_delete_failed", extra={"keys": keys, "error": str(exc)})

    async def delete_pattern(self, pattern: str) -> None:
        """حذف کلیدها بر اساس الگو (برای invalidate کردن گروهی)."""
        client = self.client
        if client is None:
            return
        try:
            async for key in client.scan_iter(match=pattern):
                await client.delete(key)
        except Exception as exc:  # pragma: no cover
            logger.warning("cache_delete_pattern_failed", extra={"pattern": pattern, "error": str(exc)})

    async def ping(self) -> bool:
        client = self.client
        if client is None:
            return False
        try:
            return bool(await client.ping())
        except Exception:  # pragma: no cover
            return False

    async def close(self) -> None:
        if not self._enabled:
            self._client = None
            return
        if self._client is not None:
            try:
                await self._client.aclose()
            except Exception:  # pragma: no cover
                pass
            self._client = None

    def disable(self) -> None:
        """غیرفعال‌سازی cache (مثلاً در تست‌ها)."""
        self._enabled = False


# ---- کمک‌کننده‌های ساخت کلید (تا نام کلیدها یکسان بماند) ----
def tenant_metadata_key(tenant_id: str) -> str:
    return f"tenant:{tenant_id}:metadata"


def tenant_features_key(tenant_id: str) -> str:
    return f"tenant:{tenant_id}:features"


def feature_access_key(tenant_id: str, feature_key: str) -> str:
    return f"feature_access:{tenant_id}:{feature_key}"


cache = CacheClient(settings.redis_url)
