from redis import Redis
from typing import Optional, Union, Any, Literal
from pydantic import BaseModel
from redis.client import list_or_args
from redis.exceptions import ResponseError

from . import models
from icecream import ic
from .models import LIST, VAL, StarterModel
from limeutils import byte_conv, ValidationError, listify




class Red(Redis):
    
    def __init__(self, *args, **kwargs):
        self.pre = kwargs.pop('pre', '')
        self.ver = kwargs.pop('ver', '')
        self.ttl = kwargs.pop('ttl', -1)
        self.clear_wrongtype = kwargs.pop('wrongtype', True)
        super().__init__(*args, **kwargs)

    
    def formatkey(self, key: str) -> str:
        """
        Create the final key name with prefix and/or version if necessary
        :param key: The key to format
        :return:    str
        """
        pre = self.pre.strip()
        ver = self.ver.strip()
        
        # Prevent multiple formatting which would be bad
        ll = key.split(':')
        if pre and ver:
            if ll[0] == pre and ll[1] == ver:
                return key
        elif pre and not ver:
            if ll[0] == pre:
                return key
        elif not pre and ver:
            if ll[0] == ver:
                return key

        ll = [pre, ver, key]
        ll = list(filter(None, ll))
        return ":".join(ll)


    def set(self, key: str, val: Union[VAL, LIST, set, dict], **kwargs):
        """
        Set and updates a key
        :param key:     Key name
        :param val:     Value to save. Could be any valid value inc dict or list
        :param kwargs:  Checks for clear, insert, and parent kwargs
        :return:        Depends on the type of key
        """
        key = self.formatkey(key)
        clear = kwargs.pop('clear', False)
        insert = kwargs.pop('insert', 'end')
        ex = kwargs.pop('ttl', self.ttl)
        
        if clear:
            self.delete(key)
        
        if isinstance(val, (str, int, float, bytes)):
            if self.clear_wrongtype and self._get_type(key) != 'string':
                self.delete(key)
            return super().set(key, val, ex=ex, **kwargs)
        
        elif isinstance(val, (list, tuple)):
            if self.clear_wrongtype and self._get_type(key) != 'list':
                self.delete(key)
                    
            if insert == 'end':
                ret = self.rpush(key, *val)
            elif insert == 'start':
                ret = self.lpush(key, *val)
            else:
                raise ValidationError(choices=['start', 'end'])
            
            if ex != -1:
                self.expire(key, ex)
            return ret
            
        elif isinstance(val, dict):
            if self.clear_wrongtype and self._get_type(key) != 'hash':
                self.delete(key)
            ret = self.hset(key, mapping=val)
            if ex != -1:
                self.expire(key, ex)
            return ret
        
        elif isinstance(val, set):
            if self.clear_wrongtype and self._get_type(key) != 'set':
                self.delete(key)
            ret = self.sadd(key, *val)
            if ex != -1:
                self.expire(key, ex)
            return ret
    
    
    def get(self, key: str, default=None, **kwargs):
        """
        Get the value of a key
        :param key:     Key name
        :param default: Default value if key doesn't exist
        :param kwargs:  Checks for start, end, only, and parent kwargs
        :return:        Depends on the type of key
        """
        start = kwargs.pop('start', 0)
        end = kwargs.pop('end', -1)
        only = kwargs.pop('only', None)
        
        key = self.formatkey(key)
        datatype = byte_conv(super().type(key))
        
        if datatype == 'string':
            return byte_conv(super().get(key))
        
        elif datatype == 'list':
            data = super().lrange(key, start, end)
            return [byte_conv(i) for i in data]
        
        elif datatype == 'hash':
            if only:
                only = [only] if isinstance(only, str) else list(only)
                data = super().hmget(key, only)
                data = [byte_conv(i) for i in data]
                d = dict(zip(only, data))
            else:
                data = super().hgetall(key)
                d = {byte_conv(k):byte_conv(v) for k, v in data.items()}
            # ic(d)
            return d
        elif datatype == 'set':
            data = super().smembers(key)
            return {byte_conv(v) for v in data}
        return default


    def exists(self, *keys) -> int:
        """
        Checks if keys exists. Returns the number of keys that exist not which of the keys exists.
        :param keys:    Names of keys
        :return:        int No. of keys that exist
        """
        for i in keys:
            pass
        keys = [self.formatkey(i) for i in keys]
        return super().exists(*keys)
    
    
    def delete(self, *keys):
        """
        Delete keys
        :param keys:    Keys to delete
        :return:        int Number of keys deleted
        """
        keys = list(map(self.formatkey, keys))
        return super().delete(*keys)

    
    def _get_type(self, key: str):
        return byte_conv(super().type(key))


# class Redis:
#     def __init__(self, **kwargs):
#         self.conn = reds.Redis(**kwargs)
#
#
#
#
#
#     def hset(self, key: str, field: str, val: Optional[V] = '', mapping: Optional[dict] = None,
#              ttl=None, pre=None, ver=None) -> int:
#         data = models.Hset(key=key, field=field, val=val, mapping=mapping,
#                            ttl=ttl, pre=pre, ver=ver)
#         key = self.cleankey(data)
#         ttl = data.ttl if data.ttl is not None else self.ttl
#
#         self.pipe.hset(key, data.field, data.val, data.mapping)
#         self.pipe.expire(key, ttl)
#         [hset_ret, _] = self.pipe.execute()
#         return hset_ret