# coding: utf-8


import logging

from django.conf.urls import url
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from tastypie.utils.urls import trailing_slash

from idm.api.exceptions import Forbidden, BadRequest
from idm.api.frontend import apifields
from idm.api.frontend.base import FrontendApiResource
from idm.api.frontend.forms import UserWorkflowTestForm, GroupWorkflowTestForm
from idm.core import models
from idm.core.constants.workflow import RUN_REASON
from idm.core.workflow.shortcuts import workflow as run_workflow
from idm.users.constants.user import USER_TYPES

log = logging.getLogger(__name__)


class WorkflowResource(FrontendApiResource):
    """
    Ресурс workflow
    """
    approver = apifields.UserForeignKey(attribute='approver')
    system = apifields.SystemForeignKey()
    user = apifields.UserForeignKey()

    class Meta(FrontendApiResource.Meta):
        abstract = False
        object_class = models.Workflow
        queryset = models.Workflow.objects.select_related('approver', 'user', 'system')
        resource_name = 'workflow'
        list_allowed_methods = []
        detail_allowed_methods = ['get', 'post']
        detail_uri_name = 'id'
        limit = 100

    def _should_send_mail(self, request_data):
        return not bool(request_data.get('silent'))

    def get_object_list_for_detail(self, request, **kwargs):
        return self.get_object_list(request).select_related('system__actual_workflow')

    def prepend_urls(self):
        return [
            # запуск доктестов
            url(r'^(?P<resource_name>%s)/(?P<id>\d+)/doctest%s$' %
                (self._meta.resource_name, trailing_slash()),
                self.wrap_view('workflow_run_doctest'), name='api_workflow_test'),
            # проверка, что вернет воркфлоу на конкретный запрос
            url(r'^(?P<resource_name>%s)/(?P<id>\d+)/test%s$' %
                (self._meta.resource_name, trailing_slash()),
                self.wrap_view('workflow_test'), name='api_workflow_test'),
            # на подтверждение
            url(r'^(?P<resource_name>%s)/(?P<id>\d+)/commit%s$' %
                (self._meta.resource_name, trailing_slash()),
                self.wrap_view('workflow_commit'), name='api_workflow_commit'),
            # подтверждение
            url(r'^(?P<resource_name>%s)/(?P<id>\d+)/approve%s$' %
                (self._meta.resource_name, trailing_slash()),
                self.wrap_view('workflow_approve'), name='api_workflow_approve'),
            # отказ
            url(r'^(?P<resource_name>%s)/(?P<id>\d+)/decline%s$' %
                (self._meta.resource_name, trailing_slash()),
                self.wrap_view('workflow_decline'), name='api_workflow_decline'),
            # удаление
            url(r'^(?P<resource_name>%s)/(?P<id>\d+)/delete%s$' %
                (self._meta.resource_name, trailing_slash()),
                self.wrap_view('workflow_delete'), name='api_workflow_delete'),
        ]

    def dehydrate_for_detail(self, bundle):
        bundle.data['can_approve'] = bundle.obj.can_approve(bundle.request.user)
        bundle.data['can_edit'] = bundle.obj.state == 'edit'
        diff, group_diff = bundle.obj.get_diff_with_current(highlight=False, full=True)
        bundle.data['diff'] = diff
        bundle.data['group_diff'] = group_diff
        return bundle

    def get_detail(self, request, **kwargs):
        workflow = get_object_or_404(models.Workflow.objects.select_related('system'), pk=kwargs['id'])

        if workflow.user_id == request.user.id:
            return super(WorkflowResource, self).get_detail(request, **kwargs)

        if (workflow.state == 'commited' and
                workflow.system.is_permitted_for(request.user, 'core.approve_workflow')):
            return super(WorkflowResource, self).get_detail(request, **kwargs)

        raise Forbidden('You have no permission to view this workflow')

    def post_detail(self, request, id, **kwargs):
        """
        Сохранить workflow
        """
        data = self.deserialize(request, request.body)
        code = data.get('workflow', None)
        if code is None:
            raise BadRequest('request must contain parameter "workflow"')
        group_code = data.get('group_workflow', '')

        workflow = get_object_or_404(
            models.Workflow.objects.select_related('system__actual_workflow').filter(user=request.user), id=id
        )
        try:
            workflow.edit(code, group_code, data.get('comment', ''))
        except PermissionDenied as e:
            raise BadRequest(str(e))

        diff, group_diff = workflow.get_diff_with_current(highlight=False, full=True)

        return self.create_response(
            request,
            {
                'id': workflow.id,
                'diff': diff,
                'group_diff': group_diff,
            },
        )

    def workflow_test(self, request, id=None, **kwargs):
        """
        Проверить
        """
        self.method_check(request, allowed=['post'])

        if not id:
            log.error('id not found in request %s', request)
            raise BadRequest('Invalid data sent - no workflow id.')

        workflow_dev = get_object_or_404(models.Workflow.objects.select_related('system'), id=id)
        system = workflow_dev.system
        data = self.deserialize(request, request.body)
        if 'group' in data:
            form = GroupWorkflowTestForm(workflow_dev.system, data=data)
        else:
            user_type = data.pop('user_type', [USER_TYPES.USER])[-1]
            if user_type == USER_TYPES.TVM_APP and not system.use_tvm_role:
                raise BadRequest(_('Система `%s` не поддерживает роли для tvm-приложений') % system.slug)

            form = UserWorkflowTestForm(system, user_type, data=data)

        if not form.is_valid():
            raise BadRequest(form.errors)

        query = form.clean()
        code = workflow_dev.workflow if query['subject_field'] == 'user' else workflow_dev.group_workflow
        try:
            context = run_workflow(
                code,
                role_data=query['path'].data,
                node=query['path'],
                system=system,
                requester=query['requester'],
                fields_data={},  # TODO: Пробрасывать поля
                system_specific={},
                scope=query['path'].parent.value_path,
                subject=query['subject'],
                parent=None,
                ignore_approvers=False,
                reason=RUN_REASON.ONLINE_TEST,
                request_type=query['request_type']
            )

        except Exception as e:
            return self.create_response(request, {'is_valid': False, 'result': force_text(e)})

        approvers = context.get('approvers')
        result = {
            'is_valid': True,
            'result': force_text(approvers),
            'doctest': workflow_dev.get_doctest(query['requester'], query['subject'], query['path'], approvers),
        }
        return self.create_response(request, result)

    def workflow_run_doctest(self, request, id=None, **kwargs):
        """
        Отправить workflow на согласование
        """
        self.method_check(request, allowed=['post'])

        if not id:
            log.error('id not found in request %s', request)
            raise BadRequest('Invalid data sent - no workflow id.')

        workflow = get_object_or_404(models.Workflow, id=id)
        try:
            workflow.test()
        except ValueError as e:
            raise BadRequest(str(e))

        return self.create_response(
            request,
            {'id': workflow.id, 'message': _('OK')}
        )

    def workflow_commit(self, request, id=None, **kwargs):
        """
        Отправить workflow на согласование
        """
        self.method_check(request, allowed=['post'])
        request_data = self.deserialize(request, request.body)

        if not id:
            log.error('id not found in request %s', request)
            raise BadRequest('Invalid data sent - no workflow id.')

        workflow = get_object_or_404(
            models.Workflow.objects.filter(user=request.user).select_related('system__actual_workflow', 'user'),
            id=id
        )
        try:
            workflow.commit(request.user, send_mail=self._should_send_mail(request_data))
        except PermissionDenied as e:
            raise Forbidden(str(e))
        except ValueError as e:
            raise BadRequest(str(e))

        return self.create_response(
            request,
            {'id': workflow.id, 'message': _('Workflow отправлен на согласование')}
        )

    def workflow_approve(self, request, id=None, **kwargs):
        """
        Подтвердить workflow
        """
        self.method_check(request, allowed=['post'])
        request_data = self.deserialize(request, request.body)

        if not id:
            log.error('id not found in request %s', request)
            raise BadRequest('Invalid data sent - no workflow id.')

        workflow = get_object_or_404(models.Workflow.objects.select_related('system__actual_workflow', 'user'), id=id)
        try:
            workflow.approve(request.user, send_mail=self._should_send_mail(request_data))
        except PermissionDenied as e:
            raise Forbidden(str(e))
        except ValueError as e:
            raise BadRequest(str(e))

        return self.create_response(request, {
            'id': workflow.id,
            'message': _('Изменение worklow подтверждено и сохранено в системе.'),
        })

    def workflow_decline(self, request, id=None, **kwargs):
        """
        Отменить изменения workflow
        """
        self.method_check(request, allowed=['post'])

        if not id:
            log.error('id not found in request %s', request)
            raise BadRequest('Invalid data sent - no workflow id.')

        workflow = get_object_or_404(models.Workflow.objects.select_related('system'), id=id)
        try:
            workflow.decline(request.user)
        except PermissionDenied as e:
            raise Forbidden(str(e))
        return self.create_response(request, {
            'id': workflow.id,
            'message': _('Изменение worklow отменено.'),
        })

    def workflow_delete(self, request, id=None, **kwargs):
        """
        Удалить workflow
        """
        self.method_check(request, allowed=['post'])

        if not id:
            log.error('id not found in request %s', request)
            raise BadRequest('Invalid data sent - no workflow id.')

        workflow = get_object_or_404(models.Workflow.objects.filter(user=request.user), id=id)
        if workflow.state != 'edit':
            raise BadRequest('Вы можете удалять workflow только из режима редактирования.')

        workflow.delete()
        return self.create_response(request, {'id': workflow.id, 'message': _('Workflow удален')})
