# Copyright (C) 2024 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later

from asgiref.sync import sync_to_async
from django.contrib.auth.models import User
from django.db import models
from django.db.models import Q, QuerySet
from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy as _
from participation.models import Pool, Team, Tournament
from registration.models import ParticipantRegistration, Registration, VolunteerRegistration
from tfjm.permissions import PermissionType


class Channel(models.Model):
    name = models.CharField(
        max_length=255,
        verbose_name=_("name"),
    )

    read_access = models.CharField(
        max_length=16,
        verbose_name=_("read permission"),
        choices=PermissionType,
    )

    write_access = models.CharField(
        max_length=16,
        verbose_name=_("write permission"),
        choices=PermissionType,
    )

    tournament = models.ForeignKey(
        'participation.Tournament',
        on_delete=models.CASCADE,
        blank=True,
        null=True,
        default=None,
        verbose_name=_("tournament"),
        related_name='chat_channels',
        help_text=_("For a permission that concerns a tournament, indicates what is the concerned tournament."),
    )

    pool = models.ForeignKey(
        'participation.Pool',
        on_delete=models.CASCADE,
        blank=True,
        null=True,
        default=None,
        verbose_name=_("pool"),
        related_name='chat_channels',
        help_text=_("For a permission that concerns a pool, indicates what is the concerned pool."),
    )

    team = models.ForeignKey(
        'participation.Team',
        on_delete=models.CASCADE,
        blank=True,
        null=True,
        default=None,
        verbose_name=_("team"),
        related_name='chat_channels',
        help_text=_("For a permission that concerns a team, indicates what is the concerned team."),
    )

    private = models.BooleanField(
        verbose_name=_("private"),
        default=False,
        help_text=_("If checked, only users who have been explicitly added to the channel will be able to access it."),
    )

    invited = models.ManyToManyField(
        'auth.User',
        verbose_name=_("invited users"),
        related_name='+',
        blank=True,
        help_text=_("Extra users who have been invited to the channel, "
                    "in addition to the permitted group of the channel."),
    )

    def __str__(self):
        return str(format_lazy(_("Channel {name}"), name=self.name))

    @staticmethod
    async def get_accessible_channels(user: User, permission_type: str = 'read') -> QuerySet["Channel"]:
        permission_type = 'write_access' if 'write' in permission_type.lower() else 'read_access'

        qs = Channel.objects.none()
        if user.is_anonymous:
            return Channel.objects.filter(**{permission_type: PermissionType.ANONYMOUS})

        qs |= Channel.objects.filter(**{permission_type: PermissionType.AUTHENTICATED})
        registration = await Registration.objects.aget(user_id=user.id)

        if registration.is_admin:
            return Channel.objects.all()

        if registration.is_volunteer:
            registration = await VolunteerRegistration.objects \
                .prefetch_related('jury_in__tournament', 'organized_tournaments').aget(user_id=user.id)

            qs |= Channel.objects.filter(**{permission_type: PermissionType.VOLUNTEER})

            qs |= Channel.objects.filter(Q(tournament__in=registration.interesting_tournaments),
                                         **{permission_type: PermissionType.TOURNAMENT_MEMBER})

            qs |= Channel.objects.filter(Q(tournament__in=registration.organized_tournaments.all()),
                                         **{permission_type: PermissionType.TOURNAMENT_ORGANIZER})

            qs |= Channel.objects.filter(Q(tournament__pools__in=registration.pools_presided.all())
                                         | Q(tournament__in=registration.organized_tournaments.all()),
                                         **{permission_type: PermissionType.TOURNAMENT_JURY_PRESIDENT})

            qs |= Channel.objects.filter(Q(pool__in=registration.jury_in.all())
                                         | Q(pool__tournament__in=registration.organized_tournaments.all())
                                         | Q(pool__tournament__pools__in=registration.pools_presided.all()),
                                         **{permission_type: PermissionType.JURY_MEMBER})

            qs |= Channel.objects.filter(Q(pool__in=registration.jury_in.all())
                                         | Q(pool__tournament__in=registration.organized_tournaments.all())
                                         | Q(pool__tournament__pools__in=registration.pools_presided.all()),
                                         **{permission_type: PermissionType.POOL_MEMBER})
        else:
            registration = await ParticipantRegistration.objects \
                .prefetch_related('team__participation__pools', 'team__participation__tournament').aget(user_id=user.id)

            team = registration.team
            tournaments = []
            if team.participation.valid:
                tournaments.append(team.participation.tournament)
            if team.participation.final:
                tournaments.append(await Tournament.objects.aget(final=True))

            qs |= Channel.objects.filter(Q(tournament__in=tournaments),
                                         **{permission_type: PermissionType.TOURNAMENT_MEMBER})

            qs |= Channel.objects.filter(Q(pool__in=team.participation.pools.all()),
                                         **{permission_type: PermissionType.POOL_MEMBER})

            qs |= Channel.objects.filter(Q(team=team),
                                         **{permission_type: PermissionType.TEAM_MEMBER})

        qs |= Channel.objects.filter(invited=user)

        return qs

    class Meta:
        verbose_name = _("channel")
        verbose_name_plural = _("channels")
        ordering = ('name',)


class Message(models.Model):
    channel = models.ForeignKey(
        Channel,
        on_delete=models.CASCADE,
        verbose_name=_("channel"),
        related_name='messages',
    )

    author = models.ForeignKey(
        'auth.User',
        verbose_name=_("author"),
        on_delete=models.SET_NULL,
        null=True,
        related_name='chat_messages',
    )

    created_at = models.DateTimeField(
        verbose_name=_("created at"),
        auto_now_add=True,
    )

    updated_at = models.DateTimeField(
        verbose_name=_("updated at"),
        auto_now=True,
    )

    content = models.TextField(
        verbose_name=_("content"),
    )

    def get_author_name(self):
        registration = self.author.registration

        author_name = f"{self.author.first_name} {self.author.last_name}"
        if registration.is_volunteer:
            if registration.is_admin:
                author_name += " (CNO)"

            if self.channel.pool:
                if registration == self.channel.pool.jury_president:
                    author_name += " (P. jury)"
                elif registration in self.channel.pool.juries.all():
                    author_name += " (Juré⋅e)"
                elif registration in self.channel.pool.tournament.organizers.all():
                    author_name += " (CRO)"
                else:
                    author_name += " (Bénévole)"
            elif self.channel.tournament:
                if registration in self.channel.tournament.organizers.all():
                    author_name += " (CRO)"
                elif any([registration.id == pool.jury_president
                          for pool in self.channel.tournament.pools.all()]):
                    pools = ", ".join([pool.short_name
                                       for pool in self.channel.tournament.pools.all()
                                       if pool.jury_president == registration])
                    author_name += f" (P. jury {pools})"
                elif any([pool.juries.contains(registration)
                          for pool in self.channel.tournament.pools.all()]):
                    pools = ", ".join([pool.short_name
                                       for pool in self.channel.tournament.pools.all()
                                       if pool.juries.acontains(registration)])
                    author_name += f" (Juré⋅e {pools})"
                else:
                    author_name += " (Bénévole)"
            else:
                if registration.organized_tournaments.exists():
                    tournaments = ", ".join([tournament.name
                                             for tournament in registration.organized_tournaments.all()])
                    author_name += f" (CRO {tournaments})"
                if Pool.objects.filter(jury_president=registration).exists():
                    tournaments = Tournament.objects.filter(pools__jury_president=registration).distinct()
                    tournaments = ", ".join([tournament.name for tournament in tournaments])
                    author_name += f" (P. jury {tournaments})"
                elif registration.jury_in.exists():
                    tournaments = Tournament.objects.filter(pools__juries=registration).distinct()
                    tournaments = ", ".join([tournament.name for tournament in tournaments])
                    author_name += f" (Juré⋅e {tournaments})"
        else:
            if registration.team_id:
                team = Team.objects.get(id=registration.team_id)
                author_name += f" ({team.trigram})"
            else:
                author_name += " (sans équipe)"

        return author_name

    async def aget_author_name(self):
        return await sync_to_async(self.get_author_name)()

    class Meta:
        verbose_name = _("message")
        verbose_name_plural = _("messages")
        ordering = ('created_at',)
