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

import csv

from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property
from django.utils.translation import ugettext as _
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView
from django.views.generic.list import ListView

from mtp.apps.common.utils import EXCEL_BOM
from mtp.apps.common.views_utils import (
    AjaxResponseMixin,
    ConditionalMixin,
    RestrictToSuperuserOrTrainerMixin,
    SuccessMessageMixin,
    SyncableDeleteView,
)
from mtp.apps.import_export.exceptions import ImportDataError
from mtp.apps.import_export.views import (
    XLSExportDetailView, ModelXLSImportView, ActivityXLSImportView
)
from mtp.apps.trainings.models import Training, TrainingSession

from .constants import DEFAULT_GROUP_ATTRIBUTES
from .forms import (
    BrainstormerForm,
    BrainstormerPropositionForm,
    BrainstormerPropositionGroupForm,
)
from .models import (
    Brainstormer,
    BrainstormerProposition,
    BrainstormerPropositionGroup,
    BrainstormerTemplate
)
from .resources import BrainstormerGroupResource, BrainstormerPropositionResource, \
    BrainstormerResource, BrainstormerTemplateResource


class BrainstormerSessionListView(RestrictToSuperuserOrTrainerMixin, ListView):
    """ Return Brainstormer List for a session. """

    def get_queryset(self):
        session = get_object_or_404(TrainingSession, id=self.kwargs['session_uuid'])
        return Brainstormer.objects.filter(session=session, is_deleted=False)

    def get_context_data(self, **kwargs):
        context = super(
            BrainstormerSessionListView, self).get_context_data(**kwargs)
        session = get_object_or_404(TrainingSession, id=self.kwargs['session_uuid'])
        context.update({
            'session': session
        })
        return context


class BrainstormerDetailView(RestrictToSuperuserOrTrainerMixin, DetailView):
    """ Detail of Brainstormer propositions. """

    model = Brainstormer
    pk_url_kwarg = 'brainstormer_uuid'


class BrainstormerEditBaseMixin(RestrictToSuperuserOrTrainerMixin, SuccessMessageMixin):
    """ Edit base for Brainstormer. """

    model = Brainstormer
    pk_url_kwarg = 'brainstormer_uuid'

    def get_success_url(self):
        return reverse('brainstormers:list_by_session',
                       kwargs={'session_uuid': self.object.session.id})


class BrainstormerCreateView(ConditionalMixin, BrainstormerEditBaseMixin, CreateView):
    """ Create Brainstormer. """

    form_class = BrainstormerForm
    success_message = _('Your brainstormer was successfully created.')

    @cached_property
    def session(self):
        """Get the session associated with this request"""

        return get_object_or_404(TrainingSession, id=self.kwargs['session_uuid'])

    def get_success_url(self):
        """Get the url of the redirection when create is successful"""

        return reverse('brainstormers:list_by_session',
                       kwargs={'session_uuid': self.session.id})

    def get_initial(self):
        """Get initial data for the form"""

        initial = {'session': self.session.id}

        return initial

    def is_condition_met(self):
        """Only allow create when session is not closed"""

        return not self.session.is_closed

    def condition_not_met(self):
        """Action to take if the session is closed.

        Raises
        ------
        PermissionDenied
            When the session is closed.

        """

        raise PermissionDenied


class BrainstormerUpdateView(BrainstormerEditBaseMixin, UpdateView):
    """ Update Brainstormer. """

    form_class = BrainstormerForm
    success_message = _('Your brainstormer was successfully updated.')


class BrainstormerDeleteView(BrainstormerEditBaseMixin, SyncableDeleteView):
    """ Delete Brainstormer. """

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


class BrainstormerTemplateEditBaseMixin(
        AjaxResponseMixin, RestrictToSuperuserOrTrainerMixin, SuccessMessageMixin):
    """ Edit base for Brainstormer Template. """

    model = BrainstormerTemplate
    pk_url_kwarg = 'template_uuid'
    fields = [
        'name',
        'description',
        'activity'
    ]

    def get_form(self, form_class=None):
        """Override the max_length and the help_text attributes on the field name

        We can't enforce this at the model level since the "name" field
        is inherited from ``TrainingActivity``.

        For the parameters, see ``django.views.generics.edit.ModelFormMixin``
        """

        form = super().get_form(form_class)
        form.fields['name'].widget.attrs['maxlength'] = 100
        form.fields['name'].help_text = _('Maximum 100 characters')

        return form


class BrainstormerTemplateCreateView(BrainstormerTemplateEditBaseMixin, CreateView):
    """ Create Brainstormer Template. """

    success_message = _('Your brainstormer template 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,
            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``, save the brainstormer template 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 the training activity.

        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 BrainstormerTemplateDetailView(BrainstormerTemplateEditBaseMixin, DetailView):
    """ Detail Template Brainstormer. """


class BrainstormerTemplateUpdateView(BrainstormerTemplateEditBaseMixin, UpdateView):
    """ Update Template Brainstormer. """

    success_message = _('Your brainstormer template was successfully updated.')


class BrainstormerTemplateDeleteView(BrainstormerTemplateEditBaseMixin, SyncableDeleteView):
    """ Delete Template Brainstormer. """

    success_message = _('Your brainstormer template was successfully deleted.')


class BrainstormerPropositionEditBaseMixin(RestrictToSuperuserOrTrainerMixin, SuccessMessageMixin):
    """ Edit base for Brainstormer Proposition. """

    model = BrainstormerProposition
    pk_url_kwarg = 'proposition_uuid'

    def get_success_url(self):
        return reverse(
            'brainstormers:detail',
            kwargs={'brainstormer_uuid': self.kwargs['brainstormer_uuid']}
        )


class BrainstormerPropositionCreateView(BrainstormerPropositionEditBaseMixin, CreateView):
    """Create a BrainstormerProposition."""

    form_class = BrainstormerPropositionForm
    success_message = _('Your brainstormer proposition was successfully created.')

    def get_initial(self):
        """Return the initial form values."""

        group = get_object_or_404(BrainstormerPropositionGroup, id=self.kwargs['group_uuid'])
        return {'group': group}

    def get_form(self, form_class=None):
        """Edit the form according the current brainstormer.

        For the parameters, see ``django.view.generic.edit.FormMixin``"""

        form = super().get_form(form_class)
        brainstormer = get_object_or_404(Brainstormer, id=self.kwargs['brainstormer_uuid'])

        # Change field queryset for adapt them to current brainstormer
        form.fields['group'].queryset = brainstormer.groups.non_deleted()
        form.fields['user'].queryset = brainstormer.session.users.non_deleted()

        return form


class BrainstormerPropositionUpdateView(BrainstormerPropositionEditBaseMixin, UpdateView):
    """ Update BrainstormerProposition. """

    form_class = BrainstormerPropositionForm
    success_message = _('Your brainstormer proposition was successfully updated.')


class BrainstormerPropositionDeleteView(BrainstormerPropositionEditBaseMixin, SyncableDeleteView):
    """ Delete BrainstormerProposition. """

    success_message = _('Your brainstormer proposition was successfully deleted.')


class BrainstormerPropositionGroupEditBaseMixin(RestrictToSuperuserOrTrainerMixin,
                                                SuccessMessageMixin):
    """ Edit base for Brainstormer Proposition Group. """

    model = BrainstormerPropositionGroup
    pk_url_kwarg = 'group_uuid'

    def get_success_url(self):
        return reverse(
            'brainstormers:detail',
            kwargs={'brainstormer_uuid': self.kwargs['brainstormer_uuid']}
        )


class BrainstormerPropositionGroupCreateView(BrainstormerPropositionGroupEditBaseMixin, CreateView):
    """ Create BrainstormerPropositionGroup. """

    form_class = BrainstormerPropositionGroupForm
    success_message = _('Your brainstormer group was successfully created.')

    def get_initial(self):
        brainstormer = get_object_or_404(Brainstormer, id=self.kwargs['brainstormer_uuid'])
        initial = {'brainstormer': brainstormer}
        return initial


class BrainstormerPropositionGroupUpdateView(BrainstormerPropositionGroupEditBaseMixin, UpdateView):
    """ Update BrainstormerPropositionGroup. """

    form_class = BrainstormerPropositionGroupForm
    success_message = _('Your brainstormer group was successfully updated.')


class BrainstormerPropositionGroupDeleteView(BrainstormerPropositionGroupEditBaseMixin,
                                             SyncableDeleteView):
    """ Delete BrainstormerPropositionGroup. """

    success_message = _('Your brainstormer group was successfully deleted.')

    def delete(self, request, *args, **kwargs):
        group = self.get_object()
        if 'deleteAll' not in request.POST:
            for proposition in group.propositions.all():
                group_undefined, _ = BrainstormerPropositionGroup.objects.get_or_create(
                    brainstormer=group.brainstormer,
                    **DEFAULT_GROUP_ATTRIBUTES
                )
                proposition.group = group_undefined
                proposition.save()
        return super().delete(request, *args, **kwargs)


class CSVBrainstormerExportView(RestrictToSuperuserOrTrainerMixin, DetailView):
    """ Export CSV Brainstormer propositions. """

    model = Brainstormer
    pk_url_kwarg = 'brainstormer_uuid'

    def get(self, *args, **kwargs):
        super().get(*args, **kwargs)

        # Create the HttpResponse object with the appropriate CSV header.
        response = HttpResponse(content_type='text/csv')
        response['Content-Disposition'] = 'attachment; filename="brainstormer.csv"'

        writer = csv.writer(response, delimiter=';')

        # Header
        writer.writerow([EXCEL_BOM])  # BOM for excel
        writer.writerow([
            'Brainstormer Propositions',
            self.object.name])
        writer.writerow([])

        # Generation of CSV
        grouped_propositions = self.object.get_grouped_propositions()
        for group, propositions in grouped_propositions.items():
            writer.writerow([
                'Group',
                group.name,
                '%s propositions' % (len(propositions))])
            for ind, proposition in enumerate(propositions):
                writer.writerow([str(ind + 1), proposition.text, proposition.user])

        return response


class XLSBrainstormerExportView(XLSExportDetailView):
    """Export XLS Brainstormer groups and propositions."""

    resource = BrainstormerResource
    pk_url_kwarg = 'brainstormer_uuid'


class XLSBrainstormerImportView(ConditionalMixin, ModelXLSImportView):
    """Import XLS Brainstormer"""

    @cached_property
    def session(self):
        """Get the session associated with this request"""

        return get_object_or_404(TrainingSession, pk=self.kwargs['session_uuid'])

    def is_condition_met(self):
        """Only allow import when session is not closed"""

        return not self.session.is_closed

    def condition_not_met(self):
        """Action to take if the session is closed.

        Raises
        ------
        PermissionDenied
            When the session is closed.

        """

        raise PermissionDenied

    def get_success_url(self):
        if not self.imported_object:
            return self.success_url
        return reverse(
            'brainstormers:detail',
            kwargs={'brainstormer_uuid': self.imported_object.pk}
        )

    def import_data(self, sheet_name):
        data_object = self.reader.read_values(sheet_name, 2)

        obj, __ = self.create_object(
            data_object, resource=BrainstormerResource, force_creation=True,
            training_session=self.session)

        for data in self.reader.read_table(sheet_name):
            if self.is_compatible(data, BrainstormerGroupResource):
                group, __ = self.create_object(
                    data, resource=BrainstormerGroupResource,
                    brainstormer=obj
                )
                obj.groups.add(group)
            elif self.is_compatible(data, BrainstormerPropositionResource):
                try:
                    self.create_object(
                        data, resource=BrainstormerPropositionResource,
                        group=group
                    )
                except UnboundLocalError:
                    raise ImportDataError(
                        _("Problem in your file: no group is provided before proposition"),
                        line=data.line
                    )
            else:
                raise ImportDataError(_("Data is not compatible."), line=data.line)
        return obj


class XLSBrainstormerTemplateExportView(XLSExportDetailView):
    """Export XLS Brainstormer groups and propositions."""

    resource = BrainstormerTemplateResource
    pk_url_kwarg = 'brainstormer_template_uuid'


class XLSBrainstormerTemplateImportView(ActivityXLSImportView):

    def import_data(self, sheet_name):

        resource = BrainstormerTemplateResource
        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 %s." % fields,
                line=data_object.line
            )

        return obj
