import json
from logging import getLogger
from math import ceil
from intranet.search.core.errors import UnrecoverableError, RecoverableError
from intranet.search.core import redis


log = getLogger(__name__)


class DoesNotExist(UnrecoverableError):
    pass


class StorageUnavailable(RecoverableError):
    pass


class Storage:
    def create(self, *args, **kwargs):
        raise NotImplementedError

    def update(self, *args, **kwargs):
        raise NotImplementedError

    def get(self, *args, **kwargs):
        raise NotImplementedError


class ArtefactStorage(Storage):
    def __init__(self, revision):
        self.revision = revision

    def get_base_lookup(self):
        if self.revision:
            return {
                'search': self.revision['search'],
                'index': self.revision['index'],
                'backend': self.revision['backend'],
                'revision_id': self.revision['id'],
            }
        else:
            return {}


def merge_dicts(*dcts):
    '''
    >>> merge_dicts({1: 1, 2: 2, 3: 0, 4: 15}, {2: 666, 4: 0})
    {1: 1, 2: 666, 3: 0, 4: 15}
    '''
    data = {}
    for dct in dcts:
        for key, value in dct.items():
            data[key] = value or data.get(key, value)

    return data


class BufferedStorageMixin:
    key_prefix = ''
    buffer_key = None

    def _key(self):
        return f'{self.key_prefix}:{self.buffer_key}'

    @classmethod
    def get_buffered_keys(cls):
        mask = f'{cls.key_prefix}:*'
        keys = redis.get_client().keys(mask)
        prefix_len = len(mask) - 1
        # возвращаем только суффиксы, которые нужно сдампить
        return [key[prefix_len:] for key in keys]

    def _encode(self, id_, data):
        return json.dumps([id_, data])

    def _decode(self, raw_data):
        id_, data = json.loads(raw_data)
        return id_, data

    def create(self, *objects):
        pipe = redis.get_client().pipeline()
        ids = []

        key = self._key()
        for obj in objects:
            id_, data = self._create(obj)
            pipe.rpush(key, self._encode(id_, data))
            ids.append(id_)

        pipe.execute()

    def _iterate_list(self, key, chunk_size=1000):
        count = redis.get_client().llen(key)

        for i in range(int(ceil(count / chunk_size))):
            chunk = redis.get_client().lrange(key, i * chunk_size, (i + 1) * chunk_size - 1)
            yield from chunk

    def flush(self):
        key = self._key()

        result = {}
        items_count = 0
        for item in self._iterate_list(key):
            id_, data = self._decode(item)
            items_count += 1
            result[id_] = merge_dicts(result.get(id_, {}), data)

        for data in result.values():
            try:
                self._flush(data)
            except Exception as e:
                log.error(e)

        # удаляем то что сфлашили
        redis.get_client().ltrim(key, items_count, -1)

        return items_count

    def purge(self):
        redis.get_client().delete(self._key())
