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

All views in this module must inherit from ``SurveyBaseView`` 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.

"""

import json

from django.core.urlresolvers import reverse
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.common.views_utils import (
    EditFormWithFormsetMixin, OrderableListMixin, RequireSafeMixin,
    RestrictToSuperuserOrTrainerMixin, RestrictToTrainerMixin, SuccessMessageMixin,
    SyncableDeleteView
)
from mtp.apps.trainings.models import Training
from mtp.apps.translation.export_views.views import TranslationExportListView
from mtp.apps.translation.import_views.views import ContentTranslationImportViewMixin

from .forms import ChoicesFormSet, QuestionForm, SurveyPoolsFormSet
from .models import Pool, Question, Survey


class SurveyBaseView(RestrictToSuperuserOrTrainerMixin):
    """Base view for all views related to the surveys.

    Define the ``current_main_section`` entry in the context, limit access to active
    superusers and trainers, and provides the queryset to use, depending on the user.

    """

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

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

        return super().get_context_data(current_main_section='surveys', **kwargs)

    def get_pool_queryset_for_current_user(self, with_questions_count=False):
        """Get all ``Pool`` instances the current user is allowed to see.

        If the user is a superuser, it will see all the global pools.
        If it's a trainer, it will only see his own pools.

        Also excludes deleted pools from the result.

        Returns
        -------
        Queryset of ``mtp.apps.surveys.models.Pool``
            A lazy queryset with the wanted pools.

        """

        if self.request.user.is_superuser:
            return Pool.non_deleted.filter_for_admin(admin=self.request.user,
                                                     include_others=True,
                                                     with_questions_count=with_questions_count)
        else:
            return Pool.non_deleted.filter_for_trainer(trainer=self.request.user,
                                                       include_globals=False,
                                                       with_questions_count=with_questions_count)


class PoolsBaseView(SurveyBaseView):
    """Base view to manage ``Question`` instances of a given ``Pool``.

    This provide a default ``get_queryset`` method to use in all views related to the ``Pool``
    model.

    """
    model = Pool

    def get_queryset(self, with_questions_count=False):
        """Queryset to use to filter only pools visible to the current user.

        See ``SurveyBaseView.get_pool_queryset_for_current_user``.

        """

        return self.get_pool_queryset_for_current_user(with_questions_count)


class PoolsList(RequireSafeMixin, PoolsBaseView, generic.ListView):
    """View to list the ``Pool`` instances the current user is allowed to see."""

    def get_queryset(self):  # pylint: disable=arguments-differ
        # we force the ``with_questions_count`` argument to ``True`` so we don't accept it as arg
        return super().get_queryset(with_questions_count=True)


class PoolsSort(OrderableListMixin, PoolsBaseView, generic.ListView):
    """View to sort the ``Pool`` instances the current user is allowed to see."""


class PoolEditBase(SuccessMessageMixin, PoolsBaseView):
    """A base view for all views related to editing a ``Pool`` instance.

    It will work for creation, edition and deletion.

    """

    fields = ['name', 'is_mandatory']
    pk_url_kwarg = 'pool_uuid'

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

        It actually returns the url of the list of pools.

        """
        return self.model.get_list_url()


class PoolCreate(PoolEditBase, generic.CreateView):
    """The view to create a ``Pool`` instance.

    The author is automatically set on saving.

    """

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

    def form_valid(self, form):
        """Set the author, save the pool 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 list of pools.

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

        """

        self.object = form.save(commit=False)
        self.object.author = self.request.user
        self.object.save()

        self.send_message(form.cleaned_data)

        return HttpResponseRedirect(self.get_success_url())


class PoolUpdate(PoolEditBase, generic.UpdateView):
    """The view to edit a ``Pool`` instance"""

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


class PoolDelete(PoolEditBase, SyncableDeleteView):
    """The view to delete a ``Pool`` instance, with confirmation"""

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


class QuestionsBaseView(SurveyBaseView):
    """Base view to manage ``Question`` instances of a given ``Pool``.

    This provide a ``pool`` property and a default ``get_queryset`` method to use in all views
    related to the ``Question`` model.
    Also add the pool the context.

    Attributes
    ----------
    pool : instance of ``mtp.apps.surveys.models.Pool``
        It's a cached property that returns the pool based on the url, or raise
        a ``Http404`` exception if the pool doesn't exists or cannot be seen by
        the current user.

    """
    model = Question

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

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

        """

        return get_object_or_404(
            self.get_pool_queryset_for_current_user(),
            pk=self.kwargs['pool_uuid']
        )

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

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

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

    def get_queryset(self):
        """Queryset to use to display questions, for the pool passed in the url."""

        return Question.non_deleted.filter_for_pool(self.pool)


class QuestionsList(RequireSafeMixin, QuestionsBaseView, generic.ListView):
    """View to list the ``Question`` instances of a given ``Pool``."""


class QuestionsSort(OrderableListMixin, QuestionsBaseView, generic.ListView):
    """View to sort the ``Pool`` instances of a given ``Pool``."""


class QuestionEditBase(QuestionsBaseView):
    """A base view for all views related to editing a ``Question`` instance.

    It will work for creation, edition and deletion.

    """

    pk_url_kwarg = 'question_uuid'

    def __init__(self, *args, **kwargs):
        self.choices_formset = None
        super().__init__(*args, **kwargs)

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

        It actually returns the url of the list of questions of the pool.

        """

        return self.pool.get_questions_list_url()


class QuestionFormView(SuccessMessageMixin, EditFormWithFormsetMixin, QuestionEditBase):
    form_class = QuestionForm
    formset_class = ChoicesFormSet
    context_formset_name = 'choices_formset'

    def save_form(self, form, commit=True):
        """Save the form, forcing it's pool to be the one got from the url attribute.

        For the parameters, see ``mtp.apps.commons.views_utils.EditFormWithFormsetMixin``"""

        # 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

        # Force the pool to be the current one, got from the url parameter
        self.object.pool = self.pool

        # Save the pool only if asked.
        if commit:
            self.object.save()

    def get_context_data(self, **kwargs):
        """Add configuration values from the ``Question`` model for js manipulation.

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

        return super().get_context_data(
            allowed_value_types=json.dumps(Question.ALLOWED_VALUE_TYPES),
            choices_expected=json.dumps(Question.CHOICES_EXPECTED),
            allowed_global_satisfaction=json.dumps(Question.ALLOWED_GLOBAL_SATISFACTION),
            **kwargs
        )


class QuestionCreate(QuestionFormView, generic.CreateView):
    """The view to create a ``Question`` instance."""

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


class QuestionUpdate(QuestionFormView, generic.UpdateView):
    """The view to edit a ``Question`` instance"""

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


class QuestionDelete(SuccessMessageMixin, QuestionEditBase, SyncableDeleteView):
    """The view to delete a ``Question`` instance, with confirmation"""

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


class TrainingSurveyView(SuccessMessageMixin, EditFormWithFormsetMixin,
                         RestrictToTrainerMixin, generic.UpdateView):
    """View to edit a survey for a training by a trainer.

    Note that the survey is specific to this couple (training, trainer)
    """

    model = Survey
    fields = []
    formset_class = SurveyPoolsFormSet
    context_formset_name = 'pools_formset'

    success_message = _("The survey was successfully updated.")

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

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

        """

        return get_object_or_404(Training, pk=self.kwargs['training_uuid'])

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

        For the parameters, see ``django.forms.generic.detail.SingleObjectMixin``
        """

        # We add the training, and we split the pool forms in two: global and owns owned by the
        # user, to ease rendering.

        return super().get_context_data(
            training=self.training,
            pool_forms=[
                (True, [form for form in self.formset.forms if form.instance.pool.is_global]),
                (False, [form for form in self.formset.forms if not form.instance.pool.is_global]),
            ],
            **kwargs)

    def get_object(self, queryset=None):
        """Get the survey based on the training in the url and the current user.

        For the parameters, see ``django.views.generic.detail.SingleObjectMixin``"""

        # Returns [0] because the result is a tuple with the survey and a "created" flag.
        return Survey.objects.get_or_create_for_training(self.training, self.request.user)[0]

    def get_success_url(self):
        """Get the url where to go back when the survey is saved..

        It actually returns the url of the survey..

        """

        return self.training.get_survey_url()

    def get_formset(self, **kwargs):
        return super().get_formset(
            queryset=self.object.get_survey_pools(only_with_questions=True, only_displayed=False)
        )


class SurveyExportTranslationView(TranslationExportListView):
    """ This view allows to export text to translate for pools list. """

    model = Pool


class SurveyImportTranslationView(ContentTranslationImportViewMixin):
    """Allow to import text to translate for all surveys."""

    def get_old_form_url(self):
        return reverse('assessments:import-old-translation')
