from __future__ import absolute_import, unicode_literals

from django.contrib import messages
from django.core.urlresolvers import reverse, reverse_lazy
from django.db.models import F, Max
from django.db.utils import IntegrityError
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _

from acls.models import AccessControlList
from common.views import (
    AssignRemoveView, ConfirmView, FormView, SingleObjectCreateView,
    SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView
)
from documents.models import Document
from documents.views import DocumentListView

from .forms import (
    WorkflowForm, WorkflowInstanceTransitionForm, WorkflowStateForm,
    WorkflowTransitionForm
)
from .models import (
    Workflow, WorkflowInstance, WorkflowInstanceLogEntry, WorkflowState,
    WorkflowTransition, WorkflowRuntimeProxy, WorkflowStateRuntimeProxy
)
from .permissions import (
    permission_workflow_create, permission_workflow_delete,
    permission_workflow_edit, permission_workflow_tools,
    permission_workflow_view,
)
from .tasks import task_launch_all_workflows


class DocumentWorkflowInstanceListView(SingleObjectListView):
    def dispatch(self, request, *args, **kwargs):
        AccessControlList.objects.check_access(
            permissions=permission_workflow_view, user=request.user,
            obj=self.get_document()
        )

        return super(
            DocumentWorkflowInstanceListView, self
        ).dispatch(request, *args, **kwargs)

    def get_document(self):
        return get_object_or_404(Document, pk=self.kwargs['pk'])

    def get_extra_context(self):
        return {
            'hide_link': True,
            'object': self.get_document(),
            'title': _(
                'Workflows for document: %s'
            ) % self.get_document(),
        }

    def get_queryset(self):
        return self.get_document().workflows.all()


class WorkflowInstanceDetailView(SingleObjectListView):
    def dispatch(self, request, *args, **kwargs):
        AccessControlList.objects.check_access(
            permissions=permission_workflow_view, user=request.user,
            obj=self.get_workflow_instance().document
        )

        return super(
            WorkflowInstanceDetailView, self
        ).dispatch(request, *args, **kwargs)

    def get_extra_context(self):
        return {
            'hide_object': True,
            'navigation_object_list': ('object', 'workflow_instance'),
            'object': self.get_workflow_instance().document,
            'title': _('Detail of workflow: %(workflow)s') % {
                'workflow': self.get_workflow_instance()
            },
            'workflow_instance': self.get_workflow_instance(),
        }

    def get_queryset(self):
        return self.get_workflow_instance().log_entries.order_by('-datetime')

    def get_workflow_instance(self):
        return get_object_or_404(WorkflowInstance, pk=self.kwargs['pk'])


class WorkflowInstanceTransitionView(FormView):
    form_class = WorkflowInstanceTransitionForm
    template_name = 'appearance/generic_form.html'

    def form_valid(self, form):
        self.get_workflow_instance().do_transition(
            comment=form.cleaned_data['comment'],
            transition=form.cleaned_data['transition'], user=self.request.user
        )
        return HttpResponseRedirect(self.get_success_url())

    def get_extra_context(self):
        return {
            'navigation_object_list': ('object', 'workflow_instance'),
            'object': self.get_workflow_instance().document,
            'submit_label': _('Submit'),
            'title': _(
                'Do transition for workflow: %s'
            ) % self.get_workflow_instance(),
            'workflow_instance': self.get_workflow_instance(),
        }

    def get_form_extra_kwargs(self):
        return {
            'user': self.request.user,
            'workflow_instance': self.get_workflow_instance()
        }

    def get_success_url(self):
        return self.get_workflow_instance().get_absolute_url()

    def get_workflow_instance(self):
        return get_object_or_404(WorkflowInstance, pk=self.kwargs['pk'])


# Setup

class SetupWorkflowListView(SingleObjectListView):
    extra_context = {
        'title': _('Workflows'),
        'hide_link': True,
    }
    model = Workflow
    view_permission = permission_workflow_view


class SetupWorkflowCreateView(SingleObjectCreateView):
    form_class = WorkflowForm
    model = Workflow
    view_permission = permission_workflow_create
    post_action_redirect = reverse_lazy('document_states:setup_workflow_list')


class SetupWorkflowEditView(SingleObjectEditView):
    form_class = WorkflowForm
    model = Workflow
    view_permission = permission_workflow_edit
    post_action_redirect = reverse_lazy('document_states:setup_workflow_list')


class SetupWorkflowDeleteView(SingleObjectDeleteView):
    model = Workflow
    view_permission = permission_workflow_delete
    post_action_redirect = reverse_lazy('document_states:setup_workflow_list')


class SetupWorkflowDocumentTypesView(AssignRemoveView):
    decode_content_type = True
    object_permission = permission_workflow_edit
    left_list_title = _('Available document types')
    right_list_title = _('Document types assigned this workflow')

    def add(self, item):
        self.get_object().document_types.add(item)
        # TODO: add task launching this workflow for all the document types
        # of item

    def get_extra_context(self):
        return {
            'title': _(
                'Document types assigned the workflow: %s'
            ) % self.get_object(),
            'object': self.get_object(),
        }

    def get_object(self):
        return get_object_or_404(Workflow, pk=self.kwargs['pk'])

    def left_list(self):
        return AssignRemoveView.generate_choices(
            self.get_object().get_document_types_not_in_workflow()
        )

    def right_list(self):
        return AssignRemoveView.generate_choices(
            self.get_object().document_types.all()
        )

    def remove(self, item):
        self.get_object().document_types.remove(item)
        # TODO: add task deleting this workflow for all the document types of
        # item


class SetupWorkflowStateListView(SingleObjectListView):
    def dispatch(self, request, *args, **kwargs):
        AccessControlList.objects.check_access(
            permissions=permission_workflow_view, user=request.user,
            obj=self.get_workflow()
        )

        return super(
            SetupWorkflowStateListView, self
        ).dispatch(request, *args, **kwargs)

    def get_extra_context(self):
        return {
            'hide_link': True,
            'object': self.get_workflow(),
            'title': _('States of workflow: %s') % self.get_workflow()
        }

    def get_queryset(self):
        return self.get_workflow().states.all()

    def get_workflow(self):
        return get_object_or_404(Workflow, pk=self.kwargs['pk'])


class SetupWorkflowStateCreateView(SingleObjectCreateView):
    form_class = WorkflowStateForm
    view_permission = permission_workflow_edit

    def get_extra_context(self):
        return {
            'object': self.get_workflow(),
            'title': _(
                'Create states for workflow: %s'
            ) % self.get_workflow()
        }

    def get_workflow(self):
        return get_object_or_404(Workflow, pk=self.kwargs['pk'])

    def get_queryset(self):
        return self.get_workflow().states.all()

    def get_success_url(self):
        return reverse(
            'document_states:setup_workflow_states', args=(self.kwargs['pk'],)
        )

    def form_valid(self, form):
        self.object = form.save(commit=False)
        self.object.workflow = self.get_workflow()
        self.object.save()
        return super(SetupWorkflowStateCreateView, self).form_valid(form)


class SetupWorkflowStateDeleteView(SingleObjectDeleteView):
    model = WorkflowState
    view_permission = permission_workflow_edit

    def get_extra_context(self):
        return {
            'navigation_object_list': ('object', 'workflow_instance'),
            'object': self.get_object(),
            'workflow_instance': self.get_object().workflow,
        }

    def get_success_url(self):
        return reverse(
            'document_states:setup_workflow_states',
            args=(self.get_object().workflow.pk,)
        )


class SetupWorkflowStateEditView(SingleObjectEditView):
    form_class = WorkflowStateForm
    model = WorkflowState
    view_permission = permission_workflow_edit

    def get_extra_context(self):
        return {
            'navigation_object_list': ('object', 'workflow_instance'),
            'object': self.get_object(),
            'workflow_instance': self.get_object().workflow,
        }

    def get_success_url(self):
        return reverse(
            'document_states:setup_workflow_states',
            args=(self.get_object().workflow.pk,)
        )


# Transitions


class SetupWorkflowTransitionListView(SingleObjectListView):
    view_permission = permission_workflow_view

    def get_workflow(self):
        return get_object_or_404(Workflow, pk=self.kwargs['pk'])

    def get_queryset(self):
        return self.get_workflow().transitions.all()

    def get_extra_context(self):
        return {
            'hide_link': True,
            'object': self.get_workflow(),
            'title': _(
                'Transitions of workflow: %s'
            ) % self.get_workflow()
        }


class SetupWorkflowTransitionCreateView(SingleObjectCreateView):
    form_class = WorkflowTransitionForm
    view_permission = permission_workflow_edit

    def get_extra_context(self):
        return {
            'object': self.get_workflow(),
            'title': _(
                'Create transitions for workflow: %s'
            ) % self.get_workflow()
        }

    def get_form_kwargs(self):
        kwargs = super(
            SetupWorkflowTransitionCreateView, self
        ).get_form_kwargs()
        kwargs['workflow'] = self.get_workflow()
        return kwargs

    def get_workflow(self):
        return get_object_or_404(Workflow, pk=self.kwargs['pk'])

    def get_queryset(self):
        return self.get_workflow().transitions.all()

    def get_success_url(self):
        return reverse(
            'document_states:setup_workflow_transitions',
            args=(self.kwargs['pk'],)
        )

    def form_valid(self, form):
        self.object = form.save(commit=False)
        self.object.workflow = self.get_workflow()
        try:
            self.object.save()
        except IntegrityError:
            messages.error(
                self.request, _('Unable to save transition; integrity error.')
            )
            return super(
                SetupWorkflowTransitionCreateView, self
            ).form_invalid(form)
        else:
            return HttpResponseRedirect(self.get_success_url())


class SetupWorkflowTransitionDeleteView(SingleObjectDeleteView):
    model = WorkflowTransition
    view_permission = permission_workflow_edit

    def get_extra_context(self):
        return {
            'object': self.get_object(),
            'navigation_object_list': ('object', 'workflow_instance'),
            'workflow_instance': self.get_object().workflow,
        }

    def get_success_url(self):
        return reverse(
            'document_states:setup_workflow_transitions',
            args=(self.get_object().workflow.pk,)
        )


class SetupWorkflowTransitionEditView(SingleObjectEditView):
    form_class = WorkflowTransitionForm
    model = WorkflowTransition
    view_permission = permission_workflow_edit

    def get_extra_context(self):
        return {
            'navigation_object_list': ('object', 'workflow_instance'),
            'object': self.get_object(),
            'workflow_instance': self.get_object().workflow,
        }

    def get_form_kwargs(self):
        kwargs = super(
            SetupWorkflowTransitionEditView, self
        ).get_form_kwargs()
        kwargs['workflow'] = self.get_object().workflow
        return kwargs

    def get_success_url(self):
        return reverse(
            'document_states:setup_workflow_transitions',
            args=(self.get_object().workflow.pk,)
        )


class WorkflowListView(SingleObjectListView):
    view_permission = permission_workflow_view

    def get_queryset(self):
        return WorkflowRuntimeProxy.objects.all()

    def get_extra_context(self):
        return {
            'hide_link': True,
            'title': _('Workflows')
        }


class WorkflowDocumentListView(DocumentListView):
    def dispatch(self, request, *args, **kwargs):
        self.workflow = get_object_or_404(
            WorkflowRuntimeProxy, pk=self.kwargs['pk']
        )

        AccessControlList.objects.check_access(
            permissions=permission_workflow_view, user=request.user,
            obj=self.workflow
        )

        return super(
            WorkflowDocumentListView, self
        ).dispatch(request, *args, **kwargs)

    def get_document_queryset(self):
        return Document.objects.filter(workflows__workflow=self.workflow)

    def get_extra_context(self):
        return {
            'hide_links': True,
            'object': self.workflow,
            'title': _('Documents with the workflow: %s') % self.workflow
        }


class WorkflowStateDocumentListView(DocumentListView):
    def dispatch(self, request, *args, **kwargs):
        self.workflow_state = get_object_or_404(
            WorkflowStateRuntimeProxy, pk=self.kwargs['pk']
        )

        AccessControlList.objects.check_access(
            permissions=permission_workflow_view, user=request.user,
            obj=self.workflow_state.workflow
        )

        return super(
            WorkflowStateDocumentListView, self
        ).dispatch(request, *args, **kwargs)

    def get_document_queryset(self):
        latest_entries = WorkflowInstanceLogEntry.objects.annotate(
            max_datetime=Max(
                'workflow_instance__log_entries__datetime'
            )
        ).filter(
            datetime=F('max_datetime')
        )

        state_latest_entries = latest_entries.filter(
            transition__destination_state=self.workflow_state
        )

        return Document.objects.filter(
            workflows__pk__in=state_latest_entries.values_list(
                'workflow_instance', flat=True
            )
        )

    def get_extra_context(self):
        return {
            'hide_links': True,
            'object': self.workflow_state,
            'navigation_object_list': ('object', 'workflow'),
            'workflow': WorkflowRuntimeProxy.objects.get(
                pk=self.workflow_state.workflow.pk
            ),
            'title': _(
                'Documents in the workflow "%s", state "%s"'
            ) % (self.workflow_state.workflow, self.workflow_state)
        }


class WorkflowStateListView(SingleObjectListView):
    def dispatch(self, request, *args, **kwargs):
        AccessControlList.objects.check_access(
            permissions=permission_workflow_view, user=request.user,
            obj=self.get_workflow()
        )

        return super(
            WorkflowStateListView, self
        ).dispatch(request, *args, **kwargs)

    def get_extra_context(self):
        return {
            'hide_columns': True,
            'hide_link': True,
            'object': self.get_workflow(),
            'title': _('States of workflow: %s') % self.get_workflow()
        }

    def get_queryset(self):
        return WorkflowStateRuntimeProxy.objects.filter(
            workflow=self.get_workflow()
        )

    def get_workflow(self):
        return get_object_or_404(WorkflowRuntimeProxy, pk=self.kwargs['pk'])


class ToolLaunchAllWorkflows(ConfirmView):
    extra_context = {
        'title': _('Launch all workflows?')
    }
    view_permission = permission_workflow_tools

    def view_action(self):
        task_launch_all_workflows.apply_async()
        messages.success(
            self.request, _('Workflow launch queued successfully.')
        )
