"""This module contains the views for the ``questionnaires`` app"""

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 (
    AjaxResponseMixin,
    EditFormWithFormsetMixin,
    OrderableListMixin,
    SuccessMessageMixin,
    SyncableDeleteView,
)
from mtp.apps.trainings.models import Training, TrainingActivity
from mtp.apps.trainings.views import TrainingBaseView

from .forms import (
    ChoicesQuestionChoicesFormSet,
    ChoicesQuestionForm,
    ImageChoicesQuestionForm,
    OpenQuestionForm,
    QuestionsDeleteForm,
    ScoringChoicesQuestionChoicesFormSet,
    ScoringChoicesQuestionForm,
    ScoringImageChoicesQuestionForm,
)
from .models import (
    Choice,
    ChoicesQuestion,
    ImageChoicesQuestion,
    LiveQuiz,
    LivePolling,
    OpenQuestion,
    Polling,
    Question,
    Quiz,
)


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

    Define the queryset to use for the current user.

    """

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

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

        """

        return TrainingActivity.objects.non_deleted().filter(
            training__in=self.get_training_queryset_for_current_user()
        )


class QuestionnaireDetailMixin:
    """Base view to use for the details views of the ``Questionnaire`` subclasses"""

    template_name = 'questionnaires/questionnaire_detail.html'

    @cached_property
    def questions(self):
        """Get the questions who belongs to the current questionnaire."""

        return Question.objects.non_deleted().filter(
            questionnaire_id=self.object.id
        ).order_by('sort_order')

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

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

        """

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


class QuestionnaireEditMixin:
    """Base view to use for the edit views of the ``Questionnaire`` subclasses"""

    template_name = 'questionnaires/questionnaire_form.html'

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

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

        """

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


class QuestionnaireCreateMixin:
    """Base view to use for the create views of the ``Questionnaire`` subclasses"""

    template_name = 'questionnaires/questionnaire_form.html'

    def get_context_data(self, **kwargs):
        """Add the ``Training``, the questionnaire model and the create url to the context

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

        """

        return super().get_context_data(
            training=self.training,
            questionnaire_model=self.model,
            create_url=self.model.get_create_url(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 questionnaire 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 questionnaire.

        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 QuizEditBase(AjaxResponseMixin, SuccessMessageMixin, QuestionnaireBase):
    """Base view to create or update a given instance of ``mtp.apps.questionnaires.models.Quiz``"""

    model = Quiz
    fields = (
        'name',
        'description',
        'is_score_displayed',
        'is_questions_order_random',
        'are_questions_mandatory',
    )
    pk_url_kwarg = 'quiz_uuid'

    def get_queryset(self):
        """Filter the ``Quiz`` instances accordingly to the current user"""

        return super().get_questionnaire_queryset_for_current_user()


class QuizDetail(QuizEditBase, QuestionnaireDetailMixin, generic.DetailView):
    """The details view of a ``mtp.apps.questionnaires.models.Quiz`` instance."""

    pass


class QuizCreate(QuizEditBase, QuestionnaireCreateMixin, generic.CreateView):
    """The create view of a ``mtp.apps.questionnaires.models.Quiz`` instance."""

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


class QuizUpdate(QuizEditBase, QuestionnaireEditMixin, generic.UpdateView):
    """The update view of a ``mtp.apps.questionnaires.models.Quiz`` instance."""

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


class QuizDelete(QuizEditBase, QuestionnaireEditMixin, SyncableDeleteView):
    """The delete view of a ``mtp.apps.questionnaires.models.Quiz`` instance."""

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


class LiveQuizEditBase(AjaxResponseMixin, SuccessMessageMixin, QuestionnaireBase):
    """Base view to create or update a given instance of
    ``mtp.apps.questionnaires.models.LiveQuiz``
    """

    model = LiveQuiz
    fields = (
        'name',
        'description',
        'is_score_displayed',
    )
    pk_url_kwarg = 'livequiz_uuid'

    def get_queryset(self):
        """Filter the ``LiveQuiz`` instances accordingly to the current user"""

        return super().get_questionnaire_queryset_for_current_user()


class LiveQuizDetail(LiveQuizEditBase, QuestionnaireDetailMixin, generic.DetailView):
    """The details view of a ``mtp.apps.questionnaires.models.LiveQuiz`` instance."""

    pass


class LiveQuizCreate(LiveQuizEditBase, QuestionnaireCreateMixin, generic.CreateView):
    """The create view of a ``mtp.apps.questionnaires.models.LiveQuiz`` instance."""

    success_message = _('Your live quiz was successfully created.')


class LiveQuizUpdate(LiveQuizEditBase, QuestionnaireEditMixin, generic.UpdateView):
    """The update view of a ``mtp.apps.questionnaires.models.LiveQuiz`` instance."""

    success_message = _('Your live quiz was successfully updated.')


class LiveQuizDelete(LiveQuizEditBase, QuestionnaireEditMixin, SyncableDeleteView):
    """The delete view of a ``mtp.apps.questionnaires.models.LiveQuiz`` instance."""

    success_message = _('Your live quiz was successfully deleted.')


class PollingEditBase(AjaxResponseMixin, SuccessMessageMixin, QuestionnaireBase):
    """Base view to create or update a given instance of
    ``mtp.apps.questionnaires.models.Polling``
    """

    model = Polling
    fields = (
        'name',
        'description',
        'are_questions_mandatory',
    )
    pk_url_kwarg = 'polling_uuid'

    def get_queryset(self):
        """Filter the ``Polling`` instances accordingly to the current user"""

        return super().get_questionnaire_queryset_for_current_user()


class PollingDetail(PollingEditBase, QuestionnaireDetailMixin, generic.DetailView):
    """The details view of a ``mtp.apps.questionnaires.models.Polling`` instance."""

    pass


class PollingCreate(PollingEditBase, QuestionnaireCreateMixin, generic.CreateView):
    """The create view of a ``mtp.apps.questionnaires.models.Polling`` instance."""

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


class PollingUpdate(PollingEditBase, QuestionnaireEditMixin, generic.UpdateView):
    """The update view of a ``mtp.apps.questionnaires.models.Polling`` instance."""

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


class PollingDelete(PollingEditBase, QuestionnaireEditMixin, SyncableDeleteView):
    """The delete view of a ``mtp.apps.questionnaires.models.Polling`` instance."""

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


class LivePollingEditBase(AjaxResponseMixin, SuccessMessageMixin, QuestionnaireBase):
    """Base view to create or update a given instance of
    ``mtp.apps.questionnaires.models.LivePolling``
    """

    model = LivePolling
    fields = (
        'name',
        'description',
    )
    pk_url_kwarg = 'livepolling_uuid'

    def get_queryset(self):
        """Filter the ``LivePolling`` instances accordingly to the current user"""

        return super().get_questionnaire_queryset_for_current_user()


class LivePollingDetail(LivePollingEditBase, QuestionnaireDetailMixin, generic.DetailView):
    """The details view of a ``mtp.apps.questionnaires.models.LivePolling`` instance."""

    pass


class LivePollingCreate(LivePollingEditBase, QuestionnaireCreateMixin, generic.CreateView):
    """The create view of a ``mtp.apps.questionnaires.models.LivePolling`` instance."""

    success_message = _('Your live polling was successfully created.')


class LivePollingUpdate(LivePollingEditBase, QuestionnaireEditMixin, generic.UpdateView):
    """The update view of a ``mtp.apps.questionnaires.models.LivePolling`` instance."""

    success_message = _('Your live polling was successfully updated.')


class LivePollingDelete(LivePollingEditBase, QuestionnaireEditMixin, SyncableDeleteView):
    """The delete view of a ``mtp.apps.questionnaires.models.LivePolling`` instance."""

    success_message = _('Your live polling was successfully deleted.')


class QuestionEditBase(SuccessMessageMixin, QuestionnaireBase):
    """Base view to create or update a given instance of ``Question``

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

    """

    pk_url_kwarg = 'question_uuid'

    @cached_property
    def questionnaire(self):
        """Get the current questionnaire based on the ``questionnaire_uuid`` argument in the url.

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

        """

        return get_object_or_404(
            self.get_questionnaire_queryset_for_current_user(),
            pk=self.kwargs['questionnaire_uuid']
        )

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

        kwargs = super().get_form_kwargs()

        kwargs['questionnaire'] = self.questionnaire

        return kwargs

    def get_context_data(self, **kwargs):
        """Add the current questionnaire to the context

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

        """

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

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

        The user can only see questions who belongs to the current questionnaire.
        Also excludes deleted questions from the result.

        Returns
        -------
        Queryset of ``Question``
            A lazy queryset with the wanted questions.

        """

        return Question.objects.non_deleted().filter(
            questionnaire_id=self.questionnaire.id
        )

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

        If we got a "next url" in the GET params, return this url,
        Else, return the url of the detail of the questionnaire.

        """

        if 'next_url' in self.request.GET:
            return self.request.GET['next_url']

        return self.questionnaire.get_absolute_url()


class QuestionCreateBase(generic.CreateView):
    """Base view to create a new ``Question`` instance"""

    def get_context_data(self, **kwargs):
        """Add the create url to the context

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

        """

        return super().get_context_data(
            create_url=self.model.get_create_url(
                questionnaire=self.questionnaire
            ),
            **kwargs
        )


class QuestionsSort(QuestionEditBase, OrderableListMixin, generic.ListView):
    """View to sort the questions for a ``Questionnaire`` instance."""

    model = Question
    template_name = 'questionnaires/questions_sort.html'


class QuestionsDelete(QuestionEditBase, generic.FormView):
    """View to soft delete a list of ``Question`` instances"""

    template_name = 'questionnaires/questions_delete.html'
    form_class = QuestionsDeleteForm
    success_message = _('Your questions were successfully deleted.')

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

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

        """

        super().form_valid(form)

        for question in form.cleaned_data['questions']:
            question.remove()

        return HttpResponseRedirect(self.get_success_url())


class ChoicesQuestionEditBase(QuestionEditBase, EditFormWithFormsetMixin):
    """Base view for create or update a ``ChoicesQuestion`` instance."""

    model = ChoicesQuestion
    context_formset_name = 'choices_formset'
    template_name = 'questionnaires/choicesquestion_form.html'

    def get_form_class(self):
        """Get the form class to use, accordingly to the current ``Questionnaire`` subclass

        Returns
        -------
        class
            The form class to use

        """

        if self.questionnaire.is_scoring:
            return ScoringChoicesQuestionForm
        return ChoicesQuestionForm

    def get_formset_class(self):
        """Get the form class to use, accordingly to the current ``Questionnaire``subclass

        Returns
        -------
        class
            The formset class to use

        """

        if self.questionnaire.is_scoring:
            return ScoringChoicesQuestionChoicesFormSet
        return ChoicesQuestionChoicesFormSet

    def get_formset(self, **kwargs):
        """Filter out the deleted ``Choices`` in the formset

        Also, override some attributes of the formset if the current ``Questionnaire``
        is a "live" one.

        Parameters
        ----------
        For the parameters, see ``EditFormWithFormsetMixin``

        """

        formset = super().get_formset(
            queryset=Choice.objects.non_deleted(),
            form_kwargs={
                'questionnaire': self.questionnaire
            },
            **kwargs
        )

        if self.questionnaire.is_live:
            formset.max_num = 5  # Max 5 choices when the questionnaire is "live"

        return formset


class ChoicesQuestionCreate(ChoicesQuestionEditBase, QuestionCreateBase):
    """View to create a new ``ChoicesQuestion`` instance."""

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


class ChoicesQuestionUpdate(ChoicesQuestionEditBase, generic.UpdateView):
    """View to update ``ChoicesQuestion`` instance."""

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


class ImageChoicesQuestionEditBase(ChoicesQuestionEditBase):
    """Base view for create or update a ``ImageChoicesQuestion`` instance."""

    model = ImageChoicesQuestion

    def get_form_class(self):
        """Get the form class to use, accordingly to the current ``Questionnaire`` subclass

        Returns
        -------
        class
            The form class to use

        """

        if self.questionnaire.is_scoring:
            return ScoringImageChoicesQuestionForm
        return ImageChoicesQuestionForm


class ImageChoicesQuestionCreate(ImageChoicesQuestionEditBase, QuestionCreateBase):
    """View to create a new ``ImageChoicesQuestion`` instance."""

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


class ImageChoicesQuestionUpdate(ImageChoicesQuestionEditBase, generic.UpdateView):
    """View to create a new ``ImageChoicesQuestion`` instance."""

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


class OpenQuestionEditBase(QuestionEditBase):
    """Base view to create or update an ``OpenQuestion`` instance"""

    model = OpenQuestion
    form_class = OpenQuestionForm


class OpenQuestionCreate(OpenQuestionEditBase, QuestionCreateBase):
    """View to create a new ``OpenQuestion`` instance."""

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


class OpenQuestionUpdate(OpenQuestionEditBase, generic.UpdateView):
    """View to update an ``OpenQuestion`` instance."""

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

