import json

from datetime import datetime, timezone

from django.test import TestCase
from django.urls import reverse

from newsreader.accounts.tests.factories import UserFactory
from newsreader.news.collection.tests.factories import FeedFactory
from newsreader.news.core.tests.factories import CategoryFactory, FeedPostFactory


class CategoryListViewTestCase(TestCase):
    def setUp(self):
        self.user = UserFactory(password="test")
        self.client.force_login(self.user)

    def test_simple(self):
        CategoryFactory.create_batch(size=3, user=self.user)

        response = self.client.get(reverse("api:news:core:categories-list"))
        data = response.json()

        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(data), 3)

    def test_ordering(self):
        categories = [
            CategoryFactory(
                created=datetime(2019, 5, 20, 16, 7, 37, tzinfo=timezone.utc),
                user=self.user,
            ),
            CategoryFactory(
                created=datetime(2019, 7, 20, 18, 7, 37, tzinfo=timezone.utc),
                user=self.user,
            ),
            CategoryFactory(
                created=datetime(2019, 7, 20, 16, 7, 37, tzinfo=timezone.utc),
                user=self.user,
            ),
        ]

        response = self.client.get(reverse("api:news:core:categories-list"))
        data = response.json()

        self.assertEqual(response.status_code, 200)

        self.assertEqual(data[0]["id"], categories[1].pk)
        self.assertEqual(data[1]["id"], categories[2].pk)
        self.assertEqual(data[2]["id"], categories[0].pk)

    def test_empty(self):
        response = self.client.get(reverse("api:news:core:categories-list"))
        data = response.json()

        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(data), 0)

    def test_post(self):
        data = {"name": "Tech"}

        response = self.client.post(
            reverse("api:news:core:categories-list"),
            data=json.dumps(data),
            content_type="application/json",
        )
        response_data = response.json()

        self.assertEqual(response.status_code, 405)
        self.assertEqual(response_data["detail"], 'Method "POST" not allowed.')

    def test_patch(self):
        response = self.client.patch(reverse("api:news:core:categories-list"))
        data = response.json()

        self.assertEqual(response.status_code, 405)
        self.assertEqual(data["detail"], 'Method "PATCH" not allowed.')

    def test_put(self):
        response = self.client.put(reverse("api:news:core:categories-list"))
        data = response.json()

        self.assertEqual(response.status_code, 405)
        self.assertEqual(data["detail"], 'Method "PUT" not allowed.')

    def test_delete(self):
        response = self.client.delete(reverse("api:news:core:categories-list"))
        data = response.json()

        self.assertEqual(response.status_code, 405)
        self.assertEqual(data["detail"], 'Method "DELETE" not allowed.')

    def test_categories_with_unauthenticated_user(self):
        self.client.logout()

        CategoryFactory.create_batch(size=3, user=self.user)

        response = self.client.get(reverse("api:news:core:categories-list"))

        self.assertEqual(response.status_code, 403)

    def test_categories_with_unauthorized_user(self):
        other_user = UserFactory()
        CategoryFactory.create_batch(size=3, user=other_user)

        response = self.client.get(reverse("api:news:core:categories-list"))
        data = response.json()

        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(data), 0)


class NestedCategoryListViewTestCase(TestCase):
    def setUp(self):
        self.user = UserFactory(password="test")
        self.client.force_login(self.user)

    def test_simple(self):
        category = CategoryFactory.create(user=self.user)
        FeedFactory.create_batch(size=5, category=category)

        response = self.client.get(
            reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk})
        )
        data = response.json()

        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(data), 5)

        self.assertTrue("id" in data[0])
        self.assertTrue("name" in data[0])
        self.assertTrue("category" in data[0])
        self.assertTrue("url" in data[0])
        self.assertTrue("favicon" in data[0])

    def test_empty(self):
        category = CategoryFactory.create(user=self.user)

        response = self.client.get(
            reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk})
        )
        data = response.json()

        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(data), 0)
        self.assertEqual(data, [])

    def test_not_known(self):
        response = self.client.get(
            reverse("api:news:core:categories-nested-rules", kwargs={"pk": 100})
        )

        self.assertEqual(response.status_code, 404)

    def test_post(self):
        response = self.client.post(
            reverse("api:news:core:categories-nested-rules", kwargs={"pk": 100}),
            data=json.dumps({}),
            content_type="application/json",
        )
        data = response.json()

        self.assertEqual(response.status_code, 405)
        self.assertEqual(data["detail"], 'Method "POST" not allowed.')

    def test_patch(self):
        category = CategoryFactory.create(user=self.user)

        response = self.client.patch(
            reverse(
                "api:news:core:categories-nested-rules", kwargs={"pk": category.pk}
            ),
            data=json.dumps({"name": "test"}),
            content_type="application/json",
        )
        data = response.json()

        self.assertEqual(response.status_code, 405)
        self.assertEqual(data["detail"], 'Method "PATCH" not allowed.')

    def test_put(self):
        category = CategoryFactory.create(user=self.user)

        response = self.client.put(
            reverse(
                "api:news:core:categories-nested-rules", kwargs={"pk": category.pk}
            ),
            data=json.dumps({"name": "test"}),
            content_type="application/json",
        )
        data = response.json()

        self.assertEqual(response.status_code, 405)
        self.assertEqual(data["detail"], 'Method "PUT" not allowed.')

    def test_delete(self):
        category = CategoryFactory.create(user=self.user)

        response = self.client.delete(
            reverse(
                "api:news:core:categories-nested-rules", kwargs={"pk": category.pk}
            ),
            content_type="application/json",
        )
        data = response.json()

        self.assertEqual(response.status_code, 405)
        self.assertEqual(data["detail"], 'Method "DELETE" not allowed.')

    def test_with_unauthenticated_user(self):
        self.client.logout()

        category = CategoryFactory.create(user=self.user)
        FeedFactory.create_batch(size=5, category=category)

        response = self.client.get(
            reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk})
        )

        self.assertEqual(response.status_code, 403)

    def test_with_unauthorized_user(self):
        other_user = UserFactory.create()

        category = CategoryFactory.create(user=other_user)
        FeedFactory.create_batch(size=5, category=category)

        response = self.client.get(
            reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk})
        )

        self.assertEqual(response.status_code, 403)

    def test_ordering(self):
        category = CategoryFactory.create(user=self.user)
        rules = [
            FeedFactory.create(category=category, name="Durp"),
            FeedFactory.create(category=category, name="Slurp"),
            FeedFactory.create(category=category, name="Burp"),
        ]

        response = self.client.get(
            reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk})
        )
        data = response.json()

        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(data), 3)

        self.assertEqual(data[0]["id"], rules[2].pk)
        self.assertEqual(data[1]["id"], rules[0].pk)
        self.assertEqual(data[2]["id"], rules[1].pk)

    def test_only_rules_from_category_are_returned(self):
        other_category = CategoryFactory(user=self.user)
        FeedFactory.create_batch(size=5, category=other_category)

        category = CategoryFactory.create(user=self.user)
        rules = [
            FeedFactory.create(category=category, name="Durp"),
            FeedFactory.create(category=category, name="Slurp"),
            FeedFactory.create(category=category, name="Burp"),
        ]

        response = self.client.get(
            reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk})
        )
        data = response.json()

        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(data), 3)

        self.assertEqual(data[0]["id"], rules[2].pk)
        self.assertEqual(data[1]["id"], rules[0].pk)
        self.assertEqual(data[2]["id"], rules[1].pk)


class NestedCategoryPostView(TestCase):
    def setUp(self):
        self.user = UserFactory(password="test")
        self.client.force_login(self.user)

    def test_simple(self):
        category = CategoryFactory.create(user=self.user)
        rules = FeedFactory.create_batch(size=5, category=category, user=self.user)

        for rule in rules:
            FeedPostFactory.create_batch(size=5, rule=rule)

        response = self.client.get(
            reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk})
        )
        data = response.json()

        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(data["results"]), 25)

        self.assertTrue("id" in data["results"][0])
        self.assertTrue("title" in data["results"][0])
        self.assertTrue("body" in data["results"][0])
        self.assertTrue("rule" in data["results"][0])
        self.assertTrue("url" in data["results"][0])

    def test_no_rules(self):
        category = CategoryFactory.create(user=self.user)

        response = self.client.get(
            reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk})
        )
        data = response.json()
        posts = data["results"]

        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(data["results"]), 0)
        self.assertEqual(posts, [])

    def test_no_posts(self):
        category = CategoryFactory.create(user=self.user)
        FeedFactory.create_batch(size=5, user=self.user, category=category)

        response = self.client.get(
            reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk})
        )
        data = response.json()
        posts = data["results"]

        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(data["results"]), 0)
        self.assertEqual(posts, [])

    def test_not_known(self):
        response = self.client.get(
            reverse("api:news:core:categories-nested-posts", kwargs={"pk": 100})
        )

        self.assertEqual(response.status_code, 404)

    def test_post(self):
        response = self.client.post(
            reverse("api:news:core:categories-nested-posts", kwargs={"pk": 100}),
            data=json.dumps({}),
            content_type="application/json",
        )
        data = response.json()

        self.assertEqual(response.status_code, 405)
        self.assertEqual(data["detail"], 'Method "POST" not allowed.')

    def test_patch(self):
        category = CategoryFactory.create(user=self.user)

        response = self.client.patch(
            reverse(
                "api:news:core:categories-nested-posts", kwargs={"pk": category.pk}
            ),
            data=json.dumps({}),
            content_type="application/json",
        )
        data = response.json()

        self.assertEqual(response.status_code, 405)
        self.assertEqual(data["detail"], 'Method "PATCH" not allowed.')

    def test_put(self):
        category = CategoryFactory.create(user=self.user)

        response = self.client.put(
            reverse(
                "api:news:core:categories-nested-posts", kwargs={"pk": category.pk}
            ),
            data=json.dumps({}),
            content_type="application/json",
        )
        data = response.json()

        self.assertEqual(response.status_code, 405)
        self.assertEqual(data["detail"], 'Method "PUT" not allowed.')

    def test_delete(self):
        category = CategoryFactory.create(user=self.user)

        response = self.client.delete(
            reverse(
                "api:news:core:categories-nested-posts", kwargs={"pk": category.pk}
            ),
            content_type="application/json",
        )
        data = response.json()

        self.assertEqual(response.status_code, 405)
        self.assertEqual(data["detail"], 'Method "DELETE" not allowed.')

    def test_with_unauthenticated_user(self):
        self.client.logout()

        category = CategoryFactory.create(user=self.user)

        response = self.client.get(
            reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk})
        )

        self.assertEqual(response.status_code, 403)

    def test_with_unauthorized_user(self):
        other_user = UserFactory.create()
        category = CategoryFactory.create(user=other_user)

        response = self.client.get(
            reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk})
        )

        self.assertEqual(response.status_code, 404)

    def test_ordering(self):
        category = CategoryFactory.create(user=self.user)

        bbc_rule = FeedFactory.create(name="BBC", category=category, user=self.user)
        guardian_rule = FeedFactory.create(
            name="The Guardian", category=category, user=self.user
        )
        reuters_rule = FeedFactory.create(
            name="Reuters", category=category, user=self.user
        )

        reuters_rule = [
            FeedPostFactory.create(
                title="Second Reuters post",
                rule=reuters_rule,
                publication_date=datetime(2019, 5, 21, 15, tzinfo=timezone.utc),
            ),
            FeedPostFactory.create(
                title="First Reuters post",
                rule=reuters_rule,
                publication_date=datetime(2019, 5, 20, 12, tzinfo=timezone.utc),
            ),
        ]

        FeedPostFactory.create(
            title="Second Guardian post",
            rule=guardian_rule,
            publication_date=datetime(2019, 5, 21, 14, tzinfo=timezone.utc),
        )

        FeedPostFactory.create(
            title="First Guardian post",
            rule=guardian_rule,
            publication_date=datetime(2019, 5, 20, 11, tzinfo=timezone.utc),
        )

        FeedPostFactory.create(
            title="Second BBC post",
            rule=bbc_rule,
            publication_date=datetime(2019, 5, 21, 16, tzinfo=timezone.utc),
        )

        FeedPostFactory.create(
            title="First BBC post",
            rule=bbc_rule,
            publication_date=datetime(2019, 5, 20, 13, tzinfo=timezone.utc),
        )

        response = self.client.get(
            reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk})
        )
        data = response.json()
        posts = data["results"]

        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(data["results"]), 6)

        self.assertEqual(posts[0]["title"], "Second BBC post")
        self.assertEqual(posts[1]["title"], "Second Reuters post")
        self.assertEqual(posts[2]["title"], "Second Guardian post")

        self.assertEqual(posts[3]["title"], "First BBC post")
        self.assertEqual(posts[4]["title"], "First Reuters post")
        self.assertEqual(posts[5]["title"], "First Guardian post")

    def test_only_posts_from_category_are_returned(self):
        category = CategoryFactory.create(user=self.user)
        CategoryFactory.create(user=self.user)

        guardian_rule = FeedFactory.create(
            name="BBC", category=category, user=self.user
        )
        other_rule = FeedFactory.create(name="The Guardian", user=self.user)

        FeedPostFactory.create(rule=guardian_rule)
        FeedPostFactory.create(rule=guardian_rule)

        FeedPostFactory.create(rule=other_rule)
        FeedPostFactory.create(rule=other_rule)

        response = self.client.get(
            reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk})
        )
        data = response.json()
        posts = data["results"]

        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(data["results"]), 2)

        self.assertEqual(posts[0]["rule"]["id"], guardian_rule.pk)
        self.assertEqual(posts[1]["rule"]["id"], guardian_rule.pk)

    def test_posts(self):
        category = CategoryFactory.create(user=self.user)
        rule = FeedFactory(category=category, user=self.user)

        FeedPostFactory.create_batch(size=10, rule=rule, read=False)
        FeedPostFactory.create_batch(size=10, rule=rule, read=True)

        response = self.client.get(
            reverse(
                "api:news:core:categories-nested-posts", kwargs={"pk": category.pk}
            ),
        )

        data = response.json()
        posts = data["results"]

        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(data["results"]), 10)

        for post in posts:
            self.assertEqual(post["read"], False)
