from django.forms import model_to_dict
from django.db import transaction

from intranet.search.core.models import Revision, GroupAttr, Facet
from intranet.search.core.storages.base import Storage, DoesNotExist


def rev_model_to_dict(obj):
    obj_dict = model_to_dict(obj)
    obj_dict['date'] = obj.date
    obj_dict['organization'] = model_to_dict(obj.organization)
    return obj_dict


class RevisionStorage(Storage):
    @property
    def _revision_queryset(self):
        return Revision.objects.select_related('organization')

    def _organization_filter(self, qs, organization_id):
        if organization_id is None:
            return qs

        return qs.filter(organization=organization_id)

    def get_all_active(self, organization_id=None):
        """Возвращает активные ревизии для всех поисков
        """
        qs = self._revision_queryset.filter(status='active')
        qs = self._organization_filter(qs, organization_id)
        key_to_id = {}
        for rev in qs:
            key = (rev.search, rev.index or '', rev.backend)
            current = key_to_id.get(key)
            if current is None or current.id < rev.id:
                key_to_id[key] = rev
        result = [rev_model_to_dict(rev) for rev in key_to_id.values()]
        return result

    def get_all_actual(self, organization_id=None):
        """ Возвращает все актуальные ревизии """
        qs = self._revision_queryset.filter(status__in=['active', 'new', 'ready'])
        qs = self._organization_filter(qs, organization_id)
        return [rev_model_to_dict(i) for i in qs]

    def get_by_status(self, search, index, backend, status, limit=None, order_by=None, organization_id=None):
        """Возвращает ревизии с определенным статусом для конкретного поиска
        """
        qs = self._revision_queryset.filter(
            search=search,
            index=index,
            backend=backend,
            status=status
        )

        qs = self._organization_filter(qs, organization_id)

        if order_by is not None:
            qs = qs.order_by(order_by)

        if limit is not None:
            qs = qs[:limit]

        return [rev_model_to_dict(i) for i in qs]

    def get_by_id(self, id_):
        """Возвращает ревизию по id
        """
        try:
            r = self._revision_queryset.get(id=id_)
        except Revision.DoesNotExist:
            raise DoesNotExist('Revision does not exist: %s', id_)

        return rev_model_to_dict(r)

    def get(self, id_):
        return self.get_by_id(id_)

    def get_by_id_list(self, id_list):
        """Возвращает список ревизий с заданнымы id
        """
        return [rev_model_to_dict(i) for i in self._revision_queryset.filter(id__in=id_list)]

    def get_active(self, search, index, backend='platform',
                   organization_id=None, limit=None, order_by='-id'):
        """Возвращает последнюю активную ревизию

        :return: список ревизий, подходящих под условия
        """
        qs = (
            self._revision_queryset.filter(
                search=search,
                index=index,
                backend=backend,
                status='active'
            )
            .order_by(order_by)
        )

        qs = self._organization_filter(qs, organization_id)
        if limit:
            qs = qs[:limit]

        res = [rev_model_to_dict(i) for i in qs]

        if not res:
            raise DoesNotExist('Revision does not exist: %s, %s, %s, %s' %
                               (search, index, backend, organization_id))

        return res

    def get_actual(self, search, index, backend, organization_id=None):
        """Возвращает актуальные ревизии
        """
        qs = self._revision_queryset.filter(
            status__in=('active', 'new', 'ready'),
            search=search,
            index=index,
            backend=backend
        )

        qs = self._organization_filter(qs, organization_id)

        return [rev_model_to_dict(i) for i in qs]

    def get_or_create(self, **kwargs):
        """Создаёт или получает новую ревизию"""

        lookup = {}
        for key in ('search', 'index', 'backend', 'service', 'organization_id', 'status'):
            lookup[key] = kwargs.pop(key)

        defaults = {}
        defaults['status'] = kwargs.pop('status', 'active')

        assert(not kwargs)

        revision, is_new = Revision.objects.get_or_create(**lookup, defaults=defaults)
        return rev_model_to_dict(revision)

    def create(self, **kwargs):
        """Создает новую ревизию
        """
        lookup = {
            'index': ''
        }

        for key in ('search', 'backend', 'service', 'organization_id', 'status'):
            lookup[key] = kwargs.pop(key)

        for key in ('index',):
            if key in kwargs:
                lookup[key] = kwargs.pop(key)

        assert(not kwargs)

        revision = Revision.objects.create(**lookup)

        return rev_model_to_dict(revision)

    def set_ready(self, id_):
        """Помечает ревизию как готовую
        """
        self._set_status(id_, 'ready')

    def set_broken(self, id_):
        """Помечает ревизию как сломанную
        """
        self._set_status(id_, 'broken')

    def set_active(self, revision):
        """Делает ревизию активной
        """
        if revision['status'] == 'active':
            return

        organization_id = revision['organization']['id']

        with transaction.atomic():
            try:
                old = self._revision_queryset.get(
                    search=revision['search'],
                    index=revision['index'],
                    backend=revision['backend'],
                    status='active',
                    organization_id=organization_id
                )
            except Revision.DoesNotExist:
                pass
            else:
                self._set_status(old.id, 'ready')

            self._set_status(revision['id'], 'active')

    def purge(self, id_):
        """ Помечает ревизию как удаленную
        """
        from intranet.search.core.swarm.storage import LogbrokerDocumentStorage

        revision = self.get_by_id(id_)
        LogbrokerDocumentStorage(revision).delete_revision()

        self._set_status(id_, 'deleted')
        Facet.objects.filter(revision=id_).delete()
        GroupAttr.objects.filter(revision=id_).delete()

    def get_for_purge(self, search, index, offset):
        """ Возвращает ревизии, которые можно запуржить
        """
        revisions = (
            self._revision_queryset
            .exclude(status__in=('active', 'deleted'))
            .filter(search=search, index=index)
            .exclude(service='')
            .order_by('-id')[offset:]
        )
        return [rev_model_to_dict(i) for i in revisions]

    def get_for_service(self, service):
        """Возвращает актуальные ревизии для заданного сервиса
        """
        revisions = self._revision_queryset.filter(
            status__in=('new', 'active', 'ready'),
            service=service
        )
        return [rev_model_to_dict(i) for i in revisions]

    def update(self, *args, **kwargs):
        data = kwargs.pop('data', {})
        Revision.objects.filter(*args, **kwargs).update(**data)

    # Детали реализации

    def _set_status(self, id_, status):
        Revision.objects.filter(id=id_).update(status=status)
