"""
mizan.cache — Origin-side cache implementing the Mizan cache protocol.

Simple key-value cache with HMAC-derived keys. No reverse indexes.
Scoped purge recomputes the key and deletes directly.
Broad purge uses key-prefix scan (rare operation).

Usage:
    from mizan.cache import get_cache, cache_get, cache_put, cache_purge
"""

from __future__ import annotations

import logging
import threading
from typing import Any

from mizan_core.cache.backend import CacheBackend, MemoryCache, RedisCache
from mizan_core.cache.keys import derive_cache_key, CONTEXT_KEY_PREFIX

logger = logging.getLogger("mizan.cache")

_cache_instance: CacheBackend | None = None
_initialized = False
_init_lock = threading.Lock()


def get_cache() -> CacheBackend | None:
    """
    Get the configured cache backend, or None if caching is disabled.
    Thread-safe.
    """
    global _cache_instance, _initialized
    if _initialized:
        return _cache_instance

    with _init_lock:
        if _initialized:
            return _cache_instance

        _initialized = True
        try:
            from mizan.setup.settings import get_settings
            settings = get_settings()

            if settings.cache_secret and settings.cache_redis_url:
                _cache_instance = RedisCache(settings.cache_redis_url)
                logger.info("Mizan cache enabled (Redis: %s)", settings.cache_redis_url)
            elif settings.cache_secret and not settings.cache_redis_url:
                logger.warning(
                    "MIZAN_CACHE_SECRET is set but MIZAN_CACHE_REDIS_URL is missing. "
                    "Cache is disabled."
                )
            elif settings.cache_redis_url and not settings.cache_secret:
                logger.warning(
                    "MIZAN_CACHE_REDIS_URL is set but MIZAN_CACHE_SECRET is missing. "
                    "Cache is disabled."
                )
        except Exception:
            logger.warning("Failed to initialize Mizan cache", exc_info=True)
            _cache_instance = None

    return _cache_instance


def set_cache(backend: CacheBackend | None) -> None:
    """Override the cache backend. For testing."""
    global _cache_instance, _initialized
    _cache_instance = backend
    _initialized = True


def reset_cache() -> None:
    """Reset to uninitialized state. For testing teardown."""
    global _cache_instance, _initialized
    _cache_instance = None
    _initialized = False


def cache_get(
    secret: str,
    backend: CacheBackend,
    context: str,
    params: dict[str, Any],
    user_id: str | None = None,
    rev: int = 0,
) -> bytes | None:
    """Look up a cached context response."""
    key = derive_cache_key(secret, context, params, user_id, rev)
    return backend.get(key)


def cache_put(
    secret: str,
    backend: CacheBackend,
    context: str,
    params: dict[str, Any],
    value: bytes,
    user_id: str | None = None,
    rev: int = 0,
) -> None:
    """Store a context response in the cache."""
    key = derive_cache_key(secret, context, params, user_id, rev)
    backend.set(key, value)


def cache_purge(
    backend: CacheBackend,
    context: str,
    params: dict[str, Any] | None = None,
    secret: str | None = None,
    user_id: str | None = None,
    rev: int = 0,
) -> int:
    """
    Purge cached entries for a context.

    Scoped purge (params provided): recomputes the HMAC key and deletes
    it directly. One DELETE, no index needed.

    Broad purge (no params): scans by key prefix "ctx:{context}:*".
    This is a rare operation (Tier 3 fallback in invalidation).
    """
    if params is not None and len(params) > 0 and secret:
        key = derive_cache_key(secret, context, params, user_id, rev)
        return 1 if backend.delete(key) else 0
    else:
        prefix = f"{CONTEXT_KEY_PREFIX}{context}:"
        return backend.delete_by_prefix(prefix)


__all__ = [
    "CacheBackend",
    "MemoryCache",
    "RedisCache",
    "get_cache",
    "set_cache",
    "reset_cache",
    "cache_get",
    "cache_put",
    "cache_purge",
]
