import json
import logging
from typing import Optional

from .. import ConfigProvider

logger = logging.getLogger(__name__)

_DEFAULT_NAMESPACE = "edpconfig"


class RedisCachingConfigProvider(ConfigProvider):
    """Read-through Redis cache wrapping a single leaf ``ConfigProvider``.

    On ``get_configuration`` it first looks up the inner provider's
    :meth:`ConfigProvider.cache_key` under ``{namespace}:{cache_key}``. A hit is
    returned directly; a miss reads the wrapped provider and stores the result
    with ``ttl_seconds`` expiry.

    Caching is best-effort: any Redis error (connection, timeout, auth) is logged
    and the call falls through to the wrapped provider so a Redis outage cannot
    break configuration retrieval. Providers whose ``cache_key`` is ``None`` are
    passed through without touching Redis.
    """

    def __init__(
        self,
        inner: ConfigProvider,
        redis_client,
        ttl_seconds: int,
        namespace: str = _DEFAULT_NAMESPACE,
    ) -> None:
        self._inner = inner
        self._redis = redis_client
        self._ttl = ttl_seconds
        self._namespace = namespace

    def get_configuration(self) -> dict:
        key = self._build_key()
        if key is None:
            return self._inner.get_configuration()

        cached = self._read(key)
        if cached is not None:
            return cached

        config = self._inner.get_configuration()
        self._write(key, config)
        return config

    def _build_key(self) -> Optional[str]:
        inner_key = self._inner.cache_key()
        if not inner_key:
            return None
        return f"{self._namespace}:{inner_key}"

    def _read(self, key: str) -> Optional[dict]:
        try:
            cached = self._redis.get(key)
        except Exception:
            logger.warning("Redis GET failed for %s; reading from backend", key, exc_info=True)
            return None

        if cached is None:
            return None

        try:
            return json.loads(cached)
        except (ValueError, TypeError):
            logger.warning("Discarding corrupt cached value for %s", key)
            return None

    def _write(self, key: str, config: dict) -> None:
        try:
            self._redis.setex(key, self._ttl, json.dumps(config))
        except Exception:
            logger.warning("Redis SETEX failed for %s; value not cached", key, exc_info=True)
