""" do book related things with other users """
from django.apps import apps
from django.db import models, IntegrityError, transaction
from django.db.models import Q
from bookwyrm.settings import DOMAIN
from .base_model import BookWyrmModel
from . import fields
from .relationship import UserBlocks


class Group(BookWyrmModel):
    """A group of users"""

    name = fields.CharField(max_length=100)
    user = fields.ForeignKey("User", on_delete=models.CASCADE)
    description = fields.TextField(blank=True, null=True)
    privacy = fields.PrivacyField()

    def get_remote_id(self):
        """don't want the user to be in there in this case"""
        return f"https://{DOMAIN}/group/{self.id}"

    @classmethod
    def followers_filter(cls, queryset, viewer):
        """Override filter for "followers" privacy level to allow non-following
        group members to see the existence of group-curated lists"""

        return queryset.exclude(
            ~Q(  # user is not a group member
                Q(user__followers=viewer) | Q(user=viewer) | Q(memberships__user=viewer)
            ),
            privacy="followers",  # and the status of the group is followers only
        )

    @classmethod
    def direct_filter(cls, queryset, viewer):
        """Override filter for "direct" privacy level to allow group members
        to see the existence of groups and group lists"""

        return queryset.exclude(~Q(memberships__user=viewer), privacy="direct")


class GroupMember(models.Model):
    """Users who are members of a group"""

    created_date = models.DateTimeField(auto_now_add=True)
    updated_date = models.DateTimeField(auto_now=True)
    group = models.ForeignKey(
        "Group", on_delete=models.CASCADE, related_name="memberships"
    )
    user = models.ForeignKey(
        "User", on_delete=models.CASCADE, related_name="memberships"
    )

    class Meta:
        """Users can only have one membership per group"""

        constraints = [
            models.UniqueConstraint(fields=["group", "user"], name="unique_membership")
        ]

    def save(self, *args, **kwargs):
        """don't let a user invite someone who blocked them"""
        # blocking in either direction is a no-go
        if UserBlocks.objects.filter(
            Q(
                user_subject=self.group.user,
                user_object=self.user,
            )
            | Q(
                user_subject=self.user,
                user_object=self.group.user,
            )
        ).exists():
            raise IntegrityError()
        # accepts and requests are handled by the GroupMemberInvitation model
        super().save(*args, **kwargs)

    @classmethod
    def from_request(cls, join_request):
        """converts a join request into a member relationship"""

        # remove the invite
        join_request.delete()

        # make a group member
        return cls.objects.create(
            user=join_request.user,
            group=join_request.group,
        )

    @classmethod
    def remove(cls, owner, user):
        """remove a user from a group"""

        memberships = cls.objects.filter(group__user=owner, user=user).all()
        for member in memberships:
            member.delete()


class GroupMemberInvitation(models.Model):
    """adding a user to a group requires manual confirmation"""

    created_date = models.DateTimeField(auto_now_add=True)
    group = models.ForeignKey(
        "Group", on_delete=models.CASCADE, related_name="user_invitations"
    )
    user = models.ForeignKey(
        "User", on_delete=models.CASCADE, related_name="group_invitations"
    )

    class Meta:
        """Users can only have one outstanding invitation per group"""

        constraints = [
            models.UniqueConstraint(fields=["group", "user"], name="unique_invitation")
        ]

    def save(self, *args, **kwargs):
        """make sure the membership doesn't already exist"""
        # if there's an invitation for a membership that already exists, accept it
        # without changing the local database state
        if GroupMember.objects.filter(user=self.user, group=self.group).exists():
            self.accept()
            return

        # blocking in either direction is a no-go
        if UserBlocks.objects.filter(
            Q(
                user_subject=self.group.user,
                user_object=self.user,
            )
            | Q(
                user_subject=self.user,
                user_object=self.group.user,
            )
        ).exists():
            raise IntegrityError()

        # make an invitation
        super().save(*args, **kwargs)

        # now send the invite
        model = apps.get_model("bookwyrm.Notification", require_ready=True)
        notification_type = "INVITE"
        model.objects.create(
            user=self.user,
            related_user=self.group.user,
            related_group=self.group,
            notification_type=notification_type,
        )

    @transaction.atomic
    def accept(self):
        """turn this request into the real deal"""
        GroupMember.from_request(self)

        model = apps.get_model("bookwyrm.Notification", require_ready=True)
        # tell the group owner
        model.objects.create(
            user=self.group.user,
            related_user=self.user,
            related_group=self.group,
            notification_type="ACCEPT",
        )

        # let the other members know about it
        for membership in self.group.memberships.all():
            member = membership.user
            if member not in (self.user, self.group.user):
                model.objects.create(
                    user=member,
                    related_user=self.user,
                    related_group=self.group,
                    notification_type="JOIN",
                )

    def reject(self):
        """generate a Reject for this membership request"""

        self.delete()

""" do book related things with other users """
from django.apps import apps
from django.db import models, IntegrityError, transaction
from django.db.models import Q
from bookwyrm.settings import DOMAIN
from .base_model import BookWyrmModel
from . import fields
from .relationship import UserBlocks


class Group(BookWyrmModel):
    """A group of users"""

    name = fields.CharField(max_length=100)
    user = fields.ForeignKey("User", on_delete=models.CASCADE)
    description = fields.TextField(blank=True, null=True)
    privacy = fields.PrivacyField()

    def get_remote_id(self):
        """don't want the user to be in there in this case"""
        return f"https://{DOMAIN}/group/{self.id}"

    @classmethod
    def followers_filter(cls, queryset, viewer):
        """Override filter for "followers" privacy level to allow non-following
        group members to see the existence of group-curated lists"""

        return queryset.exclude(
            ~Q(  # user is not a group member
                Q(user__followers=viewer) | Q(user=viewer) | Q(memberships__user=viewer)
            ),
            privacy="followers",  # and the status of the group is followers only
        )

    @classmethod
    def direct_filter(cls, queryset, viewer):
        """Override filter for "direct" privacy level to allow group members
        to see the existence of groups and group lists"""

        return queryset.exclude(~Q(memberships__user=viewer), privacy="direct")


class GroupMember(models.Model):
    """Users who are members of a group"""

    created_date = models.DateTimeField(auto_now_add=True)
    updated_date = models.DateTimeField(auto_now=True)
    group = models.ForeignKey(
        "Group", on_delete=models.CASCADE, related_name="memberships"
    )
    user = models.ForeignKey(
        "User", on_delete=models.CASCADE, related_name="memberships"
    )

    class Meta:
        """Users can only have one membership per group"""

        constraints = [
            models.UniqueConstraint(fields=["group", "user"], name="unique_membership")
        ]

    def save(self, *args, **kwargs):
        """don't let a user invite someone who blocked them"""
        # blocking in either direction is a no-go
        if UserBlocks.objects.filter(
            Q(
                user_subject=self.group.user,
                user_object=self.user,
            )
            | Q(
                user_subject=self.user,
                user_object=self.group.user,
            )
        ).exists():
            raise IntegrityError()
        # accepts and requests are handled by the GroupMemberInvitation model
        super().save(*args, **kwargs)

    @classmethod
    def from_request(cls, join_request):
        """converts a join request into a member relationship"""

        # remove the invite
        join_request.delete()

        # make a group member
        return cls.objects.create(
            user=join_request.user,
            group=join_request.group,
        )

    @classmethod
    def remove(cls, owner, user):
        """remove a user from a group"""

        memberships = cls.objects.filter(group__user=owner, user=user).all()
        for member in memberships:
            member.delete()


class GroupMemberInvitation(models.Model):
    """adding a user to a group requires manual confirmation"""

    created_date = models.DateTimeField(auto_now_add=True)
    group = models.ForeignKey(
        "Group", on_delete=models.CASCADE, related_name="user_invitations"
    )
    user = models.ForeignKey(
        "User", on_delete=models.CASCADE, related_name="group_invitations"
    )

    class Meta:
        """Users can only have one outstanding invitation per group"""

        constraints = [
            models.UniqueConstraint(fields=["group", "user"], name="unique_invitation")
        ]

    def save(self, *args, **kwargs):
        """make sure the membership doesn't already exist"""
        # if there's an invitation for a membership that already exists, accept it
        # without changing the local database state
        if GroupMember.objects.filter(user=self.user, group=self.group).exists():
            self.accept()
            return

        # blocking in either direction is a no-go
        if UserBlocks.objects.filter(
            Q(
                user_subject=self.group.user,
                user_object=self.user,
            )
            | Q(
                user_subject=self.user,
                user_object=self.group.user,
            )
        ).exists():
            raise IntegrityError()

        # make an invitation
        super().save(*args, **kwargs)

        # now send the invite
        model = apps.get_model("bookwyrm.Notification", require_ready=True)
        notification_type = "INVITE"
        model.objects.create(
            user=self.user,
            related_user=self.group.user,
            related_group=self.group,
            notification_type=notification_type,
        )

    @transaction.atomic
    def accept(self):
        """turn this request into the real deal"""
        GroupMember.from_request(self)

        model = apps.get_model("bookwyrm.Notification", require_ready=True)
        # tell the group owner
        model.objects.create(
            user=self.group.user,
            related_user=self.user,
            related_group=self.group,
            notification_type="ACCEPT",
        )

        # let the other members know about it
        for membership in self.group.memberships.all():
            member = membership.user
            if member not in (self.user, self.group.user):
                model.objects.create(
                    user=member,
                    related_user=self.user,
                    related_group=self.group,
                    notification_type="JOIN",
                )

    def reject(self):
        """generate a Reject for this membership request"""

        self.delete()

do book related things with other users A group of users don't want the user to be in there in this case Override filter for "followers" privacy level to allow non-following         group members to see the existence of group-curated lists # user is not a group member # and the status of the group is followers only Override filter for "direct" privacy level to allow group members         to see the existence of groups and group lists Users who are members of a group Users can only have one membership per group don't let a user invite someone who blocked them # blocking in either direction is a no-go # accepts and requests are handled by the GroupMemberInvitation model converts a join request into a member relationship # remove the invite # make a group member remove a user from a group adding a user to a group requires manual confirmation Users can only have one outstanding invitation per group make sure the membership doesn't already exist # if there's an invitation for a membership that already exists, accept it # without changing the local database state # blocking in either direction is a no-go # make an invitation # now send the invite turn this request into the real deal # tell the group owner # let the other members know about it generate a Reject for this membership request