"""This module holds the views of the ``workshops`` app.

Notes
-----
The choice was done to use class-based views.

"""

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from django.views import generic

from mtp.apps.assessments.views import AssessableCreateMixin, MaybeAssessableFormBaseView
from mtp.apps.common.views_utils import (
    AjaxResponseMixin,
    ConditionalMixin,
    EditFormWithFormsetMixin,
    OrderableListMixin,
    SuccessMessageMixin,
    SyncableDeleteView,
)
from mtp.apps.import_export.exceptions import ImportDataError
from mtp.apps.import_export.views import XLSExportDetailView, ActivityXLSImportView
from mtp.apps.trainings.models import Training
from mtp.apps.trainings.views import TrainingBaseView

from .constants import STEP_TYPES_QUESTION, QUESTION_MULTIPLE
from .forms import (
    WorkshopAssessableForm,
    WorkshopForm,
    WorkshopStepImageForm,
    WorkshopStepQuestionAnswerFormSet,
    WorkshopStepQuestionAssessableForm,
    WorkshopStepQuestionForm,
    WorkshopStepsDeleteForm,
)
from .models import (
    Workshop,
    WorkshopStep,
    WorkshopStepImage,
    WorkshopStepQuestion
)
from .resources import (
    WorkshopResource, WorkshopStepQuestionResource,
    WorkshopStepQuestionAnswerResource
)


class WorkshopBase(TrainingBaseView):
    """Base view to use on all views related to the workshops.

    Define the queryset to use for the current user.

    """

    def get_workshop_queryset_for_current_user(self):
        """Get all ``Workshop`` instances the current user is allowed to see.

        The user can only see workshops instance he own.
        Also excludes deleted workshops from the result.

        Returns
        -------
        Queryset of ``mtp.apps.workshops.models.Workshops``
            A lazy queryset with the wanted workshops.

        """
        return Workshop.objects.non_deleted().filter(
            training__in=self.get_training_queryset_for_current_user()
        )


class WorkshopEditBase(AjaxResponseMixin,
                       SuccessMessageMixin,
                       MaybeAssessableFormBaseView,
                       WorkshopBase):
    """Base view to create or update a given instance of
    ``mtp.apps.workshops.models.Workshop``

    """
    model = Workshop
    form_class = WorkshopForm
    assessable_form_class = WorkshopAssessableForm
    pk_url_kwarg = 'workshop_uuid'

    def get_queryset(self):
        return super().get_workshop_queryset_for_current_user()


class WorkshopDetail(WorkshopEditBase, generic.DetailView):
    """The details view of a ``mtp.apps.workshops.models.Workshop`` instance."""

    @cached_property
    def steps(self):
        """Get the step who belongs to the current workshop."""

        return WorkshopStep.objects.non_deleted().for_workshop(
            self.object
        ).order_by('sort_order')

    def get_context_data(self, **kwargs):
        """Add ``steps`` to the context.

        For the parameters, see ``django.forms.generic.base.ContextMixin``
        """

        return super().get_context_data(steps=self.steps, **kwargs)


class WorkshopCreate(AssessableCreateMixin, WorkshopEditBase, generic.CreateView):
    """The view to create a ``mtp.apps.workshops.models.Workshop`` instance.

    Notes
    -----
    We need to get the current ``Training`` and add it to the context.

    """

    success_message = _("Your workshop was successfully created.")

    @cached_property
    def is_assessing_container(self):
        return self.training.job_profile is not None

    def get_context_data(self, **kwargs):
        """Add ``training`` to the context.

        For the parameters, see ``django.forms.generic.base.ContextMixin``
        """

        return super().get_context_data(training=self.training, **kwargs)

    @cached_property
    def training(self):
        """Returns the current training based on the ``training_uuid``
        argument in the url.

        Raises
        ------
        ``django.http.Http404``
            If the training doesn't exists.

        """
        return get_object_or_404(
            Training.objects.non_deleted().filter(owner=self.request.user),
            pk=self.kwargs['training_uuid']
        )

    def form_valid(self, form):
        """Set the ``training`` save the workshop and redirect the user.

        Parameters
        ----------
        form : instance of ``django.forms.models.ModelForm``
            The validated form to save.

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the edit url of the workshop.

        Notes
        -----
        This method doesn't call ``super`` but implements all the necessary
        stuff (saving and redirecting)

        """
        self.object = form.save(commit=False)
        self.object.training = self.training
        self.object.save()

        return super().form_valid(form)


class WorkshopUpdate(WorkshopEditBase, generic.UpdateView):
    """The view to update a ``mtp.apps.workshops.models.Workshop`` instance."""

    success_message = _("Your workshop was successfully updated.")

    def get_context_data(self, **kwargs):
        """Add the ``Training`` and the related ``WorkshopSteps`` to the context.

        For the parameters, see ``django.forms.generic.base.ContextMixin``
        """

        return super().get_context_data(
            training=self.object.training,
            **kwargs)


class WorkshopDelete(WorkshopEditBase, SyncableDeleteView):
    """The view to delete a ``mtp.apps.workshops.models.Workshop`` instance."""

    success_message = _("Your workshop was successfully deleted.")


class WorkshopStepEditBase(SuccessMessageMixin, WorkshopBase):
    """Base view to create or update a given instance of ``mtp.apps.workshops.models.WorkshopStep``

    Notes
    -----
    We need to get the current ``Workshop`` and add it to the context.

    """

    pk_url_kwarg = 'step_uuid'

    @cached_property
    def workshop(self):
        """Returns the current workshop based on the ``workshop_uuid`` argument in the url.

        Raises
        ------
        ``django.http.Http404``
            If the workshop doesn't exists.

        """
        return get_object_or_404(
            self.get_workshop_queryset_for_current_user(),
            pk=self.kwargs['workshop_uuid']
        )

    def get_form_kwargs(self):
        """Add current workshop in form kwargs."""

        kwargs = super().get_form_kwargs()

        kwargs['workshop'] = self.workshop

        return kwargs

    def get_context_data(self, **kwargs):
        """Add ``workshop`` to the context.

        For the parameters, see ``django.forms.generic.base.ContextMixin``
        """

        return super().get_context_data(workshop=self.workshop, **kwargs)

    def get_queryset(self):
        """Get all ``WorkshopStep`` instances the current user is allowed to see.

        The user can only see workshop steps who belongs to the current workshop.
        Also excludes deleted workshops from the result.

        Returns
        -------
        Queryset of ``mtp.apps.workshops.models.WorkshopStep``
            A lazy queryset with the wanted workshop steps.

        """
        return WorkshopStep.objects.non_deleted().filter(
            workshop=self.workshop)

    def get_success_url(self):
        """Get the url where to go back when an action is done on a workshop step.

        It actually returns the url of the detail of the workshop.

        """

        return self.workshop.get_absolute_url()


class WorkshopStepSort(WorkshopStepEditBase, OrderableListMixin, generic.ListView):
    """View to sort the steps for a ``WorkshopStep`` instance."""

    model = WorkshopStep


class WorkshopStepsDelete(WorkshopStepEditBase, generic.FormView):
    """View to soft delete a list of ``WorkshopStep`` instance"""

    template_name = 'workshops/workshopsteps_delete.html'
    form_class = WorkshopStepsDeleteForm
    success_message = _('Your steps were successfully deleted.')

    def form_valid(self, form):
        """Handle the soft-delete action on the steps

        For the parameters, see ``generic.FormView``

        """

        super().form_valid(form)

        for step in form.cleaned_data['steps']:
            step.remove()

        return HttpResponseRedirect(self.get_success_url())


class WorkshopStepQuestionEditBase(MaybeAssessableFormBaseView,
                                   EditFormWithFormsetMixin,
                                   WorkshopStepEditBase):
    """Base view for create or edit a ``WorkshopStepQuestion`` instance."""

    model = WorkshopStepQuestion
    form_class = WorkshopStepQuestionForm
    formset_class = WorkshopStepQuestionAnswerFormSet
    context_formset_name = 'answers_formset'
    assessable_form_class = WorkshopStepQuestionAssessableForm

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()

        # If the workshop is assessing, we must be able to add dimensions
        # to the question. To be able to do so, the ``AssessableMixin`` used
        # by the form expects additional initialization kwargs.
        workshop = self.workshop
        form_is_assessable = self.get_form_class() is WorkshopStepQuestionAssessableForm
        if workshop.is_assessing and form_is_assessable:
            kwargs['training'] = workshop.training
            kwargs['activity'] = workshop

        return kwargs


class WorkshopStepQuestionCreate(AssessableCreateMixin,
                                 WorkshopStepQuestionEditBase,
                                 generic.CreateView):
    """View to create a new ``WorkshopStepQuestion`` instance."""

    @cached_property
    def is_assessing_container(self):
        return self.workshop.is_assessing

    success_message = _('Your question was successfully created.')

    def form_valid(self, form):
        """Handle the redirection accordingly to the submit button clicked

        For the parameters, see ``generic.CreateView``

        """

        super().form_valid(form)

        self.send_message(form.cleaned_data)

        if '_addanother' in self.request.POST:
            return HttpResponseRedirect(
                WorkshopStepQuestion.get_create_url(workshop=self.workshop)
            )
        else:
            return HttpResponseRedirect(self.get_success_url())


class WorkshopStepQuestionUpdate(WorkshopStepQuestionEditBase, generic.UpdateView):
    """View to update a ``WorkshopStepQuestion`` instance."""

    success_message = _('Your question was successfully updated.')


class WorkshopStepImageEditBase(WorkshopStepEditBase):
    """Base view for create or edit a ``WorkshopStepImage`` instance."""

    model = WorkshopStepImage
    form_class = WorkshopStepImageForm


class WorkshopStepImageCreate(WorkshopStepImageEditBase, generic.CreateView):
    """View to create a new ``WorkshopStepImage`` instance."""

    success_message = _('Your image was successfully created.')


class WorkshopStepImageUpdate(WorkshopStepImageEditBase, generic.UpdateView):
    """View to update a ``WorkshopStepImage`` instance."""

    success_message = _('Your image was successfully created.')


class XLSQuizExportView(ConditionalMixin, XLSExportDetailView):
    """Export XLS Quiz."""

    resource = WorkshopResource
    pk_url_kwarg = 'workshop_uuid'

    def is_condition_met(self):
        """Test if the Quiz is exportable

        The Quiz is not exportable if he holds any steps of the "image" type

        Returns
        -------
        bool
            True if the Quiz is exportable,
            False otherwise

        """

        return not self.get_object().steps.instance_of(WorkshopStepImage).exists()


class XLSQuizImportView(ActivityXLSImportView):
    """Import view for ``Workshop`` activity."""

    template_example_file = 'import_export/Workshop.template.xlsx'

    def import_data(self, sheet_name):
        resource = WorkshopResource
        data_object = self.reader.read_values(
            sheet_name,
            len(resource.get_fields(allow_nested=False))
        )

        try:
            weight = data_object.pop('weight')
        except KeyError:
            weight = 1

        if self.is_compatible(data_object, resource):
            obj, __ = self.create_object(
                data_object, resource=resource, force_creation=True,
                weight=weight, training=self.training, is_assessing=self.is_assessing
            )
        else:
            fields = resource.get_display_fields(allow_nested=False)
            raise ImportDataError(
                _("Data is not compatible, we need this fields %(fields)s.") % {'fields': fields},
                line=data_object.line
            )

        for data in self.reader.read_table(sheet_name):
            if self.is_compatible(data, WorkshopStepQuestionResource):
                step_type = data.pop(
                    WorkshopStepQuestionResource.field_name_to_display_field('type'))
                if step_type not in STEP_TYPES_QUESTION:
                    raise ImportDataError(
                        _("Your question type provided is not compatible."),
                        line=data.line
                    )
                question, __ = self.create_object(
                    data, resource=WorkshopStepQuestionResource, force_creation=True,
                    workshop=obj
                )
                if step_type == QUESTION_MULTIPLE:
                    question.is_multiple_choices = True
                    question.save()
            elif self.is_compatible(data, WorkshopStepQuestionAnswerResource):
                try:
                    self.create_object(
                        data, resource=WorkshopStepQuestionAnswerResource, force_creation=True,
                        question=question
                    )
                except UnboundLocalError:
                    raise ImportDataError(
                        _("Problem in your file: no question provide before answers."),
                        line=data.line
                    )
            else:
                raise ImportDataError(_("Data is not compatible."), line=data.line)

        return obj
