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

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

"""

from django.http import HttpResponse
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,
    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, TrainingSessionBaseView

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


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=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(AjaxResponseMixin, 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 "%(url)s#%(anchor)s" % {
            'url': self.workshop.get_absolute_url(),
            'anchor': self.object.id,
        }


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

    model = WorkshopStep


class WorkshopStepDelete(WorkshopStepEditBase, SyncableDeleteView):
    """View to soft-delete a ``WorkshopStep`` instance."""

    def get_queryset(self):
        """Get the non-polymophic queryset to get the parent object type.

        So the object_name will be ``workshop_step`` and not one of his child's name.

        """
        return super().get_queryset().non_polymorphic()

    success_message = _('Your step was successfully delete.')


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

    model = WorkshopStepQuestion
    form_class = WorkshopStepQuestionForm
    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.')


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

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


class WorkshopStepQuestionAnswerEditBase(AjaxResponseMixin, SuccessMessageMixin, WorkshopBase):
    """Base view to edit or create a ``WorkshopStepQuestionAnswer`` instance."""

    pk_url_kwarg = 'answer_uuid'
    model = WorkshopStepQuestionAnswer
    form_class = WorkshopStepQuestionAnswerForm

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

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

        """
        return get_object_or_404(
            WorkshopStepQuestion.objects.non_deleted().filter(
                workshop=self.get_workshop_queryset_for_current_user()),
            pk=self.kwargs['question_uuid']
        )

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

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

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

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

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

        """
        return WorkshopStepQuestionAnswer.objects.non_deleted().filter(
            question__workshop=self.get_workshop_queryset_for_current_user())

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

        It actually returns the url of the detail of the workshop
        with the edited question id as anchor.

        """

        return "%(url)s#%(anchor)s" % {
            'url': self.object.question.workshop.get_absolute_url(),
            'anchor': self.object.question.id,
        }

    def form_valid(self, form):
        """Set the ``question``, save the answer 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 detail url of the workshop.

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

        """
        # it's a mixin, `self.object` will be defined in get/post of standard imported view class
        self.object = form.save(commit=False)  # pylint: disable=attribute-defined-outside-init
        self.object.question = self.question
        self.object.save()

        return super().form_valid(form)


class WorkshopStepQuestionAnswerSort(WorkshopStepQuestionAnswerEditBase, OrderableListMixin,
                                     generic.ListView):
    """View to sort ``WorkshopStepQuestionAnswer`` instances."""

    def get_queryset(self):
        """Return the answers for the current question."""
        return super().get_queryset().filter(question=self.question)


class WorkshopStepQuestionAnswerCreate(WorkshopStepQuestionAnswerEditBase, generic.CreateView):
    """View to create a new ``WorkshopStepQuestionAnswer`` instance."""

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


class WorkshopStepQuestionAnswerUpdate(WorkshopStepQuestionAnswerEditBase, generic.UpdateView):
    """View to update a ``WorkshopStepQuestionAnswer`` instance."""

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


class WorkshopStepQuestionAnswerDelete(WorkshopStepQuestionAnswerEditBase, SyncableDeleteView):
    """View to soft-delete a ``WorkshopStepQuestionAnswer`` instance."""

    success_message = _('Your answer was successfully deleted.')


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


class ExportWorkshopSessionStats(TrainingSessionBaseView, generic.DetailView):
    """View to download a file containing the statistics of the workshops

    See Also
    --------
    ``mtp.apps.workshops.utils.export_workshop_stat_xls``

    """

    def get(self, request, *args, **kwargs):
        """Get the current session and build the xlsx file to download

        For the parameters, see ``django.views.generic.View``

        Returns
        -------
        ``HttpResponse``
            The response with the attached xlsx file

        """

        session = self.get_object()
        trainer = session.trainer
        xls = export_workshop_stat_xls(session)
        file_name = 'session_%s-%s_%s_export.xlsx' % (
            trainer.first_name, trainer.last_name, session.start_date.strftime('%Y-%m-%d')
        )

        response = HttpResponse(content_type='application/vnd.ms-excel')
        response['Content-Disposition'] = 'attachment; filename=%s' % file_name

        xls.seek(0)
        response.write(xls.read())

        return response
