"""This module contains the test cases for the saba api client"""

import requests_mock
from mock import call, patch

from django.test import override_settings

from ..client import SabaApiClient, SabaApiException

from .test_cases import BaseSabaApiClientTestCase


class SabaApiClientTestCase(BaseSabaApiClientTestCase):
    """Test case for the SABA api client"""

    def test__internal_call_method_should_raise_for_bad_status_code(self):
        """Test that a response with a bad status code should raise an exception"""

        # Return an empty error message
        mock = self.adapter.register_uri('GET', self.SABA_API_URL, status_code=500)

        with self.assertRaises(SabaApiException) as exception:
            self.mocked_saba_api_client._internal_call(
                'GET',
                '',
                None
            )

            # Our mocked url is correctly called
            self.assertTrue(mock.called)
            self.assertEqual(mock.call_count, 1)

            self.assertEqual(exception.code, 500)
            self.assertEqual(exception.message, 'Empty error message')

        # Return a non-empty error message
        mock = self.adapter.register_uri(
            'GET',
            self.SABA_API_URL,
            status_code=404,
            json={
                'errorMessage': 'Not Found'
            }
        )

        with self.assertRaises(SabaApiException) as exception:
            self.mocked_saba_api_client._internal_call(
                'GET',
                '',
                None
            )

            # Our mocked url is correctly called
            self.assertTrue(mock.called)
            self.assertEqual(mock.call_count, 1)

            self.assertEqual(exception.code, 404)
            self.assertEqual(exception.message, 'Not Found')

    def test_setup_the_client_should_set_the_certificate_if_credentials_are_corrects(self):
        """Test that the certificate is correctly set up on successful login"""

        expected_certificate = 'testcertificate'
        expected_json_response = {
            'certificate': expected_certificate,
            '@type': 'SabaCertificate'
        }

        with requests_mock.Mocker() as mock:

            mock.register_uri('GET', self.SABA_LOGIN_URL, json=expected_json_response)

            saba_api_client = SabaApiClient(
                self.SABA_API_URL,
                self.SABA_USERNAME,
                self.SABA_PASSWORD,
            )

            # Our mocked url is correctly called
            self.assertTrue(mock.called)
            self.assertEqual(mock.call_count, 1)

        # The certificate is correctly setup on our client
        self.assertEqual(
            saba_api_client._certificate,
            expected_certificate
        )

    def test_setup_the_client_should_raise_if_credentials_are_incorrect(self):
        """Test that the setup of the SabaApiClient should raise if credentials are incorrect"""

        expected_json_response = {
            'errorMessage': 'The username or password that you entered is incorrect.'
        }

        with requests_mock.Mocker() as mock:
            mock.register_uri(
                'GET',
                self.SABA_LOGIN_URL,
                json=expected_json_response,
                status_code=500
            )
            with self.assertRaises(SabaApiException) as exception:
                SabaApiClient(
                    self.SABA_API_URL,
                    self.SABA_USERNAME,
                    self.SABA_PASSWORD
                )

                # Our mocked url is correctly called
                self.assertTrue(mock.called)
                self.assertEqual(mock.call_count, 1)

                self.assertEqual(
                    exception.message,
                    expected_json_response['errorMessage']
                )

    def test_user_details_method_should_return_the_users_details(self):
        """Test that the ``user_details`` method should correctly return the user details"""

        expected_json_response = {
            'id': 'user_id'
        }

        mock = self.adapter.register_uri(
            'GET',
            self.SABA_USER_DETAILS_URL % ('test_username', 'id'),
            json=expected_json_response,
            status_code=200
        )

        user_details = self.mocked_saba_api_client.user_details('test_username', ['id'])

        # Our mocked url is correctly called
        self.assertTrue(mock.called)
        self.assertEqual(mock.call_count, 1)

        self.assertEqual(
            user_details,
            expected_json_response
        )

    def test_get_enrollment_should_return_the_enrollment_details_if_it_exists(self):
        """Test that the ``get_enrollment`` should return the enrollment if it exists"""

        json_response = {
            'results': [
                {
                    'student_id': {
                        'displayName': 'Theodoros Adamidis',
                        'id': 'persn000000000018835'
                    },
                    'offering_temp_id': {
                        'displayName': 'NE Sales Assessment',
                        'id': 'cours000000000005983'
                    },
                    'start_date': '2016-01-19'
                }
            ]
        }

        mock = self.adapter.register_uri(
            'GET',
            self.SABA_ENROLLMENTS_SEARCH_URL,
            json=json_response,
            status_code=200
        )

        enrollment_details = self.mocked_saba_api_client.get_enrollment(
            'persn000000000018835',
            'cours000000000005983',
            '2016-01-19',
        )

        # Our mocked url is correctly called
        self.assertTrue(mock.called)
        self.assertEqual(mock.call_count, 1)

        self.assertEqual(
            enrollment_details,
            json_response['results'][0]
        )

    def test_get_enrollment_should_raises_if_multiple_results(self):
        """Test that ``get_enrollment`` method raises if multiple enrollments exists"""

        json_response = {
            'results': [
                {
                    'student_id': {
                        'displayName': 'Theodoros Adamidis',
                        'id': 'persn000000000018835'
                    },
                    'offering_temp_id': {
                        'displayName': 'NE Sales Assessment',
                        'id': 'cours000000000005983'
                    },
                    'start_date': '2016-01-19'
                },
                {
                    'student_id': {
                        'displayName': 'Theodoros Adamidis',
                        'id': 'persn000000000018835'
                    },
                    'offering_temp_id': {
                        'displayName': 'NE Sales Assessment',
                        'id': 'cours000000000005983'
                    },
                    'start_date': '2016-01-19'
                }
            ]
        }

        mock = self.adapter.register_uri(
            'GET',
            self.SABA_ENROLLMENTS_SEARCH_URL,
            json=json_response,
            status_code=200
        )

        with self.assertRaises(SabaApiException) as exception:
            self.mocked_saba_api_client.get_enrollment(
                'persn000000000018835',
                'cours000000000005983',
                '2016-01-19',
            )

            # Our mocked url is correctly called
            self.assertTrue(mock.called)
            self.assertEqual(mock.call_count, 1)

            self.assertEqual(exception.code, 404)
            self.assertEqual(
                exception.message,
                'Found Multiple enrollments for user %s / course %s taking on %s' % (
                    'persn000000000018835',
                    'cours000000000005983',
                    '2016-01-19',
                )
            )

    def test_get_enrollment_should_raises_if_empty_results(self):
        """Test that ``get_enrollment`` method raises if no enrollments exists"""

        json_response = {
            'results': []
        }

        mock = self.adapter.register_uri(
            'GET',
            self.SABA_ENROLLMENTS_SEARCH_URL,
            json=json_response,
            status_code=200
        )

        with self.assertRaises(SabaApiException) as exception:
            self.mocked_saba_api_client.get_enrollment(
                'persn000000000018835',
                'cours000000000005983',
                '2016-01-19',
            )

            # Our mocked url is correctly called
            self.assertTrue(mock.called)
            self.assertEqual(mock.call_count, 1)

            self.assertEqual(exception.code, 404)
            self.assertEqual(
                exception.message,
                'Could not found enrollment for user %s / course %s taking on %s' % (
                    'persn000000000018835',
                    'cours000000000005983',
                    '2016-01-19',
                )
            )

    def test_get_enrollment_details_should_return_the_enrollment_details_if_it_exists(self):
        """Test that the ``get_enrollment_details`` should return the enrollment if it exists"""

        json_response = {
            'registrationInfo': {
                'enrollmentStatus': {
                    'name': 'INPROGRESS'
                }
            }
        }

        mock = self.adapter.register_uri(
            'GET',
            self.SABA_ENROLLMENTS_DETAILS_URL % 'XXX',
            json=json_response,
            status_code=200
        )

        enrollment_details = self.mocked_saba_api_client.get_enrollment_details('XXX')

        # Our mocked url is correctly called
        self.assertTrue(mock.called)
        self.assertEqual(mock.call_count, 1)

        self.assertEqual(
            enrollment_details,
            json_response
        )

    def test_mark_attendance_should_call_the_attendance_api_route(self):
        """Test that the ``mark_attendance`` method should call the correct api route"""

        mock = self.adapter.register_uri(
            'POST',
            self.SABA_ATTENDANCE_URL
        )

        self.mocked_saba_api_client.mark_attendance(
            'persn000000000018835',
            'cours000000000005983'
        )

        # Our mocked url is correctly called
        self.assertTrue(mock.called)
        self.assertEqual(mock.call_count, 1)

    def test_complete_enrollment_should_call_the_enrollment_api_route(self):
        """Test that the ``complete_enrollment`` method should call the correct api route"""

        mock = self.adapter.register_uri(
            'PUT',
            self.SABA_ENROLLMENTS_STATUS % 'cours000000000005983',
        )

        self.mocked_saba_api_client.complete_enrollment(
            'cours000000000005983',
            200,
            100
        )

        # Our mocked url is correctly called
        self.assertTrue(mock.called)
        self.assertEqual(mock.call_count, 1)

    @override_settings(SABA_API_USERNAME_INTERNAL_ID='XXX')
    def test_create_transcript_should_call_the_transcript_api_route(self):
        """Test that the ``create_transcript`` method should call the correct api route"""

        mock = self.adapter.register_uri(
            'POST',
            self.SABA_TRANSCRIPT_URL,
        )

        self.mocked_saba_api_client.create_transcript(
            'persn000000000018835',
            'cours000000000005983',
            'exempt'
        )

        # Our mocked url is correctly called
        self.assertTrue(mock.called)
        self.assertEqual(mock.call_count, 1)

    def test_course_activity_should_call_the_course_activity_route(self):
        """Test that the ``course_activity`` method should call the correct api route"""

        mock = self.adapter.register_uri(
            'POST',
            self.SABA_COURSE_ACTIVITY_URL,
        )

        self.mocked_saba_api_client.course_activity(
            'persn000000000018835',
            'persn000000000018835',
            'cours000000000005983'
        )

        # Our mocked url is correctly called
        self.assertTrue(mock.called)
        self.assertEqual(mock.call_count, 1)

    def test_get_course_details_should_return_the_course_details_if_it_exists(self):
        """Test that the ``get_course_details`` should return the course if it exists"""

        json_response = {
            'results': [
                {
                    'href':
                        'https://nissanacademysb-api.sabacloud.com/v1/course/cours000000000005983',
                    'id': 'cours000000000005983',
                    'title':
                    'NE Sales Assessment'
                }
            ]
        }

        mock = self.adapter.register_uri(
            'GET',
            self.SABA_COURSE_DETAILS_URL % 'XXX',
            json=json_response,
            status_code=200
        )

        course_details = self.mocked_saba_api_client.get_course_details('XXX')

        # Our mocked url is correctly called
        self.assertTrue(mock.called)
        self.assertEqual(mock.call_count, 1)

        self.assertEqual(
            course_details,
            json_response['results'][0]
        )

    def test_get_course_details_should_return_last_version_if_multiple_results(self):
        """Test that ``get_course_details`` should return last version if multiple results"""

        course_id_1 = 'cours000000000005983'
        course_id_2 = 'cours000000000007292'

        json_response = {
            'results': [
                {
                    'href':
                        'https://nissanacademysb-api.sabacloud.com/v1/course/%s' % course_id_1,
                    'id': course_id_1,
                    'title':
                    'NE Sales Assessment'
                },
                {
                    'href':
                        'https://nissanacademysb-api.sabacloud.com/v1/course/%s' % course_id_2,
                    'id': course_id_2,
                    'title':
                    'NE Sales Assessment'
                }
            ]
        }

        mock_course_details = self.adapter.register_uri(
            'GET',
            self.SABA_COURSE_DETAILS_URL % 'XXX',
            json=json_response,
            status_code=200
        )

        # those will be called by ``get_course_details`` when multiple results
        mock_internal_id_course_1 = self.adapter.register_uri(
            'GET',
            self.SABA_COURSE_BY_INTERNAL_ID_URL % course_id_1,
            json={
                'id': course_id_1,
                'version': None
            },
            status_code=200
        )
        mock_internal_id_course_2 = self.adapter.register_uri(
            'GET',
            self.SABA_COURSE_BY_INTERNAL_ID_URL % course_id_2,
            json={
                'id': course_id_2,
                # this is the latest version
                'version': '001'
            },
            status_code=200
        )

        response = self.mocked_saba_api_client.get_course_details('XXX')

        # Our mocked url are correctly called
        for mock in [mock_course_details, mock_internal_id_course_1, mock_internal_id_course_2]:
            self.assertTrue(mock.called)
            self.assertEqual(mock.call_count, 1)

        # we got the last version
        self.assertEqual(response['id'], course_id_2)

    def test_get_course_details_should_raises_if_empty_results(self):
        """Test that ``get_course_details`` method raises if no courses exists"""

        json_response = {
            'results': []
        }

        mock = self.adapter.register_uri(
            'GET',
            self.SABA_COURSE_DETAILS_URL % 'XXX',
            json=json_response,
            status_code=200
        )

        with self.assertRaises(SabaApiException) as exception:
            self.mocked_saba_api_client.get_course_details('XXX')

            # Our mocked url is correctly called
            self.assertTrue(mock.called)
            self.assertEqual(mock.call_count, 1)

            self.assertEqual(exception.code, 404)
            self.assertEqual(
                exception.message,
                'Could not find course for external_id %s' % 'XXX'
            )

    def test_get_course_by_internal_id_should_return_the_course_details_if_it_exists(self):
        """Test that the ``get_course_by_internal_id`` should return the course if it exists"""

        json_response = {
            'id': 'XXX',
            'version': None
        }

        mock = self.adapter.register_uri(
            'GET',
            self.SABA_COURSE_BY_INTERNAL_ID_URL % 'XXX',
            json=json_response,
            status_code=200
        )

        course_details = self.mocked_saba_api_client.get_course_by_internal_id('XXX')

        # Our mocked url is correctly called
        self.assertTrue(mock.called)
        self.assertEqual(mock.call_count, 1)

        self.assertEqual(
            course_details,
            json_response
        )

    def test_get_course_details_should_raises_if_return_wrong_id(self):
        """Test that ``get_course_by_internal_id`` method raises if returns wrong internal_id"""

        json_response = {
            'id': 'YYY',
            'version': None
        }

        mock = self.adapter.register_uri(
            'GET',
            self.SABA_COURSE_BY_INTERNAL_ID_URL % 'XXX',
            json=json_response,
            status_code=200
        )

        with self.assertRaises(SabaApiException) as exception:
            self.mocked_saba_api_client.get_course_by_internal_id('XXX')

            # Our mocked url is correctly called
            self.assertTrue(mock.called)
            self.assertEqual(mock.call_count, 1)

            self.assertEqual(exception.code, 500)
            self.assertEqual(
                exception.message,
                'Bad response for course internal_id %s (id not found or mismatch)' % 'XXX'
            )

    def test_delete_transcript_should_call_the_delete_transcript_url(self):
        """Test that the ``delete_transcript`` method should call the delete transcript url"""

        json_response = {
            'id': 'fake_id',
        }

        mock = self.adapter.register_uri(
            'DELETE',
            self.SABA_TRANSCRIPT_DELETE_URL % 'fake_id',
            json=json_response,
            status_code=200
        )

        self.mocked_saba_api_client.delete_transcript('fake_id')

        self.assertTrue(mock.called)
        self.assertEqual(mock.call_count, 1)

    def test_get_transcript_should_call_the_search_transcripts_url(self):
        """Test that the ``get_transcripts`` method should call the search transcripts url"""

        json_response = {
            'results': [
                {'id': 'fake_id'}
            ]
        }

        mock = self.adapter.register_uri(
            'GET',
            self.SABA_TRANSCRIPT_SEARCH_URL % 'fake_username',
            json=json_response,
            status_code=200
        )

        self.mocked_saba_api_client.get_transcripts('fake_username')

        self.assertTrue(mock.called)
        self.assertEqual(mock.call_count, 1)

    def test_get_course_activities(self):
        """Test the ``get_course_activities`` method"""

        activities_results = [
            {
                'activityDefinition': {
                    'id': 'course_id'
                }
            },
            {
                'activityDefinition': {
                    'id': 'another_course_id'
                }
            },
        ]

        json_response = {
            'hasMoreResults': False,
            'results': activities_results
        }

        mock = self.adapter.register_uri(
            'GET',
            self.SABA_COURSE_ACTIVITY_FILTER_URL,
            json=json_response,
            status_code=200
        )

        course_activities = [
            course_activity
            for course_activity in self.mocked_saba_api_client.get_course_activities(
                'fake_user_id', 'course_id'
            )
        ]

        self.assertTrue(mock.called)
        self.assertEqual(mock.call_count, 1)
        self.assertEqual(
            course_activities,
            [
                {
                    'activityDefinition': {
                        'id': 'course_id'
                    }
                }
            ]
        )

    def test_get_course_activities_with_multiple_pages(self):
        """Test the ``get_course_activities`` method"""

        activities_results = [
            {
                'activityDefinition': {
                    'id': 'course_id'
                }
            }
        ]

        course_generator = self.mocked_saba_api_client.get_course_activities(
            'fake_user_id',
            'course_id'
        )

        # Call of the generator, with more result page
        with patch.object(self.mocked_saba_api_client,
                          '_get_paginated_course_activities',
                          return_value=(True, activities_results,)) as mock:
            course_generator.__next__()
            course_generator.__next__()

        # The mock was called two times, with two different pages
        calls = [call('fake_user_id', 0), call('fake_user_id', 1)]
        mock.assert_has_calls(calls)

        course_generator = self.mocked_saba_api_client.get_course_activities(
            'fake_user_id',
            'course_id'
        )

        # Call of the generator, with only one result page
        with patch.object(self.mocked_saba_api_client,
                          '_get_paginated_course_activities',
                          return_value=(False, activities_results,)) as mock:
            course_generator.__next__()

            # Since we got only one page with one course
            with self.assertRaises(StopIteration):
                course_generator.__next__()

        # The mock was called once, with the first page
        mock.assert_called_once_with('fake_user_id', 0)
