"""Models for the chat app."""
from datetime import date
from uuid import uuid4

from django.core.exceptions import ValidationError
from django.db.models import Q, Max, DateTimeField
from django.db.models.functions import Coalesce, Cast
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=Coalesce(Max('messages__created_at'),
                                        Cast(date(2002, 12, 31), DateTimeField()))
        )
        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()

