"""Models for the chat app."""

from uuid import uuid4

from django.core.exceptions import ValidationError
from django.db.models import Q, Max
from django.db.models.signals import post_save
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from aparnik.contrib.filefields.models import FileField
from django.db import models
from django.contrib.auth import get_user_model
from django_enumfield import enum

User = get_user_model()


class ChatSessionTypeEnum(enum.Enum):
    PRIVATE = 0
    GROUP = 1
    CHANNEL = 2

    labels = {
        PRIVATE: _('PRIVATE'),
        GROUP: _('GROUP'),
        CHANNEL: _('CHANNEL')
    }


class TrackableDateModel(models.Model):
    """Abstract model to Track the creation/updated date for a model."""

    created_at = models.DateTimeField(auto_now_add=True)
    update_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

    @property
    def resourcetype(self):
        return self._meta.object_name


def _generate_unique_uri():
    """Generates a unique uri for the chat session."""
    return str(uuid4()).replace('-', '')[:15]


class ChatSessionManager(models.Manager):
    def get_queryset(self):
        return super(ChatSessionManager, self).get_queryset().select_related()

    def user_session(self, user):
        queryset_first = self.get_queryset().filter(
            pk__in=ChatSessionMember.objects.filter(user=user).values_list('chat_session', flat=True).distinct())
        queryset = (queryset_first | self.get_queryset().filter(owner=user)).distinct().annotate(message_created_at=Max('messages__created_at'))
        return queryset

    def private(self, owner, user):
        """
        You can add private chat one by one.
        :param owner: User first time create the chat
        :param user: User add to this chat
        :return: ChatSession instance
        """
        username_first = owner.username
        username_second = user.username
        queryset = self.get_queryset().filter(Q(type=ChatSessionTypeEnum.PRIVATE), Q(owner__username=username_first,
                                                                                     members__user__username=username_second))
        queryset = (queryset | self.get_queryset().filter(Q(type=ChatSessionTypeEnum.PRIVATE),
                                                          Q(owner__username=username_second,
                                                            members__user__username=username_first))).distinct()
        if queryset.exists():
            return queryset.first()
        chat_session = ChatSession.objects.create(
            owner=owner,
            type=ChatSessionTypeEnum.PRIVATE,
        )
        ChatSessionMember.objects.create(
            chat_session=chat_session,
            user=user,
        )
        return chat_session


class ChatSession(TrackableDateModel):
    """
    A Chat Session.

    The uri's are generated by taking the first 15 characters from a UUID
    """

    owner = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name=_('Owner'))
    uri = models.URLField(default=_generate_unique_uri, verbose_name=_('Uri'))
    type = enum.EnumField(ChatSessionTypeEnum, default=ChatSessionTypeEnum.GROUP,
                          verbose_name=_('Type'))
    cover_obj = models.ForeignKey(FileField, null=True, blank=True, on_delete=models.PROTECT, verbose_name=_('Cover'))
    title = models.CharField(max_length=255, default='', blank=True, verbose_name=_('Title'))

    objects = ChatSessionManager()

    def __str__(self):
        return self.uri

    def full_clean(self, exclude=None, validate_unique=True):
        if self.type != ChatSessionTypeEnum.PRIVATE and (self.title == '' or self.title is None):
            raise ValidationError(
                {'title': [_('This filed is required.'), ], }, )

    def save(self, *args, **kwargs):
        self.full_clean()
        return super(ChatSession, self).save(*args, **kwargs)

    def get_api_uri(self):
        return reverse('aparnik-api:chats:details', args=[self.uri])

    def get_api_mark_as_read_uri(self):
        return reverse('aparnik-api:chats:mark-as-read', args=[self.uri])

    def get_member_api_uri(self):
        return reverse('aparnik-api:chats:members', args=[self.uri])

    def get_messages_api_uri(self):
        return reverse('aparnik-api:chats:messages', args=[self.uri])

    def get_messages_create_api_uri(self):
        return reverse('aparnik-api:chats:messages-create', args=[self.uri])


class ChatSessionMessage(TrackableDateModel):
    """Store messages for a session."""

    user = models.ForeignKey(User, on_delete=models.PROTECT)
    chat_session = models.ForeignKey(
        ChatSession, related_name='messages', on_delete=models.PROTECT
    )
    message = models.TextField(default='', max_length=2000)
    file_obj = models.ForeignKey(FileField, null=True, blank=True, on_delete=models.CASCADE, verbose_name=_('File'))

    def get_api_uri(self):
        return reverse('aparnik-api:chats:messages-details', args=[self.pk])


class ChatSessionMemberManager(models.Manager):
    def get_queryset(self):
        return super(ChatSessionMemberManager, self).get_queryset().select_related()


class ChatSessionMember(TrackableDateModel):
    """Store all users in a chat session."""

    chat_session = models.ForeignKey(
        ChatSession, related_name='members', on_delete=models.PROTECT
    )
    user = models.ForeignKey(User, on_delete=models.PROTECT)

    objects = ChatSessionMemberManager()

    def get_api_uri(self):
        return reverse('aparnik-api:chats:members-details', args=[self.pk])


def post_save_chat_session_message_receiver(sender, instance, created, *args, **kwargs):
    if created:
        from .tasks import send_chat_message
        send_chat_message.delay(instance.pk)


post_save.connect(post_save_chat_session_message_receiver, sender=ChatSessionMessage)


class ChatMessageNotificationManager(models.Manager):
    def get_queryset(self):
        return super(ChatMessageNotificationManager, self).get_queryset().select_related()

    def active(self):
        return self.get_queryset()

    def mark_as_read_chat_session(self, user, chat_session):
        count = self.unread_chat_session(user, chat_session).update(is_read=True)
        return count

    def unread_chat_session(self, user, chat_session):
        return self.active().filter(chat_message__chat_session=chat_session, is_read=False,
                                                        user=user)

    def unread_count_chat_session(self, user, chat_session):
        return self.unread_chat_session(user, chat_session).count()

    def unread(self, user):
        return self.active().filter(user=user, is_read=False)

    def unread_count(self, user):
        return self.active().filter(user=user, is_read=False).count()


class ChatMessageNotification(TrackableDateModel):
    """Store all users notification in a chat message."""

    chat_message = models.ForeignKey(
        ChatSessionMessage, related_name='message_notifications', on_delete=models.PROTECT
    )
    user = models.ForeignKey(User, on_delete=models.PROTECT)
    is_read = models.BooleanField(default=False, verbose_name=_('Is read'))

    objects = ChatMessageNotificationManager()

