import json
from functools import wraps

import redis
from loguru import logger as logger
from redis import StrictRedis
from redisbloom.client import Client

from souJpg.comm.contextManagers import ExceptionCatcher

encoding = "utf-8"


class RedisOps:
    def __init__(self, params={}):
        self.params = params
        host = self.params.get("redis_host", "gpu0.dev.yufei.com")
        port = self.params.get("redis_port", 6379)
        self.r = redis.Redis(host=host, port=port, db=0)
        self.bf = Client(host=host, port=port, db=0)
        self.createdBfNames = set()

    def mget(self, keys=None):
        values = self.r.mget(keys=keys)
        values_ = []
        for value in values:
            if value is not None:
                value = value.decode(encoding)
            values_.append(value)
        return values_

    def mset(self, keydict=None):
        """
        set multi key-value
        :param keydict:
        :return:
        """
        self.r.mset(keydict)

    def set(self, key=None, value=None, ex=None):
        self.r.set(name=key, value=value, ex=ex)

    def get(self, key):
        value = self.r.get(name=key)
        if value is not None:
            value = value.decode(encoding)

        return value

    def lpush(self, key=None, value=None):
        self.r.lpush(key, value)

    def rpop(self, key=None):
        value = self.r.rpop(name=key)
        if value is not None:
            value = value.decode(encoding)

        return value

    def bfPush(self, bfName=None, value=None):
        if bfName in self.createdBfNames:
            self.bf.cfAddNX(bfName, value)
        else:
            try:
                self.bf.cfCreate(bfName, 10000000)

            except BaseException as e:
                logger.debug(
                    "bfName: already existed! will add it to existed set", bfName
                )
                self.createdBfNames.add(bfName)

    def bfExist(self, bfName=None, value=None):
        return self.bf.cfExists(bfName, value)

    def refresh(self, key=None):
        return self.r.expire(key, 1)

    def deleteKey(self, key=None):
        return self.r.delete(key)


redisOps = RedisOps()


def cached(ex=60 * 60 * 24, redisKey=None):
    """
    also key must be compose of kwargs
    result must be json serializable and also dict object
    """

    def cached_real(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # Generate the cache key from the function's arguments.
            key = redisKey
            if key is None:
                args_ = []
                for key, value in kwargs.items():
                    logger.debug("key: {key}, value: {value}", key=key, value=value)
                    args_.append(str(value))

                key_parts = [func.__name__] + args_
                key = "-".join(key_parts)

            result = None
            with ExceptionCatcher() as ec:
                result = redisOps.get(key)

            if result is None:
                # Run the function and cache the result for next time.
                value = func(*args, **kwargs)
                with ExceptionCatcher() as ec:
                    if value is not None:
                        logger.debug("cache key: {key}", key=key)

                        value_json = json.dumps(value)
                        redisOps.set(key=key, value=value_json, ex=ex)

            else:
                logger.info("cache hit: {key}", key=key)

                value = json.loads(result)

            return value

        return wrapper

    return cached_real
