""" testing user follow suggestions """
from collections import namedtuple
from unittest.mock import patch

from django.db.models import Q
from django.test import TestCase

from bookwyrm import models
from bookwyrm.suggested_users import suggested_users, get_annotated_users


@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
@patch("bookwyrm.activitystreams.add_status_task.delay")
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
@patch("bookwyrm.lists_stream.populate_lists_task.delay")
@patch("bookwyrm.activitystreams.add_book_statuses_task.delay")
@patch("bookwyrm.suggested_users.rerank_user_task.delay")
@patch("bookwyrm.suggested_users.remove_user_task.delay")
class SuggestedUsers(TestCase):
    """using redis to build activity streams"""

    def setUp(self):
        """use a test csv"""
        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", "mouse@mouse.mouse", "password", local=True, localname="mouse"
            )

    def test_get_rank(self, *_):
        """a float that reflects both the mutuals count and shared books"""
        Mock = namedtuple("AnnotatedUserMock", ("mutuals", "shared_books"))
        annotated_user_mock = Mock(3, 27)
        rank = suggested_users.get_rank(annotated_user_mock)
        self.assertEqual(rank, 3)  # 3.9642857142857144)

    def test_store_id_from_obj(self, *_):
        """redis key generation by user obj"""
        self.assertEqual(
            suggested_users.store_id(self.local_user),
            f"{self.local_user.id}-suggestions",
        )

    def test_store_id_from_id(self, *_):
        """redis key generation by user id"""
        self.assertEqual(
            suggested_users.store_id(self.local_user.id),
            f"{self.local_user.id}-suggestions",
        )

    def test_get_counts_from_rank(self, *_):
        """reverse the rank computation to get the mutuals and shared books counts"""
        counts = suggested_users.get_counts_from_rank(3.9642857142857144)
        self.assertEqual(counts["mutuals"], 3)
        # self.assertEqual(counts["shared_books"], 27)

    def test_get_objects_for_store(self, *_):
        """list of people to follow for a given user"""

        mutual_user = models.User.objects.create_user(
            "rat", "rat@local.rat", "password", local=True, localname="rat"
        )
        suggestable_user = models.User.objects.create_user(
            "nutria",
            "nutria@nutria.nutria",
            "password",
            local=True,
            localname="nutria",
            discoverable=True,
        )

        # you follow rat
        mutual_user.followers.add(self.local_user)
        # rat follows the suggested user
        suggestable_user.followers.add(mutual_user)

        results = suggested_users.get_objects_for_store(
            f"{self.local_user.id}-suggestions"
        )
        self.assertEqual(results.count(), 1)
        match = results.first()
        self.assertEqual(match.id, suggestable_user.id)
        self.assertEqual(match.mutuals, 1)

    def test_get_stores_for_object(self, *_):
        """possible follows"""
        mutual_user = models.User.objects.create_user(
            "rat", "rat@local.rat", "password", local=True, localname="rat"
        )
        suggestable_user = models.User.objects.create_user(
            "nutria",
            "nutria@nutria.nutria",
            "password",
            local=True,
            localname="nutria",
            discoverable=True,
        )

        # you follow rat
        mutual_user.followers.add(self.local_user)
        # rat follows the suggested user
        suggestable_user.followers.add(mutual_user)

        results = suggested_users.get_stores_for_object(self.local_user)
        self.assertEqual(len(results), 1)
        self.assertEqual(results[0], f"{suggestable_user.id}-suggestions")

    def test_get_users_for_object(self, *_):
        """given a user, who might want to follow them"""
        mutual_user = models.User.objects.create_user(
            "rat", "rat@local.rat", "password", local=True, localname="rat"
        )
        suggestable_user = models.User.objects.create_user(
            "nutria",
            "nutria@nutria.nutria",
            "password",
            local=True,
            localname="nutria",
            discoverable=True,
        )
        # you follow rat
        mutual_user.followers.add(self.local_user)
        # rat follows the suggested user
        suggestable_user.followers.add(mutual_user)

        results = suggested_users.get_users_for_object(self.local_user)
        self.assertEqual(len(results), 1)
        self.assertEqual(results[0], suggestable_user)

    def test_rerank_user_suggestions(self, *_):
        """does it call the populate store function correctly"""
        with patch(
            "bookwyrm.suggested_users.SuggestedUsers.populate_store"
        ) as store_mock:
            suggested_users.rerank_user_suggestions(self.local_user)
        args = store_mock.call_args[0]
        self.assertEqual(args[0], f"{self.local_user.id}-suggestions")

    def test_get_suggestions(self, *_):
        """load from store"""
        with patch("bookwyrm.suggested_users.SuggestedUsers.get_store") as mock:
            mock.return_value = [(self.local_user.id, 7.9)]
            results = suggested_users.get_suggestions(self.local_user)
        self.assertEqual(results[0], self.local_user)
        self.assertEqual(results[0].mutuals, 7)

    def test_get_annotated_users(self, *_):
        """list of people you might know"""
        user_1 = models.User.objects.create_user(
            "nutria@local.com",
            "nutria@nutria.com",
            "nutriaword",
            local=True,
            localname="nutria",
            discoverable=True,
        )
        user_2 = models.User.objects.create_user(
            "fish@local.com",
            "fish@fish.com",
            "fishword",
            local=True,
            localname="fish",
        )
        work = models.Work.objects.create(title="Test Work")
        book = models.Edition.objects.create(
            title="Test Book",
            remote_id="https://example.com/book/1",
            parent_work=work,
        )
        with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
            # 1 shared follow
            self.local_user.following.add(user_2)
            user_1.followers.add(user_2)

            # 1 shared book
            models.ShelfBook.objects.create(
                user=self.local_user,
                book=book,
                shelf=self.local_user.shelf_set.first(),
            )
            models.ShelfBook.objects.create(
                user=user_1, book=book, shelf=user_1.shelf_set.first()
            )

        result = get_annotated_users(self.local_user)
        self.assertEqual(result.count(), 1)
        self.assertTrue(user_1 in result)
        self.assertFalse(user_2 in result)

        user_1_annotated = result.get(id=user_1.id)
        self.assertEqual(user_1_annotated.mutuals, 1)
        # self.assertEqual(user_1_annotated.shared_books, 1)

    def test_get_annotated_users_counts(self, *_):
        """correct counting for multiple shared attributed"""
        user_1 = models.User.objects.create_user(
            "nutria@local.com",
            "nutria@nutria.com",
            "nutriaword",
            local=True,
            localname="nutria",
            discoverable=True,
        )
        for i in range(3):
            user = models.User.objects.create_user(
                f"{i}@local.com",
                f"{i}@nutria.com",
                "password",
                local=True,
                localname=i,
            )
            user.following.add(user_1)
            user.followers.add(self.local_user)

        with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
            for i in range(3):
                book = models.Edition.objects.create(
                    title=i,
                    parent_work=models.Work.objects.create(title=i),
                )
                models.ShelfBook.objects.create(
                    user=self.local_user,
                    book=book,
                    shelf=self.local_user.shelf_set.first(),
                )
                models.ShelfBook.objects.create(
                    user=user_1, book=book, shelf=user_1.shelf_set.first()
                )

        result = get_annotated_users(
            self.local_user,
            ~Q(id=self.local_user.id),
            ~Q(followers=self.local_user),
        )
        user_1_annotated = result.get(id=user_1.id)
        self.assertEqual(user_1_annotated.mutuals, 3)
