from datetime import datetime, timezone
from time import struct_time
from unittest.mock import Mock, patch

from django.test import TestCase

from freezegun import freeze_time

from newsreader.news.collection.exceptions import (
    StreamDeniedException,
    StreamForbiddenException,
    StreamNotFoundException,
    StreamTimeOutException,
)
from newsreader.news.collection.feed import FeedCollector
from newsreader.news.collection.tests.factories import FeedFactory
from newsreader.news.collection.utils import build_publication_date
from newsreader.news.core.models import Post
from newsreader.news.core.tests.factories import FeedPostFactory

from .mocks import duplicate_mock, empty_mock, multiple_mock, multiple_update_mock


@freeze_time("2019-10-30 12:30:00")
class FeedCollectorTestCase(TestCase):
    def setUp(self):
        self.maxDiff = None

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

        self.patched_parse = patch("newsreader.news.collection.feed.FeedStream.parse")
        self.mocked_parse = self.patched_parse.start()

    def tearDown(self):
        patch.stopall()

    def test_simple_batch(self):
        self.mocked_parse.return_value = multiple_mock

        rule = FeedFactory()
        collector = FeedCollector()
        collector.collect(rules=[rule])

        rule.refresh_from_db()

        self.assertEqual(Post.objects.count(), 3)
        self.assertEqual(rule.succeeded, True)
        self.assertEqual(rule.last_run, datetime.now(tz=timezone.utc))
        self.assertEqual(rule.error, None)

    def test_emtpy_batch(self):
        self.mocked_fetch.return_value = Mock()
        self.mocked_parse.return_value = empty_mock

        rule = FeedFactory()

        collector = FeedCollector()
        collector.collect(rules=[rule])

        rule.refresh_from_db()

        self.assertEqual(Post.objects.count(), 0)
        self.assertEqual(rule.succeeded, True)
        self.assertEqual(rule.error, None)
        self.assertEqual(rule.last_run, datetime.now(tz=timezone.utc))

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

        rule = FeedFactory()
        collector = FeedCollector()
        collector.collect(rules=[rule])

        rule.refresh_from_db()

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

    def test_denied(self):
        self.mocked_fetch.side_effect = StreamDeniedException

        old_run = datetime(2019, 10, 30, 12, 30, tzinfo=timezone.utc)
        rule = FeedFactory(last_run=old_run)

        collector = FeedCollector()
        collector.collect(rules=[rule])

        rule.refresh_from_db()

        self.assertEqual(Post.objects.count(), 0)
        self.assertEqual(rule.succeeded, False)
        self.assertEqual(rule.error, "Stream does not have sufficient permissions")
        self.assertEqual(rule.last_run, datetime.now(tz=timezone.utc))

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

        old_run = datetime(2019, 10, 30, 12, 30, tzinfo=timezone.utc)
        rule = FeedFactory(last_run=old_run)

        collector = FeedCollector()
        collector.collect(rules=[rule])

        rule.refresh_from_db()

        self.assertEqual(Post.objects.count(), 0)
        self.assertEqual(rule.succeeded, False)
        self.assertEqual(rule.error, "Stream forbidden")
        self.assertEqual(rule.last_run, datetime.now(tz=timezone.utc))

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

        last_run = datetime(2019, 10, 30, 12, 30, tzinfo=timezone.utc)
        rule = FeedFactory(last_run=last_run)

        collector = FeedCollector()
        collector.collect(rules=[rule])

        rule.refresh_from_db()

        self.assertEqual(Post.objects.count(), 0)
        self.assertEqual(rule.succeeded, False)
        self.assertEqual(rule.error, "Stream timed out")
        self.assertEqual(
            rule.last_run, datetime(2019, 10, 30, 12, 30, tzinfo=timezone.utc)
        )

    def test_duplicates(self):
        self.mocked_parse.return_value = duplicate_mock

        rule = FeedFactory()

        aware_datetime = build_publication_date(
            struct_time((2019, 5, 20, 16, 7, 37, 0, 140, 0))
        )

        FeedPostFactory(
            url="https://www.bbc.co.uk/news/world-us-canada-48338168",
            title="Trump's 'genocidal taunts' will not end Iran - Zarif",
            body="Foreign Minister Mohammad Javad Zarif says the US "
            "president should try showing Iranians some respect.",
            publication_date=aware_datetime,
            rule=rule,
        )

        aware_datetime = build_publication_date(
            struct_time((2019, 5, 20, 12, 19, 19, 0, 140, 0))
        )

        FeedPostFactory(
            url="https://www.bbc.co.uk/news/technology-48334739",
            title="Huawei's Android loss: How it affects you",
            body="Google's move to end business ties with Huawei will "
            "affect current devices and future purchases.",
            publication_date=aware_datetime,
            rule=rule,
        )

        aware_datetime = build_publication_date(
            struct_time((2019, 5, 20, 16, 32, 38, 0, 140, 0))
        )

        FeedPostFactory(
            url="https://www.bbc.co.uk/news/uk-england-birmingham-48339080",
            title="Birmingham head teacher threatened over LGBT lessons",
            body="Police are investigating the messages while an MP "
            'calls for a protest exclusion zone "to protect '
            'children".',
            publication_date=aware_datetime,
            rule=rule,
        )

        collector = FeedCollector()
        collector.collect(rules=[rule])

        rule.refresh_from_db()

        self.assertEqual(Post.objects.count(), 3)
        self.assertEqual(rule.succeeded, True)
        self.assertEqual(rule.last_run, datetime.now(tz=timezone.utc))
        self.assertEqual(rule.error, None)

    def test_items_with_identifiers_get_updated(self):
        self.mocked_parse.return_value = multiple_update_mock
        rule = FeedFactory()

        first_post = FeedPostFactory(
            remote_identifier="https://www.bbc.co.uk/news/world-us-canada-48338168",
            url="https://www.bbc.co.uk/",
            title="Trump",
            body="Foreign Minister Mohammad Javad Zarif",
            publication_date=datetime.now(tz=timezone.utc),
            rule=rule,
        )

        second_post = FeedPostFactory(
            remote_identifier="https://www.bbc.co.uk/news/technology-48334739",
            url="https://www.bbc.co.uk/",
            title="Huawei's Android loss: How it affects you",
            body="Google's move to end business ties with Huawei will",
            publication_date=datetime.now(tz=timezone.utc),
            rule=rule,
        )

        third_post = FeedPostFactory(
            remote_identifier="https://www.bbc.co.uk/news/uk-england-birmingham-48339080",
            url="https://www.bbc.co.uk/news/uk-england-birmingham-48339080",
            title="Birmingham head teacher threatened over LGBT lessons",
            body="Police are investigating the messages while an MP",
            publication_date=datetime.now(tz=timezone.utc),
            rule=rule,
        )

        collector = FeedCollector()
        collector.collect(rules=[rule])

        rule.refresh_from_db()
        first_post.refresh_from_db()
        second_post.refresh_from_db()
        third_post.refresh_from_db()

        self.assertEqual(Post.objects.count(), 3)
        self.assertEqual(rule.succeeded, True)
        self.assertEqual(rule.last_run, datetime.now(tz=timezone.utc))
        self.assertEqual(rule.error, None)

        self.assertEqual(
            first_post.title, "Trump's 'genocidal taunts' will not end Iran - Zarif"
        )

        self.assertEqual(second_post.title, "Huawei's Android loss: How it affects you")

        self.assertEqual(
            third_post.title, "Birmingham head teacher threatened over LGBT lessons"
        )
