from datetime import datetime
from unittest.mock import patch
from uuid import uuid4

from django.test import TestCase
from django.utils import timezone

import pytz

from newsreader.news.collection.choices import RuleTypeChoices
from newsreader.news.collection.exceptions import (
    StreamDeniedException,
    StreamForbiddenException,
    StreamNotFoundException,
    StreamTimeOutException,
)
from newsreader.news.collection.reddit import RedditCollector
from newsreader.news.collection.tests.factories import SubredditFactory
from newsreader.news.collection.tests.reddit.collector.mocks import (
    empty_mock,
    simple_mock_1,
    simple_mock_2,
)
from newsreader.news.core.models import Post


class RedditCollectorTestCase(TestCase):
    def setUp(self):
        self.maxDiff = None

        self.patched_get = patch("newsreader.news.collection.reddit.fetch")
        self.mocked_fetch = self.patched_get.start()

        self.patched_parse = patch(
            "newsreader.news.collection.reddit.RedditStream.parse"
        )
        self.mocked_parse = self.patched_parse.start()

    def tearDown(self):
        patch.stopall()

    def test_simple_batch(self):
        self.mocked_parse.side_effect = (simple_mock_1, simple_mock_2)

        rules = (
            (subreddit,)
            for subreddit in SubredditFactory.create_batch(
                user__reddit_access_token=str(uuid4()),
                user__reddit_refresh_token=str(uuid4()),
                enabled=True,
                size=2,
            )
        )

        collector = RedditCollector()
        collector.collect(rules=rules)

        self.assertCountEqual(
            Post.objects.values_list("remote_identifier", flat=True),
            (
                "hm6byg",
                "hpkhgj",
                "hph00n",
                "hp9mlw",
                "hpjn8x",
                "gdfaip",
                "hmd2ez",
                "hpr28u",
                "hpps6f",
                "hp7uqe",
            ),
        )

        for subreddit in rules:
            with self.subTest(subreddit=subreddit):
                self.assertEquals(subreddit.succeeded, True)
                self.assertEquals(subreddit.last_run, timezone.now())
                self.assertEquals(subreddit.error, None)

        post = Post.objects.get(
            remote_identifier="hph00n", rule__type=RuleTypeChoices.subreddit
        )

        self.assertEquals(
            post.publication_date, pytz.utc.localize(datetime(2020, 7, 11, 22, 23, 24))
        )

        self.assertEquals(post.author, "HannahB888")
        self.assertEquals(
            post.title, "Drake Interplanetary Smartkey thing that I made!"
        )
        self.assertEquals(
            post.url,
            "https://www.reddit.com/r/starcitizen/comments/hph00n/drake_interplanetary_smartkey_thing_that_i_made/",
        )

        post = Post.objects.get(
            remote_identifier="hpr28u", rule__type=RuleTypeChoices.subreddit
        )

        self.assertEquals(
            post.publication_date, pytz.utc.localize(datetime(2020, 7, 12, 10, 29, 10))
        )

        self.assertEquals(post.author, "Sebaron")
        self.assertEquals(
            post.title,
            "I am a medical student, and I recently programmed an open-source eye-tracker for brain research",
        )
        self.assertEquals(
            post.url,
            "https://www.reddit.com/r/Python/comments/hpr28u/i_am_a_medical_student_and_i_recently_programmed/",
        )

    def test_empty_batch(self):
        self.mocked_parse.side_effect = (empty_mock, empty_mock)

        rules = (
            (subreddit,)
            for subreddit in SubredditFactory.create_batch(
                user__reddit_access_token=str(uuid4()),
                user__reddit_refresh_token=str(uuid4()),
                enabled=True,
                size=2,
            )
        )

        collector = RedditCollector()
        collector.collect(rules=rules)

        self.assertEquals(Post.objects.count(), 0)

        for subreddit in rules:
            with self.subTest(subreddit=subreddit):
                self.assertEquals(subreddit.succeeded, True)
                self.assertEquals(subreddit.last_run, timezone.now())
                self.assertEquals(subreddit.error, None)

    def test_not_found(self):
        self.mocked_fetch.side_effect = StreamNotFoundException

        rule = SubredditFactory(
            user__reddit_access_token=str(uuid4()),
            user__reddit_refresh_token=str(uuid4()),
            enabled=True,
        )

        collector = RedditCollector()
        collector.collect(rules=((rule,),))

        self.assertEquals(Post.objects.count(), 0)
        self.assertEquals(rule.succeeded, False)
        self.assertEquals(rule.error, "Stream not found")

    @patch("newsreader.news.collection.reddit.RedditTokenTask")
    def test_denied(self, mocked_task):
        self.mocked_fetch.side_effect = StreamDeniedException

        rule = SubredditFactory(
            user__reddit_access_token=str(uuid4()),
            user__reddit_refresh_token=str(uuid4()),
            enabled=True,
        )

        collector = RedditCollector()
        collector.collect(rules=((rule,),))

        self.assertEquals(Post.objects.count(), 0)
        self.assertEquals(rule.succeeded, False)
        self.assertEquals(rule.error, "Stream does not have sufficient permissions")

        mocked_task.delay.assert_called_once_with(rule.user.pk)

    def test_forbidden(self):
        self.mocked_fetch.side_effect = StreamForbiddenException

        rule = SubredditFactory(
            user__reddit_access_token=str(uuid4()),
            user__reddit_refresh_token=str(uuid4()),
            enabled=True,
        )

        collector = RedditCollector()
        collector.collect(rules=((rule,),))

        self.assertEquals(Post.objects.count(), 0)
        self.assertEquals(rule.succeeded, False)
        self.assertEquals(rule.error, "Stream forbidden")

    def test_timed_out(self):
        self.mocked_fetch.side_effect = StreamTimeOutException

        rule = SubredditFactory(
            user__reddit_access_token=str(uuid4()),
            user__reddit_refresh_token=str(uuid4()),
            enabled=True,
        )

        collector = RedditCollector()
        collector.collect(rules=((rule,),))

        self.assertEquals(Post.objects.count(), 0)
        self.assertEquals(rule.succeeded, False)
        self.assertEquals(rule.error, "Stream timed out")
