"""This module contains the models for the ``Tasks`` app

Attributes
----------

TASK_STATES : ``extended_choices.Choices``
    A choices that take all of its values from the celery states (in ``celery.states.ALL_STATES``).
    It will be used as the ``choices`` argument of the ``DjangoTask.state`` field, to allow
    filtering.

    At the moment, these states are:
    - PENDING
    - RECEIVED
    - STARTED
    - SUCCESS
    - FAILURE
    - RETRY
    - REVOKED

    For each state, we use the original constant as the constant in the ``Choices`` object,
    we store it lowered in the database, and display it capitalized
    For example ``(`PENDING`, 'pending', 'Pending')``

    This ``Choices`` object also contains subsets for all subsets defined in celery, to easily
    filter on tasks based on their state:
    - EXCEPTION_STATES
    - PROPAGATE_STATES
    - READY_STATES
    - UNREADY_STATES

"""

from celery import states
from celery.result import AsyncResult

from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _

from django_extensions.db.fields import UUIDField

from extended_choices import OrderedChoices

from mtp.apps.common.limpyd_base.django_models import RedisBackedDjangoModelWithLogs


TASK_STATES = OrderedChoices(
    *[(state, state.lower(), state.capitalize()) for state in sorted(states.ALL_STATES)]
)
for subset in ('EXCEPTION_STATES', 'PROPAGATE_STATES', 'READY_STATES', 'UNREADY_STATES'):
    TASK_STATES.add_subset(subset, getattr(states, subset))


class DjangoTask(RedisBackedDjangoModelWithLogs, models.Model):
    """Django model representing a Task

    A Django task is linked to a Celery ``Task``, allowing us to
    follow a celery ``Task`` execution flow thus optionally providing feedback to the user
    who "ordered" the Celery task execution.

    This model is used in the ``mtp.apps.task.celery_tasks.DjangoBackedTask`` class.

    Attributes
    ----------
    celery_task_id: django ``UUIDField`` field
        The UUID of the celery task associated with this django task
    user: foreign key to ``mtps.apps.accounts.MTPUser``
        The optional user who created this task
    state: django ``CharField`` field
        The state of the celery task. Can be retrieved via celery but stored here too to allow
        filtering on this field. Use ``.filter(state__in=TASK_STATES.UNREADY_STATES`` to only
        filter on tasks not finished, for example.
        The max length of this field is based on the actual max length of the available states.
        A test check that it's enough, and will break if a new version of celery adds a state that
        is too long.
    started_at: django ``DateTimeField`` field
        The datetime when the tied celery task started executing
    finished_at: django ``DateTimeField`` field
        The datetime when the tied celery task finished executing

    TASK_STATES: ``extended_choices.Choices``
        An extended choices used in for the ``state`` field.
        Simple accessor of the ``TASK_STATES`` object defined at the module level, added in the
        class to easy access over the project.

    Notes
    -----
    This model inherits from ``RedisBackedDjangoModelWithLogs``, allowing us
    to log the celery task activity via the tied limpyd model ``LimpydTask``.

    """

    celery_task_id = UUIDField(
        verbose_name=_('Celery task id'),
        unique=True
    )
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        verbose_name=_('User'),
        related_name='tasks',
        null=True,
        on_delete=models.CASCADE
    )
    state = models.CharField(
        verbose_name=_('State'),
        choices=TASK_STATES,
        default=TASK_STATES.PENDING,
        max_length=8,
        db_index=True,
    )
    started_at = models.DateTimeField(
        verbose_name=_('Started At'),
        default=now
    )
    finished_at = models.DateTimeField(
        verbose_name=_('Finished At'),
        null=True
    )

    TASK_STATES = TASK_STATES

    @cached_property
    def redis_model(self):
        """Returns the ``DjangoBackedRedisModel`` linked to the current one."""

        from .redis_models import LimpydTask

        return LimpydTask

    @property
    def celery_async_result(self):
        """Get the celery ``AsyncResult`` from the ``celery_task_id``

        Returns
        -------
        celery ``AsyncResult`` or None

        """

        return AsyncResult(str(self.celery_task_id))

    @classmethod
    def get_list_url(cls):
        """Get the url to display a list of tasks

        Returns
        -------
        string

        """

        return reverse('tasks:list')

    def manage_result(self, result):
        """A hook to let subclasses manage the result of the successful celery task

        Parameters
        ----------
        result: may be anything
            The result returned by the celery task

        """

        pass
