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

All views in this module must inherit from ``EvaluationBaseView`` to correctly
handle the currently active section in the main menu and the access
restrictions.

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

"""

from django.http import HttpResponse, 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.common.views_utils import (
    AjaxResponseMixin,
    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 .forms import CriterionForm, EvaluationForm, ExerciseForm
from .models import Criterion, Evaluation, Exercise
from .resources import EvaluationResource, EvaluationCriterionResource, EvaluationExerciseResource
from .utils import export_evaluation_stat_xls


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

    Define the queryset to use for the current user.

    """

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

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

        Returns
        -------
        Queryset of ``mtp.apps.evaluations.models.Evaluations``
            A lazy queryset with the wanted evaluations.

        """
        return Evaluation.objects.non_deleted().filter(
            training=self.get_training_queryset_for_current_user()
        )


class EvaluationEditBase(AjaxResponseMixin, SuccessMessageMixin, EvaluationBase):
    """Base view to create or update a given instance of
    ``mtp.apps.evaluations.models.Evaluation``

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

    """
    model = Evaluation
    form_class = EvaluationForm
    pk_url_kwarg = 'evaluation_uuid'

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

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

        Returns
        -------
        Queryset of ``mtp.apps.evaluations.models.Evaluation``
            A lazy queryset with the wanted evaluations.

        """
        return super().get_evaluation_queryset_for_current_user()


class EvaluationCreate(EvaluationEditBase, generic.CreateView):
    """The view to create a ``mtp.apps.evaluations.models.Evaluation`` instance.

    Notes
    -----

    We need to get the ``Training`` instance from the url and add it to
    the context. The ``training`` and the ``author`` are automatically set
    on saving.

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

    @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 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)

    def form_valid(self, form):
        """Set the ``training``, the ``author``, save the evaluation 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 evaluation.

        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 EvaluationDetail(EvaluationEditBase, generic.DetailView):
    """The view to show the details of a ``mtp.apps.evaluations.models.Evaluation`` instance."""

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

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

        return super().get_context_data(
            training=self.object.training,
            exercises=Exercise.objects.non_deleted().for_evaluation(self.object),
            **kwargs)


class EvaluationUpdate(EvaluationEditBase, generic.UpdateView):
    """The view to update a ``mtp.apps.evaluations.models.Evaluation`` instance.

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

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

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

        return super().get_context_data(
            training=self.object.training,
            exercises=Exercise.objects.non_deleted().for_evaluation(self.object),
            **kwargs)


class ExerciseBaseEdit(EvaluationBase):
    """Base view to create or update a given instance of
    ``mtp.apps.evaluations.models.Exercise``

    """
    model = Exercise
    form_class = ExerciseForm
    pk_url_kwarg = 'exercise_uuid'

    def get_queryset(self):
        """Queryset to use to filter only exercises associated to the current evaluation.

        """
        return Exercise.objects.non_deleted().for_evaluation(self.evaluation)

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

        It actually returns the url to see the evaluation details.

        """
        return self.object.evaluation.get_detail_url()

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

        Raises
        ------
        ``django.http.Http404``
            If the current user cannot see the evaluation, or the evaluation
            doesn't exist.

        """
        return get_object_or_404(
            self.get_evaluation_queryset_for_current_user(),
            pk=self.kwargs['evaluation_uuid']
        )

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

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

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


class ExercisesSort(OrderableListMixin, ExerciseBaseEdit, generic.ListView):
    """View to sort the ``Exercise`` instances of the given ``Evaluation``."""


class ExerciseCreate(SuccessMessageMixin, ExerciseBaseEdit, generic.CreateView):
    """The view to create a ``mtp.apps.evaluations.models.Exercise`` instance.

    Notes
    -----

    The ``evaluation`` is automatically set on saving.

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

    def get_success_url(self):
        """Return the edition url of the created exercise with criteria anchor."""

        return "%(url)s#%(anchor)s" % {
            'url': self.object.get_edit_url(),
            'anchor': 'criteria',
        }

    def form_valid(self, form):
        """Set the ``evaluation``, save the exercise 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 evaluation.

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

        """

        self.object = form.save(commit=False)
        self.object.evaluation = self.evaluation
        self.object.save()

        self.send_message(form.cleaned_data)

        return HttpResponseRedirect(self.get_success_url())


class ExerciseUpdate(SuccessMessageMixin, ExerciseBaseEdit, generic.UpdateView):
    """The view to update a ``mtp.apps.evaluations.models.Exercise`` instance.

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

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

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

        return super().get_context_data(
            criteria=Criterion.objects.non_deleted().for_exercise(self.object),
            **kwargs)


class ExerciseDelete(SuccessMessageMixin, ExerciseBaseEdit, SyncableDeleteView):
    """The view to delete a ``mtp.apps.evaluations.models.Exercise`` instance.

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


class CriterionEditBase(EvaluationBase):
    """Base view to create or update a given instance of
    ``mtp.apps.evaluations.models.Criterion``

    """
    model = Criterion
    form_class = CriterionForm
    pk_url_kwarg = 'criterion_uuid'

    def get_queryset(self):
        """Queryset to use to filter only criteria associated to the current exercise.

        """
        return Criterion.objects.non_deleted().for_exercise(self.exercise)

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

        It actually returns the url to edit the exercise.

        """
        return self.object.exercise.get_edit_url()

    @cached_property
    def exercise(self):
        """Return the current exercise based on the ``exercise_uuid`` argument
        in the url.

        Raises
        ------
        ``django.http.Http404``
            If the current user cannot see the exercise, or the exercise doesn't exist.

        """
        return get_object_or_404(
            Exercise.objects.non_deleted().filter(
                evaluation=self.get_evaluation_queryset_for_current_user()
            ),
            pk=self.kwargs['exercise_uuid']
        )

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

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

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


class CriteriaSort(OrderableListMixin, CriterionEditBase, generic.ListView):
    """View to sort the ``Criterion`` instances of the given ``Exercise``."""


class CriterionCreate(SuccessMessageMixin, CriterionEditBase, generic.CreateView):
    """The view to create a ``mtp.apps.evaluations.models.Criterion`` instance.

    Notes
    -----

    The ``exercise`` is automatically set on saving.

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

    def form_valid(self, form):
        """Set the ``exercise``, save the criterion 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 exercise.

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

        """

        self.object = form.save(commit=False)
        self.object.exercise = self.exercise
        self.object.save()

        self.send_message(form.cleaned_data)

        return HttpResponseRedirect(self.get_success_url())


class CriterionUpdate(SuccessMessageMixin, CriterionEditBase, generic.UpdateView):
    """The view to update a ``mtp.apps.evaluations.models.Criterion`` instance.

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


class CriterionDelete(SuccessMessageMixin, CriterionEditBase, SyncableDeleteView):
    """The view to delete a ``mtp.apps.evaluations.models.Criterion`` instance.

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


class XLSEvaluationExportView(XLSExportDetailView):
    """Export XLS Evaluation."""

    resource = EvaluationResource
    pk_url_kwarg = 'evaluation_uuid'


class XLSEvaluationImportView(ActivityXLSImportView):
    """ View for import evaluation """

    model = Evaluation
    template_example_file = 'import_export/Evaluation.template.xlsx'

    def import_data(self, sheet_name):

        resource = EvaluationResource
        data_object = self.reader.read_values(
            sheet_name,
            len(resource.get_fields(allow_nested=False))
        )

        if self.is_compatible(data_object, resource):
            obj, __ = self.create_object(
                data_object, resource=resource, force_creation=True,
                training=self.training)
        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, EvaluationExerciseResource):
                exercise, __ = self.create_object(
                    data, resource=EvaluationExerciseResource, force_creation=True,
                    evaluation=obj
                )
            elif self.is_compatible(data, EvaluationCriterionResource):
                try:
                    if 'criteria verbatim description' in data:
                        data['criterion_type'] = 'QL'
                    elif 'criteria numeric description' in data:
                        data['criterion_type'] = 'QT'
                    self.create_object(
                        data, resource=EvaluationCriterionResource, force_creation=True,
                        exercise=exercise
                    )
                except UnboundLocalError:
                    raise ImportDataError(
                        _("Problem in your file: no exercise is provided before criterions"),
                        line=data.line
                    )
            else:
                raise ImportDataError(_("Data is not compatible."), line=data.line)

        return obj


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

    See Also
    --------
    ``mtp.apps.evaluations.utils.export_evaluation_stat_raw``

    """

    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_evaluation_stat_xls(session)
        file_name = 'session_%s-%s_%s_eval_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
