"""Decorator que cachea el resultado de una funcion en un store persistente."""

import asyncio
import functools
import hashlib
import json
from typing import Any, Callable


def _default_key(func: Callable, args: tuple, kwargs: dict) -> str:
    """Genera una cache key a partir del nombre de funcion y sus argumentos."""
    payload = json.dumps((func.__name__, args, sorted(kwargs.items())), default=str)
    return hashlib.sha256(payload.encode("utf-8")).hexdigest()


def cache_decorator(store: Any, ttl: float = 0, key_fn: Callable | None = None):
    """Retorna un decorator que cachea resultados en un store persistente.

    Args:
        store: Cualquier objeto con metodos get(key) y set(key, value, ttl).
               Compatible con CacheStore (cache_to_sqlite) y FileCache (cache_to_file).
        ttl: Tiempo de vida en segundos. 0 = sin expiracion.
        key_fn: Funcion opcional para generar la key. Recibe (func, args, kwargs).
                Si es None, se usa SHA-256 de (func.__name__, args, sorted(kwargs)).

    Returns:
        Decorator aplicable a funciones sincronas o asincronas.

    Example::

        store = cache_to_sqlite("cache.db")

        @cache_decorator(store, ttl=3600)
        def call_llm(prompt: str) -> str:
            ...  # llamada costosa

        result = call_llm("explain X")  # primera vez: ejecuta la funcion
        result = call_llm("explain X")  # segunda vez: desde cache
    """

    def decorator(func: Callable) -> Callable:
        if asyncio.iscoroutinefunction(func):
            @functools.wraps(func)
            async def async_wrapper(*args, **kwargs):
                make_key = key_fn or _default_key
                key = make_key(func, args, kwargs)
                cached = store.get(key)
                if cached is not None:
                    return cached
                result = await func(*args, **kwargs)
                store.set(key, result, ttl)
                return result
            return async_wrapper
        else:
            @functools.wraps(func)
            def sync_wrapper(*args, **kwargs):
                make_key = key_fn or _default_key
                key = make_key(func, args, kwargs)
                cached = store.get(key)
                if cached is not None:
                    return cached
                result = func(*args, **kwargs)
                store.set(key, result, ttl)
                return result
            return sync_wrapper

    return decorator
