"""Managers and querysets for the models in ``mtp.apps.trainings``."""

from datetime import date, timedelta

from django.db import models
from django.utils import timezone

from mtp.apps.assessments.managers import AssessableQuerySet
from mtp.apps.sync.managers import SyncableQuerySet
from mtp.discrete_apps.versionable.managers import (
    PolymorphicManagerForDependency,
    PolymorphicQuerySetForDependency,
    VersionableManager,
    VersionableQuerySet,
)


class VersionableTrainingQuerySet(SyncableQuerySet, VersionableQuerySet):
    """Queryset for the ``mtp.apps.trainings.models.Training`` model."""

    def annotate_is_duplicable(self):
        """Annotate the queryset with a ``is_duplicable`` flag for each training.

        A training can be duplicable if it has a non-empty ``business_unit``.

        Returns
        -------
        VersionableTrainingQuerySet
            A clone of the original queryset (``self``), annotated.

        """

        return self.annotate(
            is_duplicable=models.Case(
                models.When(
                    business_unit=None,
                    then=models.Value(False)
                ),
                default=models.Value(True),
                output_field=models.BooleanField(),
            )
        )

    def filter_is_duplicable(self):
        """Filter only trainings who can be duplicable.

        Returns
        -------
        VersionableTrainingQuerySet
            A clone of the original queryset (``self``), filtered.

        """
        return self.annotate_is_duplicable().filter(is_duplicable=True)

    def annotate_is_owned(self, user):
        """Annotate the queryset with a ``is_owned`` flag for a given user.

        In the result queryset, this flag is set to ``True`` for trainings marked as
        owned by the given user, and ``False`` otherwise.

        Parameters
        ----------
        user : mtp.apps.accounts.models.MTPUser
            The user for whom we want to flag all trainings.

        Returns
        -------
        VersionableTrainingQuerySet
            A clone of the original queryset (``self``), annotated.

        """

        return self.annotate(
            is_owned=models.Case(
                models.When(
                    owner=user,
                    then=models.Value(True)
                ),
                default=models.Value(False),
                output_field=models.BooleanField(),
            )
        )

    def owned_by(self, user):
        """Filter only trainings who belongs to a user.

        Parameters
        ----------
        user : mtp.apps.accounts.models.MTPUser
            The user for whom we want the trainings.

        Returns
        -------
        VersionableTrainingQuerySet
            A clone of the original queryset (``self``), filtered.

        """

        return self.filter(owner=user)

    def available_for_user(self, user):
        """Filter only trainings which can be used to create a new ``TrainingSession``.

        The training's ``version_state`` must be set to ``PUBLISHED``,
        and owned by the user.

        Parameters
        ----------
        user : mtp.apps.accounts.models.MTPUser
            The user for whom we want the trainings.

        Returns
        -------
        VersionableTrainingQuerySet
            A clone of the original queryset (``self``), filtered.

        """

        return self.owned_by(user).exclude_is_archived()

    def for_trainee(self, user):
        """Filter the ``Training`` for the users who belongs to the `trainee` group.

        trainee users have access to the ``Training`` who holds at least
        one ``TrainingSession`` the user is into, and who have a date in the past.

        Parameters
        ----------
        user : mtp.apps.accounts.models.MTPUser
            The user for whom we want the trainings.

        Returns
        -------
        VersionableTrainingQuerySet
            A clone of the original queryset (``self``), filtered.

        """

        return self.filter(
            training_sessions__in=user.training_sessions.filter(
                start_date__lte=timezone.now().date()
            ),
        )

    def for_job_profile(self, job_profile):
        """Filter only trainings associated with a job_profile.

        Parameters
        ----------
        job_profile : mtp.apps.accounts.models.Group
            The job_profile for whom we want the trainings.

        Returns
        -------
        VersionableTrainingQuerySet
            A clone of the original queryset (``self``), filtered.

        """
        return self.filter(job_profile=job_profile)

    def for_business_unit(self, business_unit):
        """Filter only trainings associated with a business_unit.

        Parameters
        ----------
        business_unit : mtp.apps.accounts.models.Group
            The business_unit for whom we want the trainings.

        Returns
        -------
        VersionableTrainingQuerySet
            A clone of the original queryset (``self``), filtered.

        """
        return self.filter(business_unit=business_unit)


class VersionableTrainingManager(VersionableManager.from_queryset(VersionableTrainingQuerySet)):
    """Manager for the ``mtp.apps.trainings.models.Training`` model."""

    pass


class DependencyTrainingActivityQuerySet(AssessableQuerySet, SyncableQuerySet,
                                         PolymorphicQuerySetForDependency):
    """Queryset for the ``mtp.apps.trainings.models.TrainingActivity`` model."""

    def for_training(self, training):
        """Filter only activities which belongs to the given ``Training``.

        Parameters
        ----------
        training : mtp.apps.accounts.models.Training
            The training for which we want the activities.

        Returns
        -------
        DependencyTrainingActivityQuerySet
            A clone of the original queryset (``self``), filtered.

        """

        return self.filter(training=training)

    def for_user(self, user):
        """
        Filter only activities owned by the given user.

        Returns
        -------
        DependencyTrainingActivityQuerySet
            A clone of the original queryset (``self``), filtered.

        """

        return self.filter(training__owner=user)

    def editable(self):
        """Filter only activities which belongs to a ``DRAFT`` ``Training``.

        Returns
        -------
        DependencyTrainingActivityQuerySet
            A clone of the original queryset (``self``), filtered.

        """

        from mtp.apps.trainings.models import Training

        return self.filter(training__version_state=Training.VERSION_STATES.DRAFT)


class DependencyTrainingActivityManager(PolymorphicManagerForDependency):
    """Manager for the ``mtp.apps.trainings.models.TrainingActivity`` model."""

    queryset_class = DependencyTrainingActivityQuerySet


class TrainingSessionQuerySet(SyncableQuerySet, models.QuerySet):
    """Queryset for the ``mtp.apps.trainings.models.Training`` model."""

    def visible_by_user(self, user):
        """Filter only sessions a user can access

        The QuerySet is filtered accordingly to the user roles or relations
        to the session's training.

        Parameters
        ----------
        user : mtp.apps.accounts.models.MTPUser
            The user for which we want the sessions.

        """

        return self.filter(
            models.Q(
                training__job_profile_id__in=user.managed_groups.job_profiles().values_list(
                    'pk', flat=True
                )
            ) | models.Q(
                training__business_unit_id__in=user.managed_groups.business_units().values_list(
                    'pk', flat=True
                )
            ) | models.Q(
                training__owner=user
            ) | models.Q(
                users=user,
                start_date__lte=timezone.now().date()
            )
        ).distinct()

    def during(self):
        """Filter only sessions which have a ``state`` to ``DURING``

        Returns
        -------
        TrainingSessionQuerySet
            A clone of the original queryset (``self``), filtered.

        """

        return self.filter(state=self.model.STATE_CHOICES.DURING)

    def closed(self):
        """Filter only sessions which have a ``state`` to ``CLOSED``

        Returns
        -------
        TrainingSessionQuerySet
            A clone of the original queryset (``self``), filtered.

        """

        return self.filter(state=self.model.STATE_CHOICES.CLOSED)

    def for_training(self, training):
        """Filter only sessions which belongs to the given ``Training``.

        Parameters
        ----------
        training : mtp.apps.accounts.models.Training
            The training for which we want the sessions.

        Returns
        -------
        TrainingSessionQuerySet
            A clone of the original queryset (``self``), filtered.

        """

        return self.filter(training=training)

    def between_dates(self, date_begin=None, date_end=None):
        """Allow filter training sessions for a given time period.

        Parameters
        ----------
        date_begin : ``datetime``, default to ``None``
            If specified, only the session starting from this time are taken into account.
        date_end : ``datetime``, default to ``None``
            If specified, the session ending after this time are excluded.

        Returns
        -------
        TrainingSessionQuerySet
            A Queryset filtered if at least one if ``date_begin`` and ``date_end`` is set.

        """
        filters = {}

        if date_begin is not None:
            filters['start_date__gte'] = date_begin
        if date_end is not None:
            filters['end_date__lte'] = date_end

        if filters:
            return self.filter(**filters)

        return self


class TrainingSessionManager(models.Manager.from_queryset(TrainingSessionQuerySet)):
    """Manager for the ``mtp.apps.trainings.models.TrainingSession`` model."""

    def close_outdated(self, delta_after=timedelta(), dry_run=False, async=False):
        """Close the "outdated" ``TrainingSession`` instances

        Where "outdated" means the session got a ``end_date`` in the past,
        and a ``state`` to ``DURING``.

        Parameters
        ----------
        delta_after : timedelta
            It's a kind of a "grace period". It's allow closing the session not directly
            after they were played, but after a given amount of time.
        dry_run : bool
            If ``False``, close the sessions and return the list of closed session pks,
            if ``True``, simply return the list of potential closed session pks without
            closing them.
        async : bool
            Default to ``False``.
            If ``True``, the session closing will be done asynchronously, otherwise it will be
            executed directly.

        Returns
        -------
        list
            A list containing the UUID of the closed sessions

        """

        date_end_filter = timezone.now() - delta_after

        outdated_sessions = self.during().between_dates(
            date_end=date_end_filter)

        return [
            session.pk if dry_run else session.close(async=async).pk
            for session in outdated_sessions
        ]

    def get_assessing_sessions(self):
        """Get assessing sessions.

        Notes
        -----
        We use assessing activities in trainings to determine if the session is assessing or not.

        Returns
        -------
        QuerySet

        """

        return self.non_deleted().filter(
            training__activities__is_deleted=False,
            training__activities__is_assessing=True,
        ).distinct()
