"""
Base cache interface for CacheStack.

All backends must implement the BaseCache abstract class.
"""

from __future__ import annotations

import asyncio
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional, Tuple


class CacheError(Exception):
    """Base exception for all CacheStack errors."""


class BackendUnavailableError(CacheError):
    """Raised when a cache backend cannot be reached."""


class SerializationError(CacheError):
    """Raised when serialization or deserialization fails."""


class BaseCache(ABC):
    """
    Abstract base class for all CacheStack backends.

    All methods are async. Use SyncCacheMixin for synchronous access.
    """

    @abstractmethod
    async def get(self, key: str) -> Optional[Any]:
        """
        Retrieve a value by key.

        Args:
            key: Cache key.

        Returns:
            Cached value or None if not found / expired.
        """

    @abstractmethod
    async def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
        """
        Store a value by key.

        Args:
            key: Cache key.
            value: Value to cache.
            ttl: Time-to-live in seconds. None means no expiry.
        """

    @abstractmethod
    async def delete(self, key: str) -> None:
        """
        Delete a key from the cache.

        Args:
            key: Cache key to delete.
        """

    @abstractmethod
    async def exists(self, key: str) -> bool:
        """
        Check if a key exists and is not expired.

        Args:
            key: Cache key.

        Returns:
            True if key exists, False otherwise.
        """

    @abstractmethod
    async def clear(self) -> None:
        """Clear all entries from the cache."""

    @abstractmethod
    async def stats(self) -> Dict[str, Any]:
        """
        Return cache statistics.

        Returns:
            Dict with at minimum: backend, hits, misses, errors.
        """

    async def get_many(self, keys: List[str]) -> Dict[str, Optional[Any]]:
        """
        Retrieve multiple values by key.

        Args:
            keys: List of cache keys.

        Returns:
            Dict mapping each key to its value (or None if missing).
        """
        results = await asyncio.gather(*[self.get(k) for k in keys])
        return dict(zip(keys, results))

    async def set_many(self, items: List[Tuple[str, Any, Optional[int]]]) -> None:
        """
        Store multiple key-value pairs.

        Args:
            items: List of (key, value, ttl) tuples.
        """
        await asyncio.gather(*[self.set(k, v, ttl) for k, v, ttl in items])

    async def delete_many(self, keys: List[str]) -> None:
        """
        Delete multiple keys.

        Args:
            keys: List of cache keys to delete.
        """
        await asyncio.gather(*[self.delete(k) for k in keys])


class SyncCacheMixin:
    """
    Mixin that provides synchronous wrappers around async BaseCache methods.

    Usage:
        class MySyncCache(SyncCacheMixin, MyAsyncBackend):
            pass

        cache = MySyncCache(...)
        cache.sync_get("key")
    """

    def sync_get(self, key: str) -> Optional[Any]:
        """Synchronous wrapper for get()."""
        return asyncio.run(self.get(key))  # type: ignore[attr-defined]

    def sync_set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
        """Synchronous wrapper for set()."""
        asyncio.run(self.set(key, value, ttl))  # type: ignore[attr-defined]

    def sync_delete(self, key: str) -> None:
        """Synchronous wrapper for delete()."""
        asyncio.run(self.delete(key))  # type: ignore[attr-defined]

    def sync_exists(self, key: str) -> bool:
        """Synchronous wrapper for exists()."""
        return asyncio.run(self.exists(key))  # type: ignore[attr-defined]

    def sync_clear(self) -> None:
        """Synchronous wrapper for clear()."""
        asyncio.run(self.clear())  # type: ignore[attr-defined]
