"""This module holds the views for the ``assessments`` app."""

from collections import defaultdict

from django.contrib import messages
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseRedirect, Http404, JsonResponse
from django.shortcuts import get_object_or_404, redirect
from django.utils import formats
from django.utils.functional import cached_property
from django.utils.formats import get_format
from django.utils.translation import ugettext_lazy as _
from django.views import generic

from django_filters.views import FilterView

from mtp.apps.accounts.constants import GROUPTYPE_JOB_PROFILE
from mtp.apps.accounts.models import Group, GroupType
from mtp.apps.assessments.exporters import AssessmentScoresExporter
from mtp.apps.common.views_utils import (
    ExportFileMixin,
    OrderableListMixin,
    RestrictToSuperuserOrTrainerMixin,
    SuccessMessageMixin,
    SyncableDeleteView,
)
from mtp.apps.import_export.constants import XLXS_CONTENT_TYPE
from mtp.apps.import_export.exceptions import ImportDataError
from mtp.apps.import_export.views import ModelXLSImportView, XLSExportDetailView
from mtp.apps.pdf_reporting.views import PDFReportMixin
from mtp.apps.trainings.views import TrainingSessionList
from mtp.apps.translation.export_views.views import TranslationExportDetailView
from mtp.apps.translation.import_views.views import ContentTranslationImportView

from .filters import CertificationResultsFilter
from .forms import (
    CertificationThresholdsForm,
    LessonDimensionWeightForm,
    LessonRecommendationThresholdForm,
    TreeDuplicateForm,
)
from .models import (
    CertificationProgram,
    CertificationProgramUserStatus,
    CertificationStatus,
    Competency,
    CompetencyTree,
    CompetencyUserScore,
    Curriculum,
    Dimension,
    Lesson,
    LessonDimensionWeight,
    LessonRecommendationThreshold,
    Topic,
)
from .resources import CompetencyResource, CompetencyTreeResource, DimensionResource


class AssessableCreateMixin:
    """Mixin to improve the behavior of creation views for assessable objects."""

    @property
    def is_assessing_container(self):
        """Checks if the containing object (training or assessable activity)
        is assessing.

        Returns
        -------
        boolean

        Notes
        -----
        Must be implemented in subclasses.

        """
        return NotImplementedError

    def get_initial(self):
        initial = super().get_initial()
        initial.update({
            'is_assessing': self.is_assessing_container,
        })
        return initial


class SessionScoresView(ExportFileMixin, RestrictToSuperuserOrTrainerMixin, TrainingSessionList):
    """View to download the assessments scores for a given ``TrainingSession`` instance.

    Notes
    -----
    This class inherit of ``TrainingSessionList`` to apply the training session filter.

    """

    @property
    def session_list(self):
        """Get the session to use during scores export.

        Notes
        -----
        We export scores on only assessing and closed session with trainees.

        Returns
        -------
        list
            The session list to export.

        """

        return self.filterset.qs

    def get_filename(self):
        """Get the filename of the assessment score file.

        Returns
        -------
        str

        """

        return 'scores-%s' % (self.selected_training.original_version or self.selected_training)

    def get(self, request, *args, **kwargs):
        """Export the session's score into a xlsx file.

        Returns
        -------
        ``HttpResponse``
            An http response with the scores's file attached.

        """

        super().get(request, *args, **kwargs)

        if not self.selected_training:
            messages.error(self.request, _('No training is selected in filterset.'))
            return redirect('trainings:session-list')

        if self.session_list:
            scores_data = AssessmentScoresExporter(self.session_list).get_file_content()
        else:
            messages.error(
                self.request,
                _('No session of "%(training)s" is compatible to export scores.' % {
                    'training': self.selected_training
                })
            )
            return redirect('trainings:session-list')

        xlsx_content_type = XLXS_CONTENT_TYPE
        response = HttpResponse(scores_data, content_type=xlsx_content_type)
        response['Content-Disposition'] = 'attachment; filename=%s.xlsx' % (
            self.get_slugified_filename(),
        )

        return response


class AssessmentBaseView(RestrictToSuperuserOrTrainerMixin):
    """Base view for all the views related to the management of a given ``job_profile``.

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

    Also, define the following contextual instances in the context:
    - ``current_job_profile``: ``mtp.apps.accounts.models.Group`` instance
        The job_profile currently selected
    - ``job_profiles``: ``mtp.apps.accounts.managers.GroupQuerySet`` instance
        A QuerySet of the available job_profiles for the current user
    - ``current_managed_group``: ``mtp.apps.accounts.models.Group`` instance
        The business_unit currently selected
    - ``managed_groups``: ``mtp.apps.accounts.managers.GroupQuerySet`` instance
        A QuerySet of the available business_units for the current user
    - ``competency_tree``: ``mtp.apps.assessments.competencies.models.CompetencyTree``
        The current competency tree

    """

    def get_job_profile_queryset(self):
        """Get the job_profiles of the current user

        Returns
        -------
        ``mtp.apps.accounts.managers.GroupQuerySet`` instance
            A QuerySet with the job profiles of the current user

        """

        return self.request.user.mgroups.non_deleted().job_profiles()

    def get_competency_tree_queryset(self):
        """Get the ``CompetencyTree`` instances the current user is allowed to access

        Returns
        -------
        ``mtp.apps.assessments.competencies.managers.CompetencyTreeQuerySet`` instance
            A QuerySet with the Competency Tree for the current user

        """

        return CompetencyTree.objects.non_deleted().annotate_can_edit(
            user=self.request.user).annotate_can_translate(user=self.request.user)

    def get_managed_groups_queryset(self):
        """Get the groups the current user is managing.

        Notes
        -----
        We only want the managed groups for which there is an existing ``CompetencyTree`` instance.

        """

        return self.request.user.managed_groups.non_deleted().business_units().filter(
            trees__isnull=False
        )

    def get_context_data(self, **kwargs):
        """Add the ``current_main_section`` and also the ``current_job_profile``,
        ``competency_tree`` and ``job_profiles``.

        """

        return super().get_context_data(
            current_job_profile=self.job_profile,
            job_profiles=self.get_job_profile_queryset(),
            managed_groups=self.get_managed_groups_queryset(),
            current_managed_group=self.managed_group,
            current_main_section='assessments',
            competency_tree=self.competency_tree,
            **kwargs
        )

    @cached_property
    def job_profile(self):
        """Return the current job_profile based on the ``jobprofile_uuid``
        argument in the url.
        Or, if the ``jobprofile_uuid`` kwarg is empty, get the first one.

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

        """

        try:
            return get_object_or_404(
                self.get_job_profile_queryset(),
                pk=self.kwargs['jobprofile_uuid']
            )
        except KeyError:
            return self.get_job_profile_queryset().first()

    @cached_property
    def competency_tree(self):
        """Return the current competency_tree based on the ``job_profile``
        and ``managed_group`` property if applicable.

        Raises
        ------
        ``django.http.Http404``
            If the user cannot see the competency tree, or if the competency tree
            does not exists.

        """

        # If we got no current ``managed_group``, go for the "original" version,
        # associated with the current job_profile
        if not self.managed_group:
            return get_object_or_404(
                self.get_competency_tree_queryset(),
                job_profile=self.job_profile
            )
        # If we got a ``managed_group``, go for the "duplicated" version,
        # associated with the current group
        else:
            return get_object_or_404(
                self.get_competency_tree_queryset(),
                original_version__job_profile=self.job_profile,
                group=self.managed_group
            )

    @cached_property
    def managed_group(self):
        """Return the current managed_group based on the ``group_uuid``
        argument in the url, if applicable.

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

        """

        try:
            return get_object_or_404(
                self.get_managed_groups_queryset(),
                pk=self.request.GET['group_uuid']
            )
        except KeyError:
            return None


class CompetencyBaseView(AssessmentBaseView):
    """Base view for all the views related to the management of the competencies.

    Define the ``current_sub_section`` and the ``competency_tree in the context,
    and provide the attributes used by its children.

    """

    model = Competency
    pk_url_kwarg = 'competency_uuid'

    def get_queryset(self):
        """Queryset to use to filter only Competency visible to the current user."""

        return Competency.objects.non_deleted().prefetch_related(
            'dimensions', 'tree'
            ).filter(tree=self.competency_tree)

    def get_context_data(self, **kwargs):
        """Define the ``current_sub_section`` and the ``competency_tree`` in the context."""

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

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

        It actually returns the url of the list of competency.

        """

        return self.competency_tree.get_competency_list_url()


class CompetencyTreeDuplicateView(SuccessMessageMixin, CompetencyBaseView, generic.FormView):
    """View to duplicate the competency tree to the given groups"""

    form_class = TreeDuplicateForm
    template_name = 'assessments/competency_tree_duplicate.html'
    success_message = _('The competency tree was successfully duplicated.')

    def form_valid(self, form):
        """Duplicate the tree to the groups 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 competency.

        Raises
        ------
        ``django.http.Http404``
            If the current user cannot duplicate the ``CompetencyTree``

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

        """

        # The current user cannot edit the competency_tree, abort
        if not self.competency_tree.can_edit:
            raise Http404

        groups = form.cleaned_data['groups']

        self.competency_tree.duplicate_to_groups(groups)

        self.send_message(form.cleaned_data)

        return HttpResponseRedirect(
            self.competency_tree.get_competency_list_url())


class CompetencyTreeExportTranslationView(TranslationExportDetailView, AssessmentBaseView):
    """ This view allows to export text to translate for all CompetencyTree. """

    model = CompetencyTree

    def get_object(self, queryset=None):
        return self.competency_tree


class CompetencyTreeImportTranslationView(ContentTranslationImportView, AssessmentBaseView):
    """Allow to import text to translate for all CompetencyTree."""

    model = CompetencyTree

    @property
    def get_translated_object(self):
        return self.competency_tree

    def get_object(self, queryset=None):
        return self.competency_tree


class CompetencyListView(CompetencyBaseView, generic.ListView):
    """View to list the ``Competency`` instances the current user is allowed to see."""


class CompetencyEditBaseView(SuccessMessageMixin, CompetencyBaseView):
    """Base View to edit, create or delete a ``Competency`` instance."""

    fields = [
        'code',
        'name',
    ]

    def get_queryset(self):
        """Filter the competencies that may be edited by the current user

        Raises
        ------
        ``django.http.Http404``
            If the current ``CompetencyTree`` instance is not editable

        """

        if not self.competency_tree.can_edit:
            raise Http404
        else:
            return super().get_queryset()


class CompetencySortView(OrderableListMixin, CompetencyEditBaseView, generic.ListView):
    """View to sort the ``Competency`` instances the current user is allowed to see."""


class CompetencyCreateView(CompetencyEditBaseView, generic.CreateView):
    """View to create a ``Competency`` instance."""

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

    def form_valid(self, form):
        """Set the ``competency_tree``, save the competency 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 competency.

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

        """

        self.object = form.save(commit=False)
        self.object.tree = self.competency_tree
        self.object.save()

        self.send_message(form.cleaned_data)

        return HttpResponseRedirect(self.get_success_url())


class CompetencyUpdateView(CompetencyEditBaseView, generic.UpdateView):
    """View to update a ``Competency`` instance."""

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


class CompetencyDeleteView(CompetencyEditBaseView, SyncableDeleteView):
    """View to soft-delete a ``Competency`` instance, with confirmation"""

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


class DimensionBaseView(SuccessMessageMixin):
    """Base View to edit, create or delete a ``Dimension`` instance."""

    model = Dimension
    pk_url_kwarg = 'dimension_uuid'
    fields = [
        'code',
        'name',
        'default_weight'
    ]

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

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

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

        It actually returns the url of the list of competencies.

        """
        return self.competency.tree.get_competency_list_url()

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

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

        """
        return get_object_or_404(
            Competency.objects.non_deleted(),
            pk=self.kwargs['competency_uuid']
        )


class DimensionSortView(OrderableListMixin, DimensionBaseView, generic.ListView):
    """View to sort the ``Dimension`` instances the current user is allowed to see."""

    def get_queryset(self):
        """Filter the dimensions according to the current competency."""

        return Dimension.objects.non_deleted().filter(competency=self.competency)


class DimensionCreateView(DimensionBaseView, generic.CreateView):
    """View to create a ``Dimension`` instance."""

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

    def form_valid(self, form):
        """Set the ``competency``, save the dimension 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 competency.

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

        """

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

        self.send_message(form.cleaned_data)

        return HttpResponseRedirect(self.get_success_url())


class DimensionUpdateView(DimensionBaseView, generic.UpdateView):
    """View to update a ``Dimension`` instance."""

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


class DimensionDeleteView(DimensionBaseView, SyncableDeleteView):
    """View to soft-delete a ``Dimension`` instance, with confirmation"""

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


class CertificationProgramBaseView(SuccessMessageMixin, AssessmentBaseView):
    """Base view for all the views related to the management of the certification programs.

    Define the ``current_main_section`` in the context, and provide the attributes
    used by his children.

    """

    model = CertificationProgram
    pk_url_kwarg = 'certificationprogram_uuid'
    fields = [
        'code'
    ]

    def get_context_data(self, **kwargs):
        """Define the ``current_sub_section`` in the context."""
        context = super().get_context_data(
            current_sub_section='certification_program',
            **kwargs
        )

        # We don't need the managed_groups for the children views
        del context['managed_groups']

        return context

    def get_queryset(self):
        """Queryset to use to filter only CertificationProgram visible to the current user."""
        return CertificationProgram.objects.non_deleted().filter(tree=self.competency_tree)

    def get_success_url(self):
        """Get the url to go back to after a successful action.

        It actually returns the url of the certification program page.

        """
        return self.competency_tree.get_certification_program_url()


class CertificationProgramView(CertificationProgramBaseView, generic.FormView):
    """View to manage the ``CertificationProgram`` of the given job profile."""

    form_class = CertificationThresholdsForm
    template_name = 'cms/assessments/certification_program.html'
    success_message = _("The score thresholds have been successfully updated.")

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs['program'] = CertificationProgram.objects.get_or_create(
            tree=self.competency_tree
        )[0]
        return kwargs

    def form_valid(self, form):
        super().form_valid(form)
        form.save()
        return HttpResponseRedirect(self.get_success_url())


class CertificationStatusBaseView(SuccessMessageMixin, AssessmentBaseView):
    """Base View to edit, create or delete a ``CertificationStatus`` instance."""

    model = CertificationStatus
    pk_url_kwarg = 'certificationstatus_uuid'
    fields = [
        'name',
    ]

    def get_queryset(self):
        return CertificationStatus.objects.non_deleted().filter(
            program__tree=self.competency_tree
        )

    def get_success_url(self):
        """Get the url to go back to after a successful action.

        It actually returns the url of the certification program page.

        """
        return self.competency_tree.get_certification_program_url()


class CertificationStatusCreateView(CertificationStatusBaseView, generic.CreateView):
    """View to create a ``CertificationStatus`` instance."""

    success_message = _("The assessment level was successfully created.")

    def form_valid(self, form):
        status = form.save(commit=False)
        status.program = CertificationProgram.objects.filter(
            tree=self.competency_tree).first()
        status.global_threshold = 100
        status.save()

        self.send_message(form.cleaned_data)

        return HttpResponseRedirect(self.get_success_url())


class CertificationStatusUpdateView(CertificationStatusBaseView, generic.UpdateView):
    """View to update a ``CertificationStatus`` instance."""

    success_message = _("The assessment level was successfully updated.")


class CertificationStatusDeleteView(CertificationStatusBaseView, SyncableDeleteView):
    """View to soft-delete a ``CertificationStatus`` instance."""

    success_message = _("The assessment level was successfully deleted.")


class CertificationStatusReferenceView(CertificationStatusBaseView, generic.UpdateView):
    """View to mark a ``CertificationStatus`` instance as reference."""

    success_message = _("The assessment level was successfully marked as reference.")

    def get(self, request, *args, **kwargs):
        """Mark the current object as reference."""

        super().get(request, *args, **kwargs)
        self.object.flag_as_reference()

        return HttpResponseRedirect(self.get_success_url())


class CertificationResultsBaseView(SuccessMessageMixin, AssessmentBaseView):
    """Base view for all view related to the ``CertificationProgramUserStatus`` model"""

    model = CertificationProgramUserStatus
    pk_url_kwarg = 'certificationprogramuserstatus_uuid'
    fields = [
        'status',
    ]

    @cached_property
    def is_job_profile_manager(self):
        """Check if the current user is the current ``job_profile`` manager

        Returns
        -------
        bool: True if the user is managing the current ``job_profile``,
            False otherwise.

        """

        return self.job_profile.manager_member == self.request.user

    def get_queryset(self):
        """Filter the QuerySet accordingly to the user's group

        The QuerySet is filtered against the current ``managed_group``
        and the current ``job_profile``.

        Returns
        -------
        ``mtp.apps.assessments.managers.CertificationProgramUserStatusQuerySet``
            The filtered QuerySet

        """

        statuses_queryset = CertificationProgramUserStatus.objects.non_deleted().filter(
                session__training__job_profile=self.job_profile
        )

        if not self.is_job_profile_manager:
            statuses_queryset = statuses_queryset.filter(
                session__training__business_unit__in=self.get_managed_groups_queryset()
            )

        return statuses_queryset.select_related('user', 'status', 'session')

    def get_context_data(self, **kwargs):
        """Define the ``current_sub_section`` in the context."""

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

    def get_success_url(self):
        """Get the url to go back to after a successful action.

        It actually returns the url of the certification results list page.

        """

        return self.competency_tree.get_certification_results_url()


class CertificationResultsListView(CertificationResultsBaseView, FilterView):
    """View to see and manage the certification results of the given job profile."""

    template_name = 'cms/assessments/certification_results_list.html'
    filterset_class = CertificationResultsFilter
    paginate_by = 20

    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({
            'competency_tree': self.competency_tree
        })

        return kwargs

    def get_context_data(self, **kwargs):
        """Define ``is_job_profile_manager`` and ``certification_statuses`` in the context.

        Also, add the ``DATETIME_INPUT_FORMAT`` to the context (we really need to find a better
        way to do this...)

        """

        available_certification_statuses = CertificationStatus.objects.non_deleted().filter(
            program__tree=self.competency_tree
        )

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


class CertificationResultsDetailView(CertificationResultsBaseView, generic.DetailView):
    """View to manage the certification results of a specific trainee."""

    template_name = 'cms/assessments/certification_results_detail.html'

    def get_context_data(self, **kwargs):
        tree = self.object.program.tree
        trainee = self.object.user

        # We fetch the list of competency scores of this trainee
        # in this training program.
        competency_scores = CompetencyUserScore.objects.filter(
            user=trainee, competency__tree=tree, session=self.object.session
        ).order_by('competency__sort_order').select_related('competency')

        # Then we fetch the lesson recommendations that have been
        # previously computed.
        recommendations = self.object.recommendations.non_deleted().select_related(
            'lesson__topic',
            'level',
        )

        recommendations_by_topic = defaultdict(list)
        for recommendation in recommendations:
            recommendations_by_topic[recommendation.lesson.topic].append(recommendation)

        # Finally we prepare the required data to build the spidergraph
        # of the trainee's competency scores. To avoid long and unreadable
        # captions on the graph itself, each axis is only labeled with
        # a number, which will be used as a prefix for the competency name
        # on the page.
        spidergraph = [
            {'label': (i + 1), 'score': cs.score}
            for i, cs in enumerate(competency_scores.all())
        ]

        return super().get_context_data(
            business_unit=self.object.business_unit,
            trainee=trainee,
            competency_scores=competency_scores,
            recommendations=recommendations,
            recommendations_by_topic=dict(recommendations_by_topic),
            spidergraph=spidergraph,
            **kwargs
        )


class CertificationResultsPDFView(PDFReportMixin, CertificationResultsDetailView):
    """Allow to download the PDF reporting about a user certification."""

    template_name = 'rml/certification_report.rml'

    def get_filename(self):
        """Get the filename of PDF certification report."""

        return "%(business_unit)s_%(short_date)s_%(user)s" % {
            'user': self.object.user,
            'business_unit': self.object.business_unit,
            'short_date': formats.date_format(self.object.session.start_date, "SHORT_DATE_FORMAT")
        }


class CertificationResultsUpdateView(CertificationResultsBaseView, generic.UpdateView):
    """View to update a ``CertificationProgramUserStatus`` instance."""

    success_message = _("The user's certification level was successfully updated.")

    def form_valid(self, form):
        """We flag the certification as forced."""

        obj = form.save(commit=False)
        obj.is_forced = True
        obj.save()

        return HttpResponseRedirect(self.get_success_url())


class CertificationResultsValidateView(CertificationResultsBaseView, generic.UpdateView):
    """View to validate a ``CertificationProgramUserStatus`` instance."""

    def post(self, request, *args, **kwargs):
        """Mark the certification as validated or not, accordingly to his current status

        Returns
        -------
        ``HttpResponse``

        """

        result = self.get_object()
        result.is_validated = not result.is_validated
        result.save()

        return JsonResponse({})


class XLSCompetenciesExportView(XLSExportDetailView):
    """Export XLS Competency Tree with competencies and dimensions."""

    resource = CompetencyTreeResource
    pk_url_kwarg = 'competencytree_uuid'


class XLSCompetenciesImportView(ModelXLSImportView):
    def get_success_url(self):
        if not self.imported_object:
            return reverse('assessments:competency-list')
        return self.imported_object.get_competency_list_url()

    def import_data(self, sheet_name):
        resource = CompetencyTreeResource
        data_object = self.reader.read_values(
            sheet_name,
            len(resource.get_fields(allow_nested=False))
        )

        job_profile_attr = resource.field_name_to_display_field('job_profile')
        job_profile = data_object.pop(job_profile_attr)
        job_profil_grouptype = GroupType.objects.get(name=GROUPTYPE_JOB_PROFILE)

        if Group.objects.filter(name=job_profile, type=job_profil_grouptype).exists():
            raise ImportDataError(
                _("This job_profile already exists!"),
                line=data_object.line
            )

        job_profil_group = Group.objects.create(name=job_profile, type=job_profil_grouptype)
        tree = CompetencyTree.objects.get(job_profile=job_profil_group)

        for data in self.reader.read_table(sheet_name):
            if self.is_compatible(data, CompetencyResource):
                competency, __ = self.create_object(
                    data, resource=CompetencyResource, force_creation=True,
                    tree=tree
                )
            elif self.is_compatible(data, DimensionResource):
                try:
                    self.create_object(
                        data, resource=DimensionResource, force_creation=True,
                        competency=competency
                    )
                except UnboundLocalError:
                    raise ImportDataError(
                        _("Problem in your file: no competency provide before dimensions."),
                        line=data.line
                    )
            else:
                raise ImportDataError(_("Data is not compatible."), line=data.line)

        return tree


class RecommendationsBaseView(SuccessMessageMixin, AssessmentBaseView):
    """Base view for all the views related to the management of the recommendations.

    Define the ``current_sub_section`` in the context, and provide the attributes
    used by its children -- specifically the ``curriculum``.

    """

    def get_context_data(self, **kwargs):
        """Define the ``current_sub_section`` in the context."""

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


class TopicBaseView(RecommendationsBaseView):
    """Base View to edit, create or delete a ``Topic`` instance."""

    model = Topic
    pk_url_kwarg = 'topic_uuid'
    fields = [
        'name',
        'description',
    ]

    @cached_property
    def curriculum(self):
        """Get the current ``Curriculum`` instance

        Returns
        -------
        ``Curriculum`` instance, or None

        Notes
        -----
        We don't raise an Exception if the Curriculum don't exist,
        but create the curriculum instead.

        """

        try:
            return Curriculum.objects.non_deleted().get(
                job_profile=self.competency_tree.original_job_profile,
                business_unit=self.managed_group
            )
        except Curriculum.DoesNotExist:
            return Curriculum.objects.create(
                job_profile=self.competency_tree.original_job_profile,
                business_unit=self.managed_group
            )

    def get_context_data(self, **kwargs):
        """Define the ``curriculum`` in the context."""

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

    def get_queryset(self):
        """Filter the topics accordingly to the current ``Curriculum``

        Returns
        -------
        ``TopicQuerySet`` instance
            The filtered topics.

        """

        return Topic.objects.non_deleted().filter(
            curriculum=self.curriculum
        ).prefetch_related('lessons')

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

        Returns
        -------
        string

        """

        return self.curriculum.get_absolute_url()


class CurriculumView(TopicBaseView, generic.ListView):
    """View to display a ``Curriculum``, which is basically
    a list of ``Topic`` instances."""

    pass


class TopicBaseEditView(TopicBaseView):
    """Base view to all views to edit a single ``Topic`` instance"""

    @cached_property
    def curriculum(self):
        """Get the current ``Curriculum`` instance from the ``curriculum_uuid`` url kwarg

        Returns
        -------
        ``Curriculum`` instance, or None

        Raises
        ------
        ``django.http.Http404``
            If the curriculum doesn't exist.

        """

        return get_object_or_404(
            Curriculum.objects.non_deleted(),
            pk=self.kwargs['curriculum_uuid']
        )


class TopicCreateView(TopicBaseEditView, generic.CreateView):
    """View to create a ``Topic`` instance."""

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

    def form_valid(self, form):
        """Save the ``topic``, set its curriculum 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 curriculum page.

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

        """

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

        self.send_message(form.cleaned_data)

        return HttpResponseRedirect(self.get_success_url())


class TopicUpdateView(TopicBaseEditView, generic.UpdateView):
    """View to update a ``Topic`` instance."""

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


class TopicDeleteView(TopicBaseEditView, SyncableDeleteView):
    """View to soft-delete a ``Topic`` instance, with confirmation."""

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

    def delete(self, request, *args, **kwargs):
        """We soft-delete lessons contained in the soft-deleted topic."""

        response = super().delete(request, *args, **kwargs)

        for lesson in self.object.lessons.non_deleted():
            lesson.remove()

        return response


class LessonBaseView(RecommendationsBaseView):
    """Base View to edit, create or delete a ``Lesson`` instance."""

    model = Lesson
    pk_url_kwarg = 'lesson_uuid'
    fields = [
        'name',
        'type',
        'reference',
        'duration',
        'description',
        'is_active',
    ]

    def get_queryset(self):
        return Lesson.objects.non_deleted()

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

        Returns
        -------
        string

        """

        return self.object.get_edit_url()


class LessonCreateView(LessonBaseView, generic.CreateView):
    """View to create a ``Lesson`` instance."""

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

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

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

        """
        return get_object_or_404(
            Topic.objects.non_deleted(),
            pk=self.kwargs['topic_uuid']
        )

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

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

    def form_valid(self, form):
        """Save the ``lesson``, set its curriculum 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 curriculum page.

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

        """

        self.object = form.save(commit=False)
        self.object.curriculum = self.topic.curriculum
        self.object.topic = self.topic
        self.object.save()

        self.send_message(form.cleaned_data)

        return HttpResponseRedirect(self.get_success_url())


class BaseLessonEditView(LessonBaseView):
    """Base view to update or delete a ``Lesson`` instance"""

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

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


class LessonUpdateView(BaseLessonEditView, generic.UpdateView):
    """View to update a ``Lesson`` instance."""

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


class LessonDeleteView(BaseLessonEditView, SyncableDeleteView):
    """View to soft-delete a ``Lesson`` instance."""

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


class LessonDependencyBaseView(RecommendationsBaseView):
    """Base View to edit, create or delete an instance of a lesson dependency.

    Lesson dependencies are ``LessonRecommendationThreshold``s and ``LessonDimensionWeight``s.

    """

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

        Raises
        ------
        ``django.http.Http404``
            If the current user cannot see the lesson,
            or the if lesson doesn't exist.

        """

        return get_object_or_404(
            Lesson.objects.non_deleted(),
            pk=self.kwargs['lesson_uuid']
        )

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

        return super().get_context_data(
            current_lesson=self.lesson,
            **kwargs
        )

    def get_success_url(self):
        """Get the url to go back to after a successful action.

        It actually returns the url of the lesson page.

        """

        return self.lesson.get_edit_url()

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs['lesson'] = self.lesson
        return kwargs

    def form_valid(self, form):
        """Save the dependency object, set its lesson 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 lesson page.

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

        """

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

        self.send_message(form.cleaned_data)

        return HttpResponseRedirect(self.get_success_url())


class LessonRecommendationLevelBaseView(LessonDependencyBaseView):
    """Base View to edit, create or delete a ``LessonRecommendationThreshold`` instance."""

    model = LessonRecommendationThreshold
    form_class = LessonRecommendationThresholdForm
    pk_url_kwarg = 'lessonrecommendationthreshold_uuid'

    def get_queryset(self):
        return LessonRecommendationThreshold.objects.non_deleted().filter(
            lesson=self.lesson
        ).select_related('lesson')


class LessonRecommendationLevelCreateView(LessonRecommendationLevelBaseView, generic.CreateView):
    """View to create a ``LessonRecommendationThreshold`` instance."""

    success_message = _("The recommendation level was successfully created.")


class LessonRecommendationLevelUpdateView(LessonRecommendationLevelBaseView, generic.UpdateView):
    """View to update a ``LessonRecommendationThreshold`` instance."""

    success_message = _("The recommendation level was successfully updated.")


class LessonRecommendationLevelDeleteView(LessonRecommendationLevelBaseView, SyncableDeleteView):
    """View to soft-delete a ``LeLessonRecommendationThresholdsson`` instance."""

    success_message = _("The recommendation level was successfully deleted.")


class LessonDimensionWeightBaseView(LessonDependencyBaseView):
    """Base View to edit, create or delete a ``LessonDimensionWeight`` instance."""

    model = LessonDimensionWeight
    form_class = LessonDimensionWeightForm
    pk_url_kwarg = 'lessondimensionweight_uuid'

    def get_form_kwargs(self):
        """Add the current competency tree to the form kwargs

        We use it in the form to filter the dimensions which can be used

        """

        kwargs = super().get_form_kwargs()
        kwargs['tree'] = CompetencyTree.objects.get(job_profile=self.lesson.curriculum.job_profile)

        return kwargs

    def get_queryset(self):
        return LessonDimensionWeight.objects.non_deleted().filter(
            lesson=self.lesson
        ).select_related('lesson')


class LessonDimensionWeightCreateView(LessonDimensionWeightBaseView, generic.CreateView):
    """View to create a ``LessonDimensionWeight`` instance."""

    success_message = _("The dimension weight was successfully created.")


class LessonDimensionWeightUpdateView(LessonDimensionWeightBaseView, generic.UpdateView):
    """View to update a ``LessonDimensionWeight`` instance."""

    success_message = _("The dimension weight was successfully updated.")


class LessonDimensionWeightDeleteView(LessonDimensionWeightBaseView, SyncableDeleteView):
    """View to soft-delete a ``LessonDimensionWeight`` instance."""

    success_message = _("The dimension weight was successfully deleted.")
