""" This module holds the models of the ``likes`` app. """

from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import F
from django.utils import timezone
from django.utils.translation import ugettext as _

from django_extensions.db.fields import UUIDField

from mtp.apps.sync.models import Syncable

from .constants import LIKE_DEFAULT_WEIGHT
from .managers import LikeQuerySet


class Like(Syncable):
    """ A like for likeable object. Any objects can receive a like.

    A user can have only one like on an object.
    To give a greater importance to this like, we can increase its weight.
    To assign a negative like, just put a negative weight like ``-1``.

    Attributes
    ----------
    author : foreign key to ``mtp.apps.account.MTPUser``
    weight : django ``SmallIntegerField``
        Weight of this like.
    date_time : django ``DateTimeField``
        The date/time when author has liked this object.
    liked_object : django ``GenericForeignKey``
        The liked object.
    content_type : django ``ForeignKey``
        The type of liked object.
    object_uuid : django ``UUIDField``
        The liked object uuid.

    Notes
    -----
    This model is ``Syncable`` and uses a Django Generic Relation.
    Morevover, a liked object must have an UUIDField for primary key.
    """

    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        related_name='likes',
        help_text=_('The user who liked this object.'),
        verbose_name=_('author'),
        on_delete=models.CASCADE
    )
    weight = models.SmallIntegerField(
        default=LIKE_DEFAULT_WEIGHT,
        help_text=_('The weight of this like.'),
        verbose_name=_('weight')
    )
    date_time = models.DateTimeField(
        default=timezone.now,
        help_text=_('The date/time when author has liked this object.'),
        verbose_name=_('date time')
    )

    liked_object = GenericForeignKey('content_type', 'object_uuid')
    content_type = models.ForeignKey(
        ContentType,
        help_text=_('The type of liked object.'),
        verbose_name=_('content type'),
        on_delete=models.CASCADE
    )
    object_uuid = UUIDField(
        help_text=_('The liked object uuid.'),
        verbose_name=_('object uuid')
    )

    objects = LikeQuerySet.as_manager()

    class Meta:
        unique_together = ('author', 'content_type', 'object_uuid')

    def __str__(self):
        return _("%(author)s likes %(liked_object)s") % {
            'author': self.author,
            'liked_object': self.liked_object,
        }


class Likeable(models.Model):
    """Abstract class to make an object likeable. To use like a Mixin.

    Add a ``likes`` relation which allows an access to likes of likeable object.

    Attributes
    ----------
    likes : django ``GenericRelation``
        Relation to all likes on this object.

    Notes
    -----
    This model uses a Django Generic Relation.

    """

    class Meta:
        abstract = True

    likes = GenericRelation(
        Like,
        content_type_field='content_type',
        object_id_field='object_uuid'
    )

    def liked_by(self):
        """List users which have liked this object.

        Returns
        -------
        list
            Return users which have liked this object.
            Annotate by the weight of their like.

        Notes
        -----
        We return only non deleted users with non deleted likes.

        """

        return get_user_model().objects.non_deleted().filter(
            likes__object_uuid=self.id,
            likes__is_deleted=False,
        ).annotate(like_weight=F('likes__weight'))
