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

from celery.utils.log import get_task_logger
from constance import config

from mtp.apps.accounts.models import Group, MTPUser
from mtp.apps.tasks.tasks import DjangoBackedCeleryTask
from mtp.celery import app

from .models import Training, TrainingSession, TrainingTask

logger = get_task_logger(__name__)  # pylint: disable=invalid-name


class TrainingCeleryTask(DjangoBackedCeleryTask):
    """

    This a specialized case of DjangoBackedCeleryTask that stores a relation
    with a ``Training``.

    """

    django_task_model = TrainingTask

    def create_django_task(self, task_id, user, training, task_type, **kwargs):
        """Create an instance of``django_task_model`` with the provided arguments

        The received training is stored in the tied ``TrainingTask`` instance (same for the task
        type), and converted into ``training_pk`` to be passed to the celery task.

        Parameters
        ----------
        training: mtp.apps.trainings.models.Training
            The training being manipulated
        task_type: str
            The type of the saved task.
            A value from ``mtp.apps.trainings.models.TASK_TYPES``

        """

        # The tasks are expecting a training pk, so we gave them from the training we reveived.
        kwargs['training_pk'] = training.pk

        if task_type not in TrainingTask.TASK_TYPES:
            raise ValueError('`%s` is not a valid task type')

        # Do not try to create an instance if we don't have a task_id (that may happen in tests)
        if not task_id:
            return None, kwargs

        django_task, __ = self.django_task_model.objects.get_or_create(
            celery_task_id=task_id,
            user=user,
            training=training,
            task_type=task_type
        )

        return django_task, kwargs


@app.task(base=TrainingCeleryTask)
def duplicate_training_to_member(training_pk, new_state, params):
    """Task that will duplicate a training to a member defined in ``params``

    Parameters
    ----------
    new_state : string
        Value from ``VERSION_STATES`` to set to the ``version_state`` on the duplicated
        training,
    params : dict
        The fields to set on the training to duplicate. Must at least contain a ``owner_id``
        key.
        Values may represent primary keys of objects, not object themselves.

    Returns
    -------
    UUID or None
        The primary key of the new duplicated training, or ``None`` if the same duplicate is
        currently in progress.

    Notes
    -----
    This task simply convert primary keys to real objects and call the ``duplicate_to_member``
    method of the training.

    """

    training = Training.objects.get(pk=training_pk)

    # Converts PKs in params into real objects to pass to ``duplicate_to_member``
    params = params.copy()
    if 'owner_id' in params:
        params['owner'] = MTPUser.objects.get(pk=params.pop('owner_id'))
    if 'business_unit_id' in params:
        params['business_unit'] = Group.objects.get(pk=params.pop('business_unit_id'))

    try:
        new_training = training.duplicate_to_member(
            new_state=new_state,
            params=params,
            async=False,
            skip_in_progress=False,
        )
    except Training.DuplicateInProgress:
        return None

    return new_training.pk


@app.task(base=TrainingCeleryTask)
def create_training_draft(training_pk, fields=None):
    """Task that will create a new draft from a training

    Parameters
    ----------
    fields : dict, optional
        The fields to set on the new training. Could be empty.
        Values may represent primary keys of objects, not object themselves.

    Returns
    -------
    UUID or None
        The primary key of the new duplicated training, or ``None`` if the same draft creation is
        currently in progress.

    Notes
    -----
    This task simply call the ``create_draft`` method of the training.

    """

    training = Training.objects.get(pk=training_pk)

    # Converts PKs in params into real objects to pass to ``create_draft``
    fields = {} if not fields else fields.copy()
    if 'owner_id' in fields:
        fields['owner'] = MTPUser.objects.get(pk=fields.pop('owner_id'))

    try:
        new_training = training.create_draft(
            async=False,
            skip_in_progress=False,
            **fields
        )
    except Training.DuplicateInProgress:
        return None

    return new_training.pk


@app.task
def compute_assessment_scores(session_pk, force_recompute=True):
    """Task that will compute assessment scores of a session.

    Parameters
    ----------
    session_pk : UUID
        The primary key of the session we want to compute assessment scores.
    force_recompute : bool
        Set to False, to recompute only certifications and recommendations.
        Set to True, to recompute ``CompetencyTreeUserScore`` too and use new user activity scores.
        Default value is True.

    """

    session = TrainingSession.objects.get(pk=session_pk)
    session.compute_assessment_scores(force_recompute=force_recompute)


@app.task
def close_outdated_training_sessions():  # pylint: disable=invalid-name
    """Task that will close the "outdated" ``TrainingSession`` instances

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

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

    Notes
    -----
    This task is called every day at midnight.

    """

    if not config.AUTO_CLOSE_SESSIONS_ACTIVATE:
        return
    else:
        return TrainingSession.objects.close_outdated(
            days_after=config.AUTO_CLOSE_SESSIONS_GRACE_PERIOD_DAYS,
            async=True
        )


@app.task
def archive_training(training_pk):
    """Task that will archive the training with the provided pk

    Parameters
    ----------
    training_pk: str
        The primary key of the training we want to archive.

    """

    Training.objects.get(pk=training_pk).archive()
