"""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
from mtp.apps.common.views_utils import (
    AjaxResponseMixin,
    SuccessMessageMixin,
    SyncableDeleteView,
    OrderableListMixin,
)
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 (
    WorkshopStepQuestionAnswerForm,
    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, WorkshopBase):
    """Base view to create or update a given instance of
    ``mtp.apps.workshops.models.Workshop``

    """
    model = Workshop
    fields = [
        'name',
        'description',
        'is_assessing',
        'is_broadcastable',
        'is_steps_order_random',
        'are_questions_mandatory',
        'is_questions_answers_order_random',
        'needs_approval',
        'enable_score'
    ]
    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."""

        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."""

        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."""

        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_context_data(self, **kwargs):
        """Add ``workshop`` to the context."""

        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.object.workshop.get_absolute_url()

    def form_valid(self, form):
        """Set the ``workshop``, save the workshop step 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.workshop = self.workshop
        self.object.save()

        return super().form_valid(form)


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(WorkshopStepEditBase):
    """Base view for create or edit a ``WorkshopStepQuestion`` instance."""

    model = WorkshopStepQuestion
    form_class = WorkshopStepQuestionForm

    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
        if workshop.is_assessing:
            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."""

        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.

        """
        return self.object.question.workshop.get_absolute_url()

    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)

        """
        self.object = form.save(commit=False)
        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
    fields = [
        'title',
        'description',
        'image'
    ]


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(XLSExportDetailView):
    """Export XLS Quiz."""

    resource = WorkshopResource
    pk_url_kwarg = 'workshop_uuid'


class XLSQuizImportView(ActivityXLSImportView):

    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, created = 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, is_assessing=self.is_assessing
                )
                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

        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
