from redis import Redis, StrictRedis

from . import BaseCache, BaseStorage, get_valid_kwargs


class RedisCache(BaseCache):
    """Redis cache backend.

    Args:
        namespace: redis namespace (default: ``'requests-cache'``)
        connection: Redis connection instance to use instead of creating a new one
        kwargs: Additional keyword arguments for :py:class:`redis.client.Redis`
    """

    def __init__(self, namespace='http_cache', connection: Redis = None, **kwargs):
        super().__init__(**kwargs)
        self.responses = RedisDict(namespace, 'responses', connection=connection, **kwargs)
        self.redirects = RedisDict(namespace, 'redirects', connection=self.responses.connection, **kwargs)


class RedisDict(BaseStorage):
    """A dictionary-like interface for redis key-value store.

    Notes:
        * In order to deal with how redis stores data/keys, all keys and data are pickled.
        * The actual key name on the redis server will be ``namespace:collection_name``.

    Args:
        namespace: Redis namespace
        collection_name: Name of the Redis hash map
        connection: (optional) Redis connection instance to use instead of creating a new one
        kwargs: Additional keyword arguments for :py:class:`redis.client.Redis`
    """

    def __init__(self, namespace, collection_name='http_cache', connection=None, **kwargs):
        super().__init__(**kwargs)
        connection_kwargs = get_valid_kwargs(Redis, kwargs)
        self.connection = connection or StrictRedis(**connection_kwargs)
        self._self_key = ':'.join([namespace, collection_name])

    def __getitem__(self, key):
        result = self.connection.hget(self._self_key, self.serialize(key))
        if result is None:
            raise KeyError
        return self.deserialize(result)

    def __setitem__(self, key, item):
        self.connection.hset(self._self_key, self.serialize(key), self.serialize(item))

    def __delitem__(self, key):
        if not self.connection.hdel(self._self_key, self.serialize(key)):
            raise KeyError

    def __len__(self):
        return self.connection.hlen(self._self_key)

    def __iter__(self):
        for v in self.connection.hkeys(self._self_key):
            yield self.deserialize(v)

    def clear(self):
        self.connection.delete(self._self_key)
