import json
import os

from functools import wraps
from typing import Any, List

from aiocache import Cache, RedisCache
from aiocache.serializers import PickleSerializer
from starlette.requests import Request

from ..util.singleton import SingletonMeta


class CacheService(metaclass=SingletonMeta):

    def __init__(self):
        endpoint = os.environ.get('CACHE_ENDPOINT', 'localhost')
        port = os.environ.get('CACHE_PORT', 6379)

        self._cache = RedisCache(endpoint=endpoint, port=port, serializer=PickleSerializer())

    async def get(self, key: str) -> Any:
        namespace = key.split(":")[0]

        if value := await self._cache.get(key, namespace=namespace):
            return value

        return None

    async def set(self, key, value, ttl=300):
        namespace = key.split(":")[0]
        return await self._cache.set(key, value, ttl=ttl, namespace=namespace)

    async def delete(self, key):
        namespace = key.split(":")[0]
        await self._cache.delete(key, namespace=namespace)


def cache_response(namespace: str, include_keys: List[str] = list, ttl: int = 300, organization_specific: bool = True, debug=False):
    """
    Caching decorator for FastAPI endpoints.

    ttl: Time to live for the cache in seconds.
    namespace: Namespace for cache keys in Redis.
    """
    def decorator(func):

        @wraps(func)
        async def wrapper(*args, **kwargs):
            try:
                cache: CacheService = CacheService()
            except Exception as e:
                print(f"Error creating cache: {e}")
                return await func(*args, **kwargs)

            cache_key = f"{namespace}"

            # If organization is specific, add organization id to the cache key
            if organization_specific:
                organization_id = kwargs.get("x_organization_id")
                cache_key += f":org:{organization_id}"

            for key in include_keys:
                value = kwargs.get(key)
                if value is not None:
                    cache_key += f":{key}:{value}"

            if debug:
                print(f"Cache key {cache_key}")

            # Try to retrieve data from cache
            try:
                if cached_value := await cache.get(cache_key):
                    return cached_value  # Return cached data
            except Exception as e:
                print(f"Error retrieving data from cache: {e}")

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

            try:
                # Store the response in Redis with a TTL
                await cache.set(cache_key, response, ttl=ttl)
            except Exception as e:
                print(f"Error caching data: {e}")

            return response

        return wrapper

    return decorator
