from unittest.mock import Mock, patch
from uuid import uuid4

from django.test import TestCase
from django.utils.lorem_ipsum import words

from newsreader.accounts.tests.factories import UserFactory
from newsreader.news.collection.exceptions import (
    StreamDeniedException,
    StreamException,
    StreamNotFoundException,
    StreamParseException,
    StreamTimeOutException,
    StreamTooManyException,
)
from newsreader.news.collection.tests.factories import TwitterTimelineFactory
from newsreader.news.collection.twitter import TwitterClient

from .mocks import simple_mock


class TwitterClientTestCase(TestCase):
    def setUp(self):
        patched_read = patch("newsreader.news.collection.twitter.TwitterStream.read")
        self.mocked_read = patched_read.start()

    def tearDown(self):
        patch.stopall()

    def test_simple(self):
        timeline = TwitterTimelineFactory()
        mock_stream = Mock(rule=timeline)

        self.mocked_read.return_value = (simple_mock, mock_stream)

        with TwitterClient([timeline]) as client:
            for data, stream in client:
                with self.subTest(data=data, stream=stream):
                    self.assertEquals(data, simple_mock)
                    self.assertEquals(stream, mock_stream)

        self.mocked_read.assert_called()

    def test_client_catches_stream_exception(self):
        timeline = TwitterTimelineFactory()

        self.mocked_read.side_effect = StreamException(message="Stream exception")

        with TwitterClient([timeline]) as client:
            for data, stream in client:
                with self.subTest(data=data, stream=stream):
                    self.assertIsNone(data)
                    self.assertIsNone(stream)
                    self.assertEquals(stream.rule.error, "Stream exception")
                    self.assertEquals(stream.rule.succeeded, False)

        self.mocked_read.assert_called()

    def test_client_catches_stream_not_found_exception(self):
        timeline = TwitterTimelineFactory.create()

        self.mocked_read.side_effect = StreamNotFoundException(
            message="Stream not found"
        )

        with TwitterClient([timeline]) as client:
            for data, stream in client:
                with self.subTest(data=data, stream=stream):
                    self.assertIsNone(data)
                    self.assertIsNone(stream)
                    self.assertEquals(stream.rule.error, "Stream not found")
                    self.assertEquals(stream.rule.succeeded, False)

        self.mocked_read.assert_called()

    def test_client_catches_stream_denied_exception(self):
        """
        Twitter also returns these responses for accounts which have been shutdown.
        Therefore the error codes should be checked inside the response body.
        See https://stackoverflow.com/questions/8357568/do-twitter-access-token-expire
        """
        user = UserFactory(
            twitter_oauth_token=str(uuid4()), twitter_oauth_token_secret=str(uuid4())
        )
        timeline = TwitterTimelineFactory(user=user)

        self.mocked_read.side_effect = StreamDeniedException(message="Not authorized")

        with TwitterClient([timeline]) as client:
            for data, stream in client:
                with self.subTest(data=data, stream=stream):
                    self.assertIsNone(data)
                    self.assertIsNone(stream)
                    self.assertEquals(stream.rule.error, "Authorization required")
                    self.assertEquals(stream.rule.succeeded, False)

        self.mocked_read.assert_called()

        user.refresh_from_db()
        timeline.refresh_from_db()

        self.assertIsNotNone(user.twitter_oauth_token)
        self.assertIsNotNone(user.twitter_oauth_token_secret)

    def test_client_catches_stream_timed_out_exception(self):
        timeline = TwitterTimelineFactory()

        self.mocked_read.side_effect = StreamTimeOutException(
            message="Stream timed out"
        )

        with TwitterClient([timeline]) as client:
            for data, stream in client:
                with self.subTest(data=data, stream=stream):
                    self.assertIsNone(data)
                    self.assertIsNone(stream)
                    self.assertEquals(stream.rule.error, "Stream timed out")
                    self.assertEquals(stream.rule.succeeded, False)

        self.mocked_read.assert_called()

    def test_client_catches_stream_too_many_exception(self):
        timeline = TwitterTimelineFactory()

        self.mocked_read.side_effect = StreamTooManyException

        with TwitterClient([timeline]) as client:
            for data, stream in client:
                with self.subTest(data=data, stream=stream):
                    self.assertIsNone(data)
                    self.assertIsNone(stream)
                    self.assertEquals(stream.rule.error, "Too many requests")
                    self.assertEquals(stream.rule.succeeded, False)

        self.mocked_read.assert_called()

    def test_client_catches_stream_parse_exception(self):
        timeline = TwitterTimelineFactory()

        self.mocked_read.side_effect = StreamParseException(
            message="Stream could not be parsed"
        )

        with TwitterClient([timeline]) as client:
            for data, stream in client:
                with self.subTest(data=data, stream=stream):
                    self.assertIsNone(data)
                    self.assertIsNone(stream)
                    self.assertEquals(stream.rule.error, "Stream could not be parsed")
                    self.assertEquals(stream.rule.succeeded, False)

        self.mocked_read.assert_called()

    def test_client_catches_long_exception_text(self):
        timeline = TwitterTimelineFactory()
        mock_stream = Mock(rule=timeline)

        self.mocked_read.side_effect = StreamParseException(message=words(1000))

        with TwitterClient([timeline]) as client:
            for data, stream in client:
                self.assertIsNone(data)
                self.assertIsNone(stream)
                self.assertEquals(len(stream.rule.error), 1024)
                self.assertEquals(stream.rule.succeeded, False)

        self.mocked_read.assert_called()

    def test_client_catches_token_expired(self):
        user = UserFactory(
            twitter_oauth_token=str(uuid4()), twitter_oauth_token_secret=str(uuid4())
        )
        timeline = TwitterTimelineFactory(user=user)

        response = Mock(json=lambda: {"errors": [{"code": 89}]})

        self.mocked_read.side_effect = StreamDeniedException(
            message="Not authorized", response=response
        )

        with TwitterClient([timeline]) as client:
            for data, stream in client:
                with self.subTest(data=data, stream=stream):
                    self.assertIsNone(data)
                    self.assertIsNone(stream)
                    self.assertEquals(stream.rule.error, "Authorization required")
                    self.assertEquals(stream.rule.succeeded, False)

        self.mocked_read.assert_called()

        user.refresh_from_db()
        timeline.refresh_from_db()

        self.assertIsNone(user.twitter_oauth_token)
        self.assertIsNone(user.twitter_oauth_token_secret)

    def test_client_does_not_reset_token(self):
        """
        The user's token and refresh token should not be reset when an generic
        exception is caught
        """
        user = UserFactory(
            twitter_oauth_token=str(uuid4()), twitter_oauth_token_secret=str(uuid4())
        )
        timeline = TwitterTimelineFactory(user=user)

        response = Mock(json=lambda: {"errors": [{"code": 100}]})

        self.mocked_read.side_effect = StreamException(
            message="Generic message", response=response
        )

        with TwitterClient([timeline]) as client:
            for data, stream in client:
                with self.subTest(data=data, stream=stream):
                    self.assertIsNone(data)
                    self.assertIsNone(stream)
                    self.assertEquals(stream.rule.error, "")
                    self.assertEquals(stream.rule.succeeded, False)

        self.mocked_read.assert_called()

        user.refresh_from_db()
        timeline.refresh_from_db()

        self.assertIsNotNone(user.twitter_oauth_token)
        self.assertIsNotNone(user.twitter_oauth_token_secret)
