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

import io
from operator import itemgetter
import zipfile

from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.utils.formats import get_format
from django.utils.dateformat import format as date_format
from django.utils.functional import cached_property
from django.utils.timezone import localtime
from django.utils.translation import ugettext_lazy as _
from django.views import generic

from django_filters.views import FilterView

from mtp.apps.accounts.models import Group
from mtp.apps.common.views_utils import (
    LoginRequiredMixin,
    NextSuccessUrlMixin,
    RestrictToSuperuserOrTrainerMixin,
    SuccessMessageMixin,
    SyncableDeleteView,
)

from ..filters import TrainingSessionFilter
from ..forms import ExportableResultsActivityForm, TrainingSessionForm
from ..models import (
    ResultsToExcelActivityMixin,
    Training,
    TrainingActivity,
    TrainingSession
)


class TrainingSessionBaseView(LoginRequiredMixin):
    """Base view for all views related to the sessions.

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

    """

    model = TrainingSession
    pk_url_kwarg = 'session_uuid'

    def get_context_data(self, **kwargs):
        """Add the ``current_main_section`` and add another queryset for trainings.

        The ``Training`` QuerySet is there to allow filtering the session.

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


        """

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

    def get_training_queryset_for_current_user(self):
        """Get all ``Training`` instances to filter the ``TrainingSession`` list.

        The Queryset is filtered accordingly to the user's type or user's group:

        - superusers and trainers:
            Can see all the non-deleted, ``PUBLISHED`` trainings they own.
        - trainees:
            Can see only the training in which there is a session they were added to.

        Returns
        -------
        Queryset of ``mtp.apps.trainings.models.Training``
            A lazy queryset with the wanted trainings.

        Raises
        ------
        ``django.http.Http404``
            if the user is neither a superuser, a trainer, or a trainee

        """

        training_qs = Training.objects.non_deleted()

        if self.request.user.is_superuser or self.request.user.is_trainer:
            return training_qs.available_for_user(self.request.user).exclude_is_draft()

        if self.request.user.is_trainee:
            return training_qs.for_trainee(self.request.user).exclude_is_draft()

        raise Http404

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

        The Queryset is filtered accordingly to the user's type or user's group,
        see ``visible_by_user`` method on ``mtp.apps.trainings.models.TrainingSession``.

        Returns
        -------
        Queryset of ``mtp.apps.trainings.models.TrainingSession``
            A lazy queryset with the wanted training sessions.

        """

        return TrainingSession.objects.non_deleted().visible_by_user(
            self.request.user
        ).select_related('trainer', 'training').order_by('-start_date')


class TrainingSessionList(TrainingSessionBaseView, FilterView):
    """View to list the ``TrainingSession`` instances the current user is allowed to see."""

    template_name = 'cms/trainings/trainingsession_list.html'
    filterset_class = TrainingSessionFilter
    paginate_by = 10

    @property
    def selected_training(self):
        """Get the selected training in filter.

        Returns
        -------
        Training
            The selected training or None.

        """

        # We fetch selected trainings pks in filter
        try:
            selected_trainings_pks = self.filterset.data.getlist('training')
        except AttributeError:  # No training is selected
            return None

        # If only one training is selected, we return it else None
        if len(selected_trainings_pks) == 1:
            return Training.objects.get(pk=selected_trainings_pks[0])

        return None

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

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

        return super().get_context_data(
            DATE_INPUT_FORMAT=get_format('DATE_INPUT_FORMATS')[2].replace('%', '').replace(
                'M',
                'i'
            ),
            selected_training=self.selected_training,
            **kwargs
        )

    def get_filterset_kwargs(self, filterset_class):
        """Set the additional kwargs for the filter form

        Parameters
        ----------
        filterset_class: object
            The filter form class to use

        Returns
        -------
        dict: A dict with the updated kwargs

        """

        kwargs = super().get_filterset_kwargs(filterset_class)

        kwargs.update({
            'trainings_queryset': self.get_training_queryset_for_current_user(),
            'user': self.request.user
        })

        return kwargs


class TrainingSessionEditBase(SuccessMessageMixin, RestrictToSuperuserOrTrainerMixin,
                              TrainingSessionBaseView):
    """Base view to manage ``TrainingSession`` instances."""

    form_class = TrainingSessionForm

    def get_form_kwargs(self):
        """Return the form kwargs accordingly to the context.

        If the training session is not new, remove the ``training`` field on the form.
        If the user is not a superuser, remove the ``trainer`` field on the form.

        See Also
        --------
        ``mtp.apps.trainings.forms.TrainingSessionForm.__init__`` method.

        """

        kwargs = super().get_form_kwargs()
        kwargs['remove_training_field'] = bool(getattr(self, 'object', None))
        kwargs['remove_trainer_field'] = not self.request.user.is_superuser
        kwargs['current_user'] = self.request.user

        return kwargs

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

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

        return super().get_context_data(
            DATETIME_INPUT_FORMAT=get_format('DATETIME_INPUT_FORMATS')[2].replace(
                '%', '').replace('M', 'i'),
            **kwargs
        )

    def get_success_url(self):
        """Return the url be redirect to after the save.

        If we got a current training instance, we redirect filtered list of sessions
        of this training, if not, simply redirect to the unfiltered list.

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

        """

        return TrainingSession.get_list_url()

    def form_valid(self, form):
        """Set the trainer if applicable, save the training session 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 training session.

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

        """

        if not self.request.user.is_superuser:
            # it's a mixin, `object` will be defined in get/post of standard imported view class
            self.object = form.save(commit=False)  # pylint: disable=attribute-defined-outside-init
            self.object.trainer = self.request.user
            self.object.save()
            form.save_m2m()  # Necessary to save the session users
        else:
            form.save()

        self.send_message(form.cleaned_data)

        return HttpResponseRedirect(self.get_success_url())


class TrainingSessionCreate(TrainingSessionEditBase, generic.CreateView):
    """The view to create a ``TrainingSession`` instance."""

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


class TrainingSessionUpdate(NextSuccessUrlMixin, TrainingSessionEditBase, generic.UpdateView):
    """The view to update a ``TrainingSession`` instance."""

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


class TrainingSessionDelete(TrainingSessionEditBase, SyncableDeleteView):
    """The view to soft-delete a ``TrainingSession`` instance."""

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


class TrainingSessionWhosWho(TrainingSessionBaseView, generic.DetailView):
    """The view to render a ``TrainingSession`` users list."""

    template_name = "cms/trainings/whoswho.html"

    def users(self):
        """Get the users from the ``TrainingSession`` who belongs to the "whoswho" group."""

        group = Group.objects.get(name='whoswho')
        return self.object.users.filter(mgroups=group).order_by('last_name', 'first_name')

    def get_context_data(self, **kwargs):
        """Add the list of users who belongs to the "whoshwo" group to the context.

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

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


class TrainingSessionClose(NextSuccessUrlMixin, TrainingSessionEditBase, generic.UpdateView):
    """The view close a ``mtp.apps.training.models.TrainingSession`` instance."""

    template_name_suffix = '_close'
    success_message = _("Your training session was successfully closed.")

    def post(self, request, *args, **kwargs):
        """Close the training session, and redirect to the list of training session.

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

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the training session list.

        """

        training_session = self.get_object()
        training_session.close(async=True)

        self.send_message()

        return HttpResponseRedirect(self.get_success_url())


class SessionTokenView(TrainingSessionBaseView, generic.DetailView):
    """View to display the selected session token."""

    template_name = 'trainings/trainingsession_token.html'


class TrainingSessionResultsExportView(TrainingSessionBaseView, generic.UpdateView):
    """View to download a zip file containing the results of the selected session"""

    form_class = ExportableResultsActivityForm
    template_name = 'trainings/trainingsession_results_export.html'

    @cached_property
    def exportable_activities(self):
        """Get the list of the activities for which we can export the results

        Returns
        -------
        list(dict)
            A list of dict containing the following keys:
            - The id of the activity
            - The name of the activity
            - The type of the activity

        """

        activity_list = [
            {
                'id': activity.id,
                'name': activity.name,
                'type': activity.verbose_activity_name
            }
            for activity in TrainingActivity.objects.for_session(self.object).non_deleted()
            if isinstance(activity, ResultsToExcelActivityMixin) and activity.is_exportable(
                self.object
            )
        ]

        return sorted(activity_list, key=itemgetter('type'))

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

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

        """

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

    def get_form_kwargs(self):
        """Populate the choices of the exportable activities"""

        kwargs = super().get_form_kwargs()

        kwargs['exportable_activities'] = self.exportable_activities

        return kwargs

    def form_valid(self, form):
        """Build the zip file containing the results of the chosen activities

        Parameters
        ----------
        form : instance of ``django.forms.models.ModelForm``
            The validated form to save.

        """

        session = self.object

        activities_to_export_ids = form.cleaned_data['activities_to_export']
        activities_to_export = TrainingActivity.objects.filter(
            id__in=activities_to_export_ids
        ).order_by('sort_order')

        zip_buffer = io.BytesIO()

        with zipfile.ZipFile(zip_buffer, 'w', compression=zipfile.ZIP_DEFLATED) as zip_file:
            for index, activity_to_export in enumerate(activities_to_export):
                excel_data = activity_to_export.results_to_excel(session)
                zip_file.writestr(
                    '%d - %s.xlsx' % (
                        index,
                        activity_to_export.name
                    ),
                    excel_data.getvalue()
                )

        trainer = session.trainer

        file_name = 'session_%s-%s_%s_export.zip' % (
            trainer.first_name,
            trainer.last_name,
            date_format(localtime(session.start_date), 'Y-m-d')
        )

        response = HttpResponse(content_type='application/x-zip-compressed')
        response['Content-Disposition'] = 'attachment; filename=%s' % file_name

        response.write(zip_buffer.getvalue())

        return response
