from collections.abc import Callable
from functools import wraps
from typing import Any

from redix.configuration import RedixConfig
from redix.exceptions import RedisClientNotSpecifiedError, ReturnTypeNotSpecifiedError
from redix.key import generate_key
from redix.serialize import Serializator, SerializerProtocol
from redix.types import TypeHintResult

config = RedixConfig()
base_serializator = Serializator()


def redix_cache(
        ttl: int = 3600,
        serializator: SerializerProtocol | None = None,
        ignore_errors: bool = True,
        prefix: str | None = None,
        tpr: TypeHintResult | None = None
) -> Callable:
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        async def wrapper(*args: Any, **kwargs: Any) -> Any:
            if not config.redis_client:
                raise RedisClientNotSpecifiedError

            working_prefix = prefix or config.prefix
            working_serializer = serializator or base_serializator
            working_tpr = tpr or func.__annotations__['return']
            if not working_tpr:
                raise ReturnTypeNotSpecifiedError

            cache_key = generate_key(
                func, args, kwargs, working_prefix, config.key_format
            )
            try:
                cached_data = await config.redis_client.get(cache_key)

                if cached_data is not None:
                    return working_serializer.deserialize(cached_data, working_tpr)
            except Exception as e:
                if not ignore_errors:
                    raise e

            result = await func(*args, **kwargs)

            try:
                serialized_data = working_serializer.serialize(result, working_tpr)
                await config.redis_client.setex(cache_key, ttl, serialized_data)
            except Exception as e:
                if not ignore_errors:
                    raise e

            return result
        return wrapper
    return decorator
