import logging
from operator import itemgetter

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

from intranet.search.core.models import Organization, Service, Revision
from intranet.search.core.storages import Storage


log = logging.getLogger(__name__)


def org_model_to_dict(obj):
    obj_dict = model_to_dict(obj)
    services = map(model_to_dict, obj.service_set.all())
    obj_dict['services'] = sorted(services, key=itemgetter('name'))
    return obj_dict


SERVICE_DIRECTORY = 'directory'


class OrganizationStorage(Storage):
    def create(self, *args, **kwargs):
        org = Organization.objects.create(**kwargs)
        return org_model_to_dict(org)

    def get_or_create(self, *args, **kwargs):
        org, created = Organization.objects.get_or_create(*args, **kwargs)
        return org_model_to_dict(org), created

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

    def delete(self, _id):
        Organization.objects.filter(id=_id).update(deleted=True, deletion_date=timezone.now())

    def restore(self, _id):
        Organization.objects.filter(id=_id).update(deleted=False, deletion_date=None)

    def get(self, _id):
        return model_to_dict(Organization.objects.get(id=_id))

    def get_or_none(self, _id):
        try:
            org = Organization.objects.get(id=_id)
        except Organization.DoesNotExist:
            org = None
        if org is None:
            return org
        return model_to_dict(org)

    def get_by_service(self, service, limit=None):
        orgs = Organization.objects.filter(deleted=False, service__name=service)
        if limit:
            orgs = orgs[:limit]
        return [model_to_dict(o) for o in orgs]

    def _organization_filter(self, _id):
        """ Возвращает фильтр по организации в зависимости от типа переданного id

        :param _id: id из директории или slug организации
        """
        if isinstance(_id, int) or _id.isdigit():
            return {'directory_id': _id}
        else:
            return {'label': _id}

    def get_by_directory_or_label(self, _id):
        """ Находит организацию по айдишнику директории или слагу

        :return: сериализованный в словарь объект организации
        """
        params = self._organization_filter(_id)
        return model_to_dict(Organization.objects.get(**params))

    def get_one_by_service(self, service, _id):
        """
        Возвращает одну организацию с данным _id и подключенным сервисов

        :param service: строка - slug сервиса
        :param _id: id из директории или slug организации

        :return: сериализованный в словарь объект организации
        :raise:
        """
        params = self._organization_filter(_id)
        params['service__name'] = service
        return model_to_dict(Organization.objects.get(**params))

    def get_services(self, _id):
        """ Возвращает все сервисы организации

        :param _id: id организации
        :return: список сериализованных в словарь сервисов организации
        """
        params = self._organization_filter(_id)
        organization = Organization.objects.get(**params)
        return [model_to_dict(i) for i in organization.service_set.all()]

    def get_all(self):
        qs = Organization.objects.prefetch_related('service_set')
        return [org_model_to_dict(o) for o in qs]

    def get_by_indexation_time(self, search, index='', limit=None):
        """ Возвращает индексации в порядке их индексации от старших к младшим
        :param search: название поиска, по которому нужно смотреть индексацию
        :param index: название индекса, по которому нужно смотреть индексацию
        :param limit: максимальное количество индексаций в выдаче
        """
        # сначала отдаем организации, которые вообще не индексировались
        indexed = (
            Revision.objects
            .filter(search=search, index=index, status='active')
            .values('organization')
        )
        qs = (
            Organization.objects
            .filter(deleted=False, service__name=search)
            .exclude(id__in=indexed)
            .values('id')
        )
        if limit:
            qs = qs[:limit]
        result = list(qs)

        # если в лимит ещё не уперлись, то дополняем список давно не индексировавшимися
        # организациями
        if not limit or len(result) < limit:
            limit = limit - len(result) if limit else None

            qs = (
                Revision.objects
                .filter(search=search, index=index, status='active')
                .filter(organization__service__name=search)
                .values_list('organization', flat=True)
                .order_by('latest_indexation_time')
            )
            if limit:
                qs = qs[:limit]
            result.extend({'id': org_id} for org_id in qs)
        return result

    @transaction.atomic
    def update_from_directory_data(self, data):
        """ Обновляет организацию и связанные с ней сервисы на основе данных из директории

        :param data: данные, получаемые из директории
        :return: сериализованный в словарь объект организации
        """
        org_dict = {
            'directory_revision': data['revision'],
            'name': data['name'],
            'label': data['label'],
            'organization_type': data['organization_type'],
        }

        org, _ = Organization.objects.get_or_create(directory_id=data['id'], defaults=org_dict)
        if org.directory_revision < data['revision']:
            Organization.objects.filter(id=org.id).update(**org_dict)

        services = [s['slug'] for s in data['services']] + [SERVICE_DIRECTORY]
        for service in services:
            self.add_service(org.id, service)
        Service.objects.filter(organization=org.id).exclude(name__in=services).delete()

        return org_model_to_dict(org)

    def add_service(self, _id, service):
        """ Добавляет сервис организации

        :param _id: организации
        :param service: название сервиса
        :return: сериализованый в словарь объект сервиса
        """
        s, _ = Service.objects.get_or_create(organization_id=_id, name=service)
        return model_to_dict(s)

    def delete_service(self, _id, service):
        """ Удаляет сервис у организации

        :param _id: организации
        :param service: название сервиса
        """
        return Service.objects.filter(organization_id=_id, name=service).delete()

    def get_revision_failed(self, service, index, active_date):
        active_revisions = (
            Revision.objects
            .filter(
                search=service,
                index=index,
                status='active',
            )
            .values('organization_id')
        )
        return (
            Organization.objects
            .filter(deleted=False, service__name=service, creation_date__lt=active_date)
            .exclude(id__in=active_revisions)
            .values('id', 'directory_id', 'label')
        )

    def count(self):
        return Organization.objects.count()
