"""This module holds the ``TrainingActivity`` related views for the ``trainings`` app."""

from django.http import HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, reverse
from django.urls import reverse_lazy
from django.utils.functional import cached_property
from django.utils.text import Truncator
from django.utils.translation import ugettext_lazy as _
from django.views import generic

from django_filters.views import FilterView

from mtp.apps.common.views_utils import (
    AjaxResponseMixin,
    SuccessMessageMixin,
    SyncableDeleteView,
)
from mtp.apps.documents.models import Document
from mtp.apps.evaluations.models import Evaluation
from mtp.apps.qrcodes.views import (
    ObjectQRCodeDownloadDetailView,
    ObjectQRCodeDownloadListView,
    QRCodeDownloadMixin,
)
from mtp.apps.roleplays.models import Roleplay
from mtp.apps.trainings.qrcodes import ActivityQRCode
from mtp.apps.workshops.models import Workshop

from ..filters import TrainingActivityFilterSet
from ..forms import (
    TrainingActivityLibraryDuplicateForm,
    TrainingActivityTrainingsDuplicateForm,
)
from ..models import (
    TrainingActivity,
    TrainingActivityLibraryStatus
)


from . import TrainingBaseView


class TrainingActivityBase(TrainingBaseView, SuccessMessageMixin):
    """Base view to manage the training activity

    Define the queryset to use, depending on the user and set the
    ``current_sub_section``in the context.

    """

    pk_url_kwarg = 'activity_uuid'
    model = TrainingActivity

    @cached_property
    def training(self):
        """Try to get the current selected training from the url.

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

        Returns
        -------
        ``Training`` instance

        """

        try:
            return get_object_or_404(
                super().get_training_queryset_for_current_user(),
                pk=self.kwargs['training_uuid']
            )
        except KeyError:
            return None

    def get_queryset(self):
        """Filter the activities owned by the current user"""

        return TrainingActivity.objects.non_deleted().annotate_is_editable(
            self.request.user
        ).annotate_is_visible(self.request.user)

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

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

        """

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

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

        if self.training:
            return self.training.get_detail_url()
        else:
            return reverse('trainings:activities-private-library')


class TrainingActivityDetailBase(TrainingActivityBase):
    """Mixin to add the current sub section to the context"""

    @cached_property
    def training(self):
        """Try to get the current selected training from the current object

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

        Returns
        -------
        ``Training`` instance

        """

        try:
            return super().training or self.object.training
        except AttributeError:
            return None

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

        For the parameters, see ``django.views.ContextMixin``

        """

        try:
            if self.training:
                current_sub_section = 'training'
            elif not self.object.library_status.is_shared:
                current_sub_section = 'private_library'
            else:
                current_sub_section = 'public_library'
        except AttributeError:
            current_sub_section = ''

        return super().get_context_data(
            current_sub_section=current_sub_section,
            **kwargs
        )

    def get_queryset(self):
        """Filter the activities visible by the current user"""

        return super().get_queryset().visible()


class TrainingActivityEditBase(TrainingActivityDetailBase, generic.UpdateView):
    """Mixin to update the QuerySet for the editable activities"""

    def get_queryset(self):
        """Filter the activity that can be edited.

        The activity can be edited only if they belong to a ``DRAFT`` training.

        Returns
        -------
        instance of mtp.apps.trainings.managers.TrainingActivityQuerySet
            The list of activities which can be edited

        """

        return super().get_queryset().editable()


class TrainingActivityDeleteView(TrainingActivityBase, SyncableDeleteView):
    """View to soft delete a ``TrainingActivity."""

    fields = []
    template_name = 'cms/trainings/trainingactivity_delete.html'
    success_message = _('Your activity was successfully deleted.')

    def get_success_url(self):
        """Get the url to redirect the user"""

        if self.object.training:
            return self.object.training.get_detail_url()
        elif self.object.library_status.is_shared:
            return reverse('trainings:activities-public-library')
        else:
            return reverse('trainings:activities-private-library')

    def get_queryset(self):
        """Filter the activity that can be deleted

        Returns
        -------
        instance of mtp.apps.trainings.managers.TrainingActivityQuerySet
            The list of activities which can be deleted

        """

        return super().get_queryset().editable()


class TrainingActivityTrainingDuplicateView(TrainingActivityEditBase):
    """View to duplicate a given ``Activity`` inside a given training, with confirmation."""

    fields = []
    template_name = 'cms/trainings/trainingactivity_training_duplicate.html'
    success_message = _('Your activity was successfully duplicated.')

    def post(self, request, *args, **kwargs):
        """Duplicate the activity, and redirect the user.

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

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the url of the list of activities.

        """

        activity = self.get_object()

        duplicate_name = Truncator(
            '%s - %s' % (activity.name, _('Copy'))
        ).chars(255, '…')

        activity.duplicate_in_training(
            self.training,
            name=duplicate_name
        )

        self.send_message()

        return HttpResponseRedirect(self.get_success_url())


class TrainingActivityDuplicateBase(AjaxResponseMixin, TrainingActivityEditBase):
    """Base view for the duplicate view related to the library"""

    form_class = TrainingActivityLibraryDuplicateForm
    success_message = _('Your activity was successfully duplicated.')

    @cached_property
    def next_url(self):
        """Try to get the next url from the url param"""

        try:
            return self.request.GET['next_url']
        except KeyError:
            return None

    def get_context_data(self, **kwargs):
        """Add the next_url to the context if it exists

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

        """

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

    def get_success_url(self):
        """Try to get the success url from the url param"""

        if self.next_url:
            return self.next_url
        else:
            return self.success_url or super().get_success_url()


class TrainingActivityTrainingsDuplicateView(TrainingActivityDuplicateBase):
    """View to duplicate a given ``Activity`` inside the given trainings, with confirmation."""

    form_class = TrainingActivityTrainingsDuplicateForm
    template_name = 'cms/trainings/trainingactivity_trainings_duplicate.html'

    def get_queryset(self):
        """Filter the activities owned by the current user"""

        return TrainingActivity.objects.non_deleted().duplicable_in_private_library(
            self.request.user
        )

    def get_form_kwargs(self):
        """Add the user parameter in the form kwargs"""

        kwargs = super().get_form_kwargs()
        kwargs['user'] = self.request.user

        return kwargs

    def form_valid(self, form):
        """Duplicate the activity, and redirect the user.

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

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the url of the list of activities.

        """

        activity = self.get_object()

        for training in form.cleaned_data['trainings']:
            activity.duplicate_in_training(training)

        self.send_message()

        return JsonResponse(
            {
                'valid': True,
                'success_url': self.get_success_url()
            }
        )


class TrainingActivityPrivateLibraryDuplicateView(TrainingActivityDuplicateBase):
    """View to duplicate a given ``Activity`` inside the current user library"""

    template_name = 'cms/trainings/trainingactivity_private_library_duplicate.html'
    success_url = reverse_lazy('trainings:activities-private-library')

    def get_queryset(self):
        """Filter the activities owned by the current user"""

        return TrainingActivity.objects.non_deleted().duplicable_in_private_library(
            self.request.user
        )

    def get_form_kwargs(self):
        """Init the form with the current user and the ``is_shared`` flag"""

        kwargs = super().get_form_kwargs()
        kwargs.update({
            'user': self.request.user,
            'is_shared': False
        })

        return kwargs

    def form_valid(self, form):
        """Duplicate the activity, and redirect the user.

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

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the url of the list of activities.

        """

        activity = self.get_object()

        activity.duplicate_in_library(
            owner=self.request.user,
            is_shared=False,
            name=form.cleaned_data['duplicate_name']
        )

        self.send_message()

        return JsonResponse(
            {
                'valid': True,
                'success_url': self.get_success_url()
            }
        )


class TrainingActivityPublicLibraryDuplicateView(TrainingActivityDuplicateBase):
    """View to duplicate a given ``Activity`` inside the current user library"""

    template_name = 'cms/trainings/trainingactivity_public_library_duplicate.html'
    success_url = reverse_lazy('trainings:activities-public-library')

    def get_form_kwargs(self):
        """Init the form with the current user and the ``is_shared`` flag"""

        kwargs = super().get_form_kwargs()
        kwargs.update({
            'user': self.request.user,
            'is_shared': True
        })

        return kwargs

    def get_queryset(self):
        """Filter the activities owned by the current user"""

        return TrainingActivity.objects.non_deleted().duplicable_in_public_library(
            self.request.user
        )

    def form_valid(self, form):
        """Duplicate the activity, and redirect the user.

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

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the url of the list of activities.

        """

        activity = self.get_object()

        activity.duplicate_in_library(
            owner=self.request.user,
            is_shared=True,
            name=form.cleaned_data['duplicate_name']
        )

        self.send_message()

        return JsonResponse(
            {
                'valid': True,
                'success_url': self.get_success_url()
            }
        )


class TrainingActivityCreateBase(TrainingActivityBase, generic.CreateView):
    """Base create view for the subclasses of the ``TrainingActivity``"""

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

        Parameters
        ----------
        For the parameters, see ``django.forms.generic.CreateView``

        """

        if self.training:
            activity_create_url = self.model.get_training_create_url(training=self.training)
        else:
            activity_create_url = self.model.get_create_url()

        return super().get_context_data(
            create_url=activity_create_url,
            **kwargs
        )

    def form_valid(self, form):
        """Associate the activity with the ``Training`` or with a ``TrainingActivityLibraryStatus``

        If we got a training from the url, we associate the activity with it.
        If not, we associate the activity with a brand new ``TrainingActivityLibraryStatus``.

        Parameters
        ----------
        For the parameters, see ``django.forms.generic.CreateView``

        """

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

        if self.training:
            self.object.training = self.training
        else:
            library_status = TrainingActivityLibraryStatus.objects.create(
                owner=self.request.user,
                is_shared=False
            )
            self.object.library_status = library_status

        self.object.save()

        return super().form_valid(form)


class ActivityListBase(TrainingActivityBase, FilterView):
    """Base view for the views displaying a list of ``TrainingActivity``"""

    paginate_by = 15
    filterset_class = TrainingActivityFilterSet


class PublicLibraryView(ActivityListBase):
    """View to list the ``TrainingActivity`` in the public library"""

    template_name = 'cms/trainings/trainingactivity_library.html'

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

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

        """

        return super().get_context_data(
            current_sub_section='public_library',
            activity_next_url=reverse('trainings:activities-public-library'),
            **kwargs
        )

    def get_queryset(self):
        """Filter the ``TrainingActivity``"""

        return TrainingActivity.objects.public_library(
        ).non_deleted().select_related(
            'library_status',
            'library_status__owner'
        ).annotate_is_owned(self.request.user)


class PrivateLibraryView(ActivityListBase):
    """View to list the ``TrainingActivity`` in the private library"""

    template_name = 'cms/trainings/trainingactivity_library.html'

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

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

        """

        return super().get_context_data(
            current_sub_section='private_library',
            available_activity_types=TrainingActivity.available_activity_types(),
            activity_next_url=reverse('trainings:activities-private-library'),
            **kwargs
        )

    def get_queryset(self):
        """Filter the ``TrainingActivity``"""

        return TrainingActivity.objects.user_library(
            self.request.user
        ).non_deleted().annotate_is_owned(self.request.user)


# pylint: disable=abstract-method
class ActivityQRCodeDownloadMixin(QRCodeDownloadMixin, TrainingActivityBase):
    """Mixin containing specific methods and attributes to download activity launch QRCode."""

    qrcode_class = ActivityQRCode


class ActivityQRCodeDownloadDetailView(ActivityQRCodeDownloadMixin, ObjectQRCodeDownloadDetailView):
    """View to download QrCode able to launch an activity."""


class ActivityQRCodeDownloadListView(ActivityQRCodeDownloadMixin, ObjectQRCodeDownloadListView):
    """View to download training QrCodes able to launch an activity."""

    def get_queryset(self):
        """Get training activities.

        Only Document, Evaluation, Workshop, Roleplay.
        We can't launch Template with a QRCode like BrainstormerTemplate.

        """
        return super().get_queryset().instance_of(
            Document, Evaluation, Workshop, Roleplay
        )

    def get_filename(self):
        """Get the filename used to name the generated zip file."""
        return str(self.training)
