# coding: utf-8


from django.conf import settings

from idm.celery_app import app
from idm.core.models import Action, RoleNode
from idm.framework.task import BaseTask, UnrecoverableError, DelayingError
from idm.utils import http
from idm.utils.tasks import get_object_or_retry_task
from idm.core.constants.instrasearch import INTRASEARCH_METHOD


class PushItemChange(BaseTask):

    monitor_success = False  # таски этого типа запускаются ad-hoc

    def init(self, action_id, node_id, method):
        # This guards against rolled back transaction. Sometimes it's hard to provide an action,
        # so there is this ugly if-clause.
        if action_id is not None:
            get_object_or_retry_task(Action, pk=action_id)
        queryset = RoleNode.objects.select_related(
            'parent',
            'system',
            'nodeset',
        ).prefetch_related(
            'fields',
            'aliases',
            'responsibilities'
        )
        node = get_object_or_retry_task(queryset, pk=node_id)
        data = node.as_intrasearch_push(method)

        http_method_name = INTRASEARCH_METHOD.as_http(method)
        http_method = getattr(http, http_method_name)

        try:
            response = http_method(
                settings.IDM_INTRASEARCH_PUSH_URL.format(source_type='rolenodes'),
                data=data.encode('utf-8'),
                headers={'Content-Type': 'application/json'},
                timeout=settings.IDM_INTRASEARCH_TIMEOUT,
            )
        except http.RequestException:
            # перевыбрасываем исключение, чтобы поретраить таск, если он упал на коннекте
            raise
        except Exception:
            self.log.exception('Some exception happened while trying to make push for node %s', node.self_path)
            raise UnrecoverableError('Exception while pushing node')

        if response.status_code >= 300:
            self.log.error(
                'Intrasearch returned wrong status code: %s while push node %s',
                response.status_code, node.self_path
            )
            raise DelayingError('Wrong intrasearch status')

        try:
            response_data = response.json()
            status = response_data['status']
        except (KeyError, ValueError):
            self.log.error(
                'Intrasearch returned unparseable data while pushing node %s: %s',
                node.self_path, response.content[:5000]
            )
            raise DelayingError('Cannot parse intraseach json')

        if status != 'ok':
            self.log.error('Intrasearch returned notok status while pushing node %s: %s', node.self_path, status)
            raise DelayingError('Intrasearch with not-ok internal status')

        # This if-clause will be existing while "retryyo" will push only adding and removing nodes.
        # (to prevent redundant updates)
        if method in (INTRASEARCH_METHOD.REMOVE, INTRASEARCH_METHOD.ADD):
            node.need_isearch_push_method = None
            node.save(update_fields=('need_isearch_push_method',))

        self.log.info('Intrasearch successfully pushed node %s [%s]', node.self_path, method)

    def bulk_init(self, action_id, node_ids, method):
        for node_id in node_ids:
            PushItemChange.delay(action_id=action_id, node_id=node_id, method=method)


PushItemChange = app.register_task(PushItemChange())
