import pickle
import base64
from hashlib import sha1
import logging

from django.conf import settings
from django.utils.encoding import force_bytes
from pymongo.errors import PyMongoError

from .base import Storage
from intranet.search.core import mongodb

log = logging.getLogger(__name__)


class BaseCacheStorage(Storage):
    """Базовый интерфейс для стораджа кеша
    """
    def get(self, *args, **kwargs):
        raise NotImplementedError

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

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


class DummyCacheStorage(BaseCacheStorage):
    """Реализация стораджа, которая на самом
    деле ничего не кеширует
    """
    def __init__(self, revision):
        self.revision = revision

    def get(self, key, default=None):
        return default

    def set(self, *args, **kwargs):
        pass

    def clear(self, *args, **kwargs):
        pass


class GlobalCacheStorage(BaseCacheStorage):
    """ Глобальный кеш в монго
    """
    def get(self, key, default=None):
        key = self._make_key(key)
        try:
            doc = self._collection.find_one({'_id': str(key)})
        except PyMongoError as exc:
            log.warning("Can't get item %s from cache. %s", key, exc)
            return default

        if doc:
            return self._loads(doc['value'])

        return default

    def set(self, key, value, timeout=None):
        key = self._make_key(key)

        try:
            self._collection.replace_one({'_id': key}, {'value': self._dumps(value)}, upsert=True)
        except PyMongoError as exc:
            log.warning("Can't set item %s to cache. %s", key, exc)

    def delete(self, key):
        self._collection.delete_one({'_id': self._make_key(key)})

    def clear(self):
        self._collection.drop()

    @property
    def _collection(self):
        name = '{}-{}'.format(settings.ISEARCH_MONGODB_CACHE_COLLECTION_PREFIX, 'global')
        return mongodb.client[settings.ISEARCH_MONGODB_DB][name]

    def _dumps(self, value):
        return base64.b64encode(pickle.dumps(value, pickle.HIGHEST_PROTOCOL))

    def _loads(self, value):
        return pickle.loads(base64.b64decode(value))

    def _make_key(self, key):
        return sha1(force_bytes(key)).hexdigest()


class CacheStorage(GlobalCacheStorage):
    """ Глобальный кеш, отдельный для каждой индексации
    """
    def __init__(self, indexation_id, revision):
        self.revision = revision
        self.indexation_id = str(indexation_id)

    def clear(self):
        # Нужно чтобы можно было дропать старые индексации
        if hasattr(self, 'indexation_id'):
            self._collection.drop()

    @property
    def _collection(self):
        name = '{}-{}-{}'.format(
            settings.ISEARCH_MONGODB_CACHE_COLLECTION_PREFIX,
            self.revision['id'],
            self.indexation_id,
        )

        return mongodb.client[settings.ISEARCH_MONGODB_DB][name]
