"""This module holds the forms for the ``trainings`` app."""

from django import forms
from django.db.models import Q
from django.utils.formats import date_format
from django.utils.translation import ugettext_lazy as _

import waffle
from django_select2.forms import ModelSelect2MultipleWidget

from mtp.apps.accounts.constants import GROUPTYPE_BUSINESS_UNIT
from mtp.apps.accounts.forms import MTPUserSelectMultipleWidget
from mtp.apps.accounts.models import Group, MTPUser

from .models import (
    Training,
    TrainingActivity,
    TrainingClientOptions,
    TrainingSession
)


def _toggle_locks(fields):
    """
    Remove ``*_lock`` fields from the ``TrainingForm`` according to the
    ``enable_training_locks`` Waffle switch.

    """
    if not waffle.switch_is_active('enable_training_locks'):
        lock_fields = [field for field in fields if field.endswith('_lock')]
        for lock_field in lock_fields:
            del fields[lock_field]


class TrainingForm(forms.ModelForm):
    """Form for ``mtp.apps.trainings.models.Training`` model."""

    class Meta:
        model = Training
        fields = [
            'name',
            'description',
            'job_profile',
            'business_unit',
            'header_image',
        ]

    def __init__(self, *args, **kwargs):
        """Init the form accordingly to the provided kwargs

        Parameters
        ----------
        kwargs: dict
            - current_user: ``mtp.apps.accounts.models.MTPUser`` instance
                Used to filter the available groups for the business_unit field.

        """

        current_user = kwargs.pop('current_user')

        super().__init__(*args, **kwargs)

        # If assessment is disabled, we remove the `job_profile` field
        if not waffle.switch_is_active('enable_assessments'):
            self.fields.pop('job_profile')
        else:

            # Filter the job_profile which can be added
            self.fields['job_profile'].queryset = Group.objects.job_profiles().non_deleted().filter(
                member_roles__user=current_user
            )

            # Filter the groups that can be added,
            # accordingly to the user's managed groups
            self.fields['business_unit'].queryset = Group.objects.filter(
                member_roles__user=current_user,
                type__name=GROUPTYPE_BUSINESS_UNIT
            )


class TrainingArchiveForm(forms.ModelForm):
    """Form for archiving a ``Training`` instance

    Attributes
    ----------
    current_user: MTPUser
        The user asking for the archiving.

    For the other parameters, see ``django.forms.ModelForm``

    """

    archive_children = forms.BooleanField(required=False)

    class Meta:
        model = Training
        fields = (
            'archive_children',
        )

    def __init__(self, current_user, *args, **kwargs):
        """Populate the label of the ``archive_children`` field

        If the training doesn't have any children, drop the field.

        """

        super().__init__(*args, **kwargs)

        duplicates_ids_nb = len(Training.objects.get_duplicate_ids(
            [self.instance.id],
            exclude_owner_id=current_user.id
        ))

        if duplicates_ids_nb:
            self.fields['archive_children'].label = _(
                'Archive the shared trainings (%d shared trainings will also be archived)' % (
                    duplicates_ids_nb
                )
            )
        else:
            self.fields.pop('archive_children')


class TrainingSessionForm(forms.ModelForm):
    """Form for ``mtp.apps.trainings.models.TrainingSession`` model."""

    AVAILABLE_PERMISSION_CHOICES = TrainingSession.PERMISSION_CHOICES.extract_subset(
        'PUBLIC', 'PRIVATE',
    )

    users_queryset = MTPUser.objects.non_deleted().active().trainees()

    users = forms.ModelMultipleChoiceField(
        widget=MTPUserSelectMultipleWidget(
            queryset=users_queryset,
            attrs={'data-allow-clear': 'false'}
        ),
        required=False,
        queryset=users_queryset,
        label=_('Users')
    )

    class Meta:
        model = TrainingSession
        fields = (
            'training',
            'trainer',
            'users',
            'start_date',
            'end_date',
            'permissions',
        )

    def __init__(self, *args, **kwargs):
        """Init the form accordingly to the provided kwargs

        Parameters
        ----------
        kwargs: dict
            - remove_training_field: bool
                If ``True``, the training field is removed.
            - remove_trainer_field: bool
                If ``True``, the trainer field is removed.
            - current_user: ``mtp.apps.accounts.models.MTPUser`` instance
                Used to filter the trainings which can be used in the
                training field.

        """

        remove_training_field = kwargs.pop('remove_training_field')
        remove_trainer_field = kwargs.pop('remove_trainer_field')
        current_user = kwargs.pop('current_user')

        super().__init__(*args, **kwargs)

        self.fields['permissions'].choices = self.AVAILABLE_PERMISSION_CHOICES

        if remove_training_field:
            self.fields.pop('training')
        else:
            self.fields['training'].queryset = Training.objects.available_for_user(
                current_user
            )

        if remove_trainer_field:
            self.fields.pop('trainer')
        else:
            self.fields['trainer'].queryset = MTPUser.objects.non_deleted().active().trainers()

    def clean(self):
        """Check ``start_date`` is before ``end_date``.

        Returns
        -------
        ``forms.Form.cleaned_data``

        Raises
        ------
        ``forms.ValidationError``
        """
        cleaned_data = super().clean()
        start_date = cleaned_data.get("start_date")
        end_date = cleaned_data.get("end_date")

        if end_date < start_date:
            raise forms.ValidationError(_("End date cannot be earlier than start date"))

        return cleaned_data


class TrainingOptionsForm(forms.ModelForm):
    """Form for ``TrainingClientOptions`` model."""

    class Meta:
        model = TrainingClientOptions
        fields = (
            'activate_show_results',
            'activate_activity_quickcreation',
            'activate_qrcode_launcher',
        )

    def __init__(self, *args, **kwargs):
        """Adapt the form according to assessment waffle.

        For the parameters, see ``django.forms.ModelForm``"""

        super().__init__(*args, **kwargs)

        if not waffle.switch_is_active('enable_assessments'):
            self.fields.pop('activate_show_results')


class TrainingActivityFormMixin(forms.ModelForm):
    """Mixin to build forms for training activities.

    Attributes
    ----------
    training: Training
        The training of the instance to create/edit.

    For the parameters, see the ``forms.ModelForm`` class.

    """

    training = None

    class Meta:
        fields = ('name', 'description')

    def __init__(self, *args, **kwargs):
        """Get the provided training if it exists."""

        self.training = kwargs.pop('training', None)
        super().__init__(*args, **kwargs)

    def save(self, commit=True):
        """Set the training if applicable, before to save instance.

        Parameters
        ----------
        For the parameters, see the ``forms.ModelForm`` class.

        """

        if self.training:
            self.instance.training = self.training

        return super().save(commit=commit)


class ExportableResultsActivityForm(forms.ModelForm):
    """A form containing a choice of activity for which the results are exportable

    Parameters
    ----------
    For the parameters, see the ``forms.Form`` class.

    """

    activities_to_export = forms.MultipleChoiceField(
        label=_('Select activities to export')
    )

    class Meta:
        model = TrainingSession
        fields = ()

    def __init__(self, *args, **kwargs):
        """Populate the available activities to export

        We expect `exportable_activities` to be part of the kwargs.

        See Also
        --------
        `mtp.apps.trainings.views.TrainingSessionResultsExportView`

        """

        exportable_activities = kwargs.pop('exportable_activities')

        super().__init__(*args, **kwargs)

        self.fields['activities_to_export'].choices = [
            (exportable_activity['id'], exportable_activity['id'])
            for exportable_activity in exportable_activities
        ]


class TrainingSelectWidget(ModelSelect2MultipleWidget):
    """Define a Searchable select widget for the ``Training`` instance"""

    search_fields = ['name__icontains']
    queryset = Training.objects.all()

    def label_from_instance(self, instance):
        """Return the representation of each training choice

        For the parameters, see the ``ModelSelect2MultipleWidget`` class.

        """

        return '%s - %s' % (
            instance.name,
            date_format(instance.created_at, 'SHORT_DATETIME_FORMAT')
        )


class TrainingActivityTrainingsDuplicateForm(forms.ModelForm):
    """A form containing a choice of trainings for which we want to duplicate an activity

    Parameters
    ----------
    For the parameters, see the ``forms.Form`` class.

    """

    trainings = forms.ModelMultipleChoiceField(
        widget=TrainingSelectWidget(),
        label=_('Scenarios'),
        queryset=Training.objects.none()
    )

    class Meta:
        model = TrainingActivity
        fields = (
            'trainings',
        )

    def __init__(self, *args, **kwargs):
        """Init the form, populating the ``trainings`` field queryset"""

        user = kwargs.pop('user')

        super().__init__(*args, **kwargs)

        training_queryset = Training.objects.owned_by(user).filter_is_editable()

        self.fields['trainings'].queryset = training_queryset
        self.fields['trainings'].widget.queryset = training_queryset


class TrainingActivityLibraryDuplicateForm(forms.ModelForm):
    """A form containing the name of the next duplicate of an ``Activity``

    Parameters
    ----------
    For the parameters, see the ``forms.Form`` class.

    """

    duplicate_name = forms.CharField(
        label=_('Name'),
        max_length=TrainingActivity._meta.get_field('name').max_length
    )

    class Meta:
        model = TrainingActivity
        fields = (
            'duplicate_name',
        )

    def __init__(self, *args, **kwargs):
        """Get the ``is_shared`` flag and the current user from the kwargs"""

        self.is_shared = kwargs.pop('is_shared')
        self.user = kwargs.pop('user')

        super().__init__(*args, **kwargs)

        next_name = TrainingActivity.objects.check_available_name(
            klass=self.instance.__class__,
            name=self.instance.name,
            owner=self.user,
            is_shared=self.is_shared
        )[1]

        if not self.fields['duplicate_name'].initial:
            self.fields['duplicate_name'].initial = next_name

    def clean_duplicate_name(self):
        """Check if the name exists in the target"""

        duplicate_name = self.cleaned_data['duplicate_name']

        available_name = TrainingActivity.objects.check_available_name(
            klass=self.instance.__class__,
            name=duplicate_name,
            owner=self.user,
            is_shared=self.is_shared
        )[0]

        if not available_name:
            raise forms.ValidationError(
                _(
                    'A %(activity_type)s named "%(activity_name)s" already exists' % {
                        'activity_type': self.instance.activity_type,
                        'activity_name': duplicate_name,
                    }
                )
            )

        return duplicate_name


class TrainingActivitySelectWidget(ModelSelect2MultipleWidget):
    """Define a Searchable select widget for the ``TrainingActivity`` instance"""

    search_fields = [
        'name__icontains',
        'library_status__owner__first_name__icontains',
        'library_status__owner__last_name__icontains'
    ]

    def label_from_instance(self, instance):
        """Return the representation of each training activity choice

        For the parameters, see the ``ModelSelect2MultipleWidget`` class.

        """

        return '%s - %s - %s' % (
            instance.verbose_activity_name,
            instance.name,
            instance.library_status.owner.get_full_name()
        )


class TrainingActivitiesAddForm(forms.ModelForm):
    """Form to add multiple activities into a ``Training`` instance

    Parameters
    ----------
    For the parameters, see the ``forms.ModelForm`` class.

    """

    activities = forms.ModelMultipleChoiceField(
        widget=TrainingActivitySelectWidget(),
        label=_('Activities'),
        queryset=TrainingActivity.objects.none()
    )

    def __init__(self, *args, **kwargs):
        """Init the form, populating the ``trainings`` field queryset"""

        user = kwargs.pop('current_user')

        super().__init__(*args, **kwargs)

        activity_queryset = TrainingActivity.objects.select_related(
            'library_status',
            'library_status__owner'
        ).filter(
            Q(library_status__owner=user, library_status__is_shared=False)
            | Q(library_status__is_shared=True)
        ).non_deleted()

        self.fields['activities'].queryset = activity_queryset
        self.fields['activities'].widget.queryset = activity_queryset

    class Meta:
        model = Training
        fields = (
            'activities',
        )
