""" tests incoming activities"""
import json
from unittest.mock import patch

from django.test import TestCase

from bookwyrm import models, views


# pylint: disable=too-many-public-methods
class InboxRelationships(TestCase):
    """inbox tests"""

    @classmethod
    def setUpTestData(self):  # pylint: disable=bad-classmethod-argument
        """basic user and book data"""
        with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
            "bookwyrm.activitystreams.populate_stream_task.delay"
        ), patch("bookwyrm.lists_stream.populate_lists_task.delay"):
            self.local_user = models.User.objects.create_user(
                "mouse@example.com",
                "mouse@mouse.com",
                "mouseword",
                local=True,
                localname="mouse",
            )
        self.local_user.remote_id = "https://example.com/user/mouse"
        self.local_user.save(broadcast=False, update_fields=["remote_id"])
        with patch("bookwyrm.models.user.set_remote_server.delay"):
            self.remote_user = models.User.objects.create_user(
                "rat",
                "rat@rat.com",
                "ratword",
                local=False,
                remote_id="https://example.com/users/rat",
                inbox="https://example.com/users/rat/inbox",
                outbox="https://example.com/users/rat/outbox",
            )

        models.SiteSettings.objects.create()

    def test_follow(self):
        """remote user wants to follow local user"""
        activity = {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://example.com/users/rat/follows/123",
            "type": "Follow",
            "actor": "https://example.com/users/rat",
            "object": "https://example.com/user/mouse",
        }

        self.assertFalse(models.UserFollowRequest.objects.exists())
        with patch(
            "bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
        ) as mock:
            views.inbox.activity_task(activity)
            self.assertEqual(mock.call_count, 1)
            response_activity = json.loads(mock.call_args[1]["args"][1])
            self.assertEqual(response_activity["type"], "Accept")

        # notification created
        notification = models.Notification.objects.get()
        self.assertEqual(notification.user, self.local_user)
        self.assertEqual(notification.notification_type, "FOLLOW")

        # the request should have been deleted
        self.assertFalse(models.UserFollowRequest.objects.exists())

        # the follow relationship should exist
        follow = models.UserFollows.objects.get(user_object=self.local_user)
        self.assertEqual(follow.user_subject, self.remote_user)

    def test_follow_duplicate(self):
        """remote user wants to follow local user twice"""
        activity = {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://example.com/users/rat/follows/123",
            "type": "Follow",
            "actor": "https://example.com/users/rat",
            "object": "https://example.com/user/mouse",
        }

        with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
            views.inbox.activity_task(activity)

        # the follow relationship should exist
        follow = models.UserFollows.objects.get(user_object=self.local_user)
        self.assertEqual(follow.user_subject, self.remote_user)

        with patch(
            "bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
        ) as mock:
            views.inbox.activity_task(activity)
            self.assertEqual(mock.call_count, 1)
            response_activity = json.loads(mock.call_args[1]["args"][1])
            self.assertEqual(response_activity["type"], "Accept")

        # the follow relationship should STILL exist
        follow = models.UserFollows.objects.get(user_object=self.local_user)
        self.assertEqual(follow.user_subject, self.remote_user)

    def test_follow_manually_approved(self):
        """needs approval before following"""
        activity = {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://example.com/users/rat/follows/123",
            "type": "Follow",
            "actor": "https://example.com/users/rat",
            "object": "https://example.com/user/mouse",
        }

        self.local_user.manually_approves_followers = True
        self.local_user.save(
            broadcast=False, update_fields=["manually_approves_followers"]
        )

        with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
            views.inbox.activity_task(activity)

        # notification created
        notification = models.Notification.objects.get()
        self.assertEqual(notification.user, self.local_user)
        self.assertEqual(notification.notification_type, "FOLLOW_REQUEST")

        # the request should exist
        request = models.UserFollowRequest.objects.get()
        self.assertEqual(request.user_subject, self.remote_user)
        self.assertEqual(request.user_object, self.local_user)

        # the follow relationship should not exist
        follow = models.UserFollows.objects.all()
        self.assertEqual(list(follow), [])

    def test_undo_follow_request(self):
        """the requester cancels a follow request"""
        self.local_user.manually_approves_followers = True
        self.local_user.save(
            broadcast=False, update_fields=["manually_approves_followers"]
        )
        with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
            request = models.UserFollowRequest.objects.create(
                user_subject=self.remote_user, user_object=self.local_user
            )
        self.assertTrue(self.local_user.follower_requests.exists())

        activity = {
            "type": "Undo",
            "id": "bleh",
            "to": ["https://www.w3.org/ns/activitystreams#Public"],
            "cc": ["https://example.com/user/mouse/followers"],
            "actor": self.remote_user.remote_id,
            "@context": "https://www.w3.org/ns/activitystreams",
            "object": {
                "@context": "https://www.w3.org/ns/activitystreams",
                "id": request.remote_id,
                "type": "Follow",
                "actor": "https://example.com/users/rat",
                "object": "https://example.com/user/mouse",
            },
        }

        views.inbox.activity_task(activity)

        self.assertFalse(self.local_user.follower_requests.exists())

    def test_unfollow(self):
        """remove a relationship"""
        with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
            rel = models.UserFollows.objects.create(
                user_subject=self.remote_user, user_object=self.local_user
            )
        activity = {
            "type": "Undo",
            "id": "bleh",
            "to": ["https://www.w3.org/ns/activitystreams#Public"],
            "cc": ["https://example.com/user/mouse/followers"],
            "actor": self.remote_user.remote_id,
            "@context": "https://www.w3.org/ns/activitystreams",
            "object": {
                "id": rel.remote_id,
                "type": "Follow",
                "actor": "https://example.com/users/rat",
                "object": "https://example.com/user/mouse",
            },
        }
        self.assertEqual(self.remote_user, self.local_user.followers.first())

        views.inbox.activity_task(activity)
        self.assertIsNone(self.local_user.followers.first())

    @patch("bookwyrm.activitystreams.add_user_statuses_task.delay")
    @patch("bookwyrm.lists_stream.add_user_lists_task.delay")
    def test_follow_accept(self, *_):
        """a remote user approved a follow request from local"""
        with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
            rel = models.UserFollowRequest.objects.create(
                user_subject=self.local_user, user_object=self.remote_user
            )
        activity = {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://example.com/users/rat/follows/123#accepts",
            "type": "Accept",
            "actor": "https://example.com/users/rat",
            "object": {
                "id": rel.remote_id,
                "type": "Follow",
                "actor": "https://example.com/user/mouse",
                "object": "https://example.com/users/rat",
            },
        }

        self.assertEqual(models.UserFollowRequest.objects.count(), 1)

        views.inbox.activity_task(activity)

        # request should be deleted
        self.assertEqual(models.UserFollowRequest.objects.count(), 0)

        # relationship should be created
        follows = self.remote_user.followers
        self.assertEqual(follows.count(), 1)
        self.assertEqual(follows.first(), self.local_user)

    def test_follow_reject(self):
        """turn down a follow request"""
        with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
            rel = models.UserFollowRequest.objects.create(
                user_subject=self.local_user, user_object=self.remote_user
            )
        activity = {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://example.com/users/rat/follows/123#accepts",
            "type": "Reject",
            "actor": "https://example.com/users/rat",
            "object": {
                "id": rel.remote_id,
                "type": "Follow",
                "actor": "https://example.com/user/mouse",
                "object": "https://example.com/users/rat",
            },
        }

        self.assertEqual(models.UserFollowRequest.objects.count(), 1)

        views.inbox.activity_task(activity)

        # request should be deleted
        self.assertFalse(models.UserFollowRequest.objects.exists())
        self.assertFalse(self.remote_user.followers.exists())
