"""
Test the enterprise support APIs.
"""
import unittest

import mock
from django.conf import settings
from django.http import HttpResponseRedirect
from django.test.utils import override_settings

from openedx.features.enterprise_support.api import (
    data_sharing_consent_required,
    enterprise_customer_for_request,
    enterprise_enabled,
    get_dashboard_consent_notification,
    get_enterprise_branding_filter_param,
    get_enterprise_consent_url,
    get_enterprise_customer_logo_url,
    insert_enterprise_pipeline_elements,
    set_enterprise_branding_filter_param
)


@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class TestEnterpriseApi(unittest.TestCase):
    """
    Test enterprise support APIs.
    """

    @override_settings(ENABLE_ENTERPRISE_INTEGRATION=False)
    def test_utils_with_enterprise_disabled(self):
        """
        Test that disabling the enterprise integration flag causes
        the utilities to return the expected default values.
        """
        self.assertFalse(enterprise_enabled())
        self.assertEqual(insert_enterprise_pipeline_elements(None), None)

    @override_settings(ENABLE_ENTERPRISE_INTEGRATION=True)
    def test_utils_with_enterprise_enabled(self):
        """
        Test that enabling enterprise integration (which is currently on by default) causes the
        the utilities to return the expected values.
        """
        self.assertTrue(enterprise_enabled())
        pipeline = ['abc', 'social.pipeline.social_auth.load_extra_data', 'def']
        insert_enterprise_pipeline_elements(pipeline)
        self.assertEqual(pipeline, ['abc',
                                    'enterprise.tpa_pipeline.handle_enterprise_logistration',
                                    'social.pipeline.social_auth.load_extra_data',
                                    'def'])

    def test_set_enterprise_branding_filter_param(self):
        """
        Test that the enterprise customer branding parameters are setting correctly.
        """
        ec_uuid = '97b4a894-cea9-4103-8f9f-2c5c95a58ba3'
        provider_id = 'test-provider-idp'

        request = mock.MagicMock(session={}, GET={'ec_src': ec_uuid})
        set_enterprise_branding_filter_param(request, provider_id=None)
        self.assertEqual(get_enterprise_branding_filter_param(request), {'ec_uuid': ec_uuid})

        set_enterprise_branding_filter_param(request, provider_id=provider_id)
        self.assertEqual(get_enterprise_branding_filter_param(request), {'provider_id': provider_id})

    @override_settings(ENABLE_ENTERPRISE_INTEGRATION=True)
    def test_get_enterprise_customer_logo_url(self):
        """
        Test test_get_enterprise_customer_logo_url return the logo url as desired.
        """
        ec_uuid = '97b4a894-cea9-4103-8f9f-2c5c95a58ba3'
        provider_id = 'test-provider-idp'
        request = mock.MagicMock(session={}, GET={'ec_src': ec_uuid})
        branding_info = mock.Mock(
            logo=mock.Mock(
                url='/test/image.png'
            )
        )

        set_enterprise_branding_filter_param(request, provider_id=None)
        with mock.patch('enterprise.utils.get_enterprise_branding_info_by_ec_uuid', return_value=branding_info):
            logo_url = get_enterprise_customer_logo_url(request)
            self.assertEqual(logo_url, '/test/image.png')

        set_enterprise_branding_filter_param(request, provider_id)
        with mock.patch('enterprise.utils.get_enterprise_branding_info_by_provider_id', return_value=branding_info):
            logo_url = get_enterprise_customer_logo_url(request)
            self.assertEqual(logo_url, '/test/image.png')

    @override_settings(ENABLE_ENTERPRISE_INTEGRATION=False)
    def test_get_enterprise_customer_logo_url_return_none(self):
        """
        Test get_enterprise_customer_logo_url return 'None' when enterprise application is not installed.
        """
        request = mock.MagicMock(session={})
        branding_info = mock.Mock()

        set_enterprise_branding_filter_param(request, 'test-idp')
        with mock.patch('enterprise.utils.get_enterprise_branding_info_by_provider_id', return_value=branding_info):
            logo_url = get_enterprise_customer_logo_url(request)
            self.assertEqual(logo_url, None)

    @override_settings(ENABLE_ENTERPRISE_INTEGRATION=True)
    @mock.patch(
        'openedx.features.enterprise_support.api.get_enterprise_branding_filter_param',
        mock.Mock(return_value=None)
    )
    def test_get_enterprise_customer_logo_url_return_none_when_param_missing(self):
        """
        Test get_enterprise_customer_logo_url return 'None' when filter parameters are missing.
        """
        request = mock.MagicMock(session={})
        branding_info = mock.Mock()

        set_enterprise_branding_filter_param(request, provider_id=None)
        with mock.patch('enterprise.utils.get_enterprise_branding_info_by_provider_id', return_value=branding_info):
            logo_url = get_enterprise_customer_logo_url(request)
            self.assertEqual(logo_url, None)

    @override_settings(ENABLE_ENTERPRISE_INTEGRATION=True)
    @mock.patch('openedx.features.enterprise_support.api.get_enterprise_customer_for_request')
    @mock.patch('openedx.features.enterprise_support.api.EnterpriseCustomer')
    def test_enterprise_customer_for_request(self, ec_class_mock, get_ec_pipeline_mock):
        """
        Test that the correct EnterpriseCustomer, if any, is returned.
        """
        def get_ec_mock(**kwargs):
            by_provider_id_kw = 'enterprise_customer_identity_provider__provider_id'
            provider_id = kwargs.get(by_provider_id_kw, '')
            uuid = kwargs.get('uuid', '')
            if uuid == 'real-uuid' or provider_id == 'real-provider-id':
                return 'this-is-actually-an-enterprise-customer'
            elif uuid == 'not-a-uuid':
                raise ValueError
            else:
                raise Exception

        ec_class_mock.DoesNotExist = Exception
        ec_class_mock.objects.get.side_effect = get_ec_mock

        get_ec_pipeline_mock.return_value = None

        request = mock.MagicMock()
        request.GET.get.return_value = 'real-uuid'
        self.assertEqual(enterprise_customer_for_request(request), 'this-is-actually-an-enterprise-customer')
        request.GET.get.return_value = 'not-a-uuid'
        self.assertEqual(enterprise_customer_for_request(request), None)
        request.GET.get.return_value = 'fake-uuid'
        self.assertEqual(enterprise_customer_for_request(request), None)
        request.GET.get.return_value = None
        self.assertEqual(
            enterprise_customer_for_request(request, tpa_hint='real-provider-id'),
            'this-is-actually-an-enterprise-customer'
        )
        self.assertEqual(enterprise_customer_for_request(request, tpa_hint='fake-provider-id'), None)
        self.assertEqual(enterprise_customer_for_request(request, tpa_hint=None), None)

        get_ec_pipeline_mock.return_value = 'also-a-real-enterprise'

        self.assertEqual(enterprise_customer_for_request(request), 'also-a-real-enterprise')

    def check_data_sharing_consent(self, consent_required=False, consent_url=None):
        """
        Used to test the data_sharing_consent_required view decorator.
        """

        # Test by wrapping a function that has the expected signature
        @data_sharing_consent_required
        def view_func(request, course_id, *args, **kwargs):
            """
            Return the function arguments, so they can be tested.
            """
            return ((request, course_id,) + args, kwargs)

        # Call the wrapped function
        args = (mock.MagicMock(), 'course-id', 'another arg', 'and another')
        kwargs = dict(a=1, b=2, c=3)
        response = view_func(*args, **kwargs)

        # If consent required, then the response should be a redirect to the consent URL, and the view function would
        # not be called.
        if consent_required:
            self.assertIsInstance(response, HttpResponseRedirect)
            self.assertEquals(response.url, consent_url)  # pylint: disable=no-member

        # Otherwise, the view function should have been called with the expected arguments.
        else:
            self.assertEqual(response, (args, kwargs))

    @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled')
    @mock.patch('openedx.features.enterprise_support.api.consent_necessary_for_course')
    def test_data_consent_required_enterprise_disabled(self,
                                                       mock_consent_necessary,
                                                       mock_enterprise_enabled):
        """
        Verify that the wrapped view is called directly when enterprise integration is disabled,
        without checking for course consent necessary.
        """
        mock_enterprise_enabled.return_value = False

        self.check_data_sharing_consent(consent_required=False)

        mock_enterprise_enabled.assert_called_once()
        mock_consent_necessary.assert_not_called()

    @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled')
    @mock.patch('openedx.features.enterprise_support.api.consent_necessary_for_course')
    def test_no_course_data_consent_required(self,
                                             mock_consent_necessary,
                                             mock_enterprise_enabled):

        """
        Verify that the wrapped view is called directly when enterprise integration is enabled,
        and no course consent is required.
        """
        mock_enterprise_enabled.return_value = True
        mock_consent_necessary.return_value = False

        self.check_data_sharing_consent(consent_required=False)

        mock_enterprise_enabled.assert_called_once()
        mock_consent_necessary.assert_called_once()

    @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled')
    @mock.patch('openedx.features.enterprise_support.api.consent_necessary_for_course')
    @mock.patch('openedx.features.enterprise_support.api.get_enterprise_consent_url')
    def test_data_consent_required(self, mock_get_consent_url, mock_consent_necessary, mock_enterprise_enabled):
        """
        Verify that the wrapped function returns a redirect to the consent URL when enterprise integration is enabled,
        and course consent is required.
        """
        mock_enterprise_enabled.return_value = True
        mock_consent_necessary.return_value = True
        consent_url = '/abc/def'
        mock_get_consent_url.return_value = consent_url

        self.check_data_sharing_consent(consent_required=True, consent_url=consent_url)

        mock_get_consent_url.assert_called_once()
        mock_enterprise_enabled.assert_called_once()
        mock_consent_necessary.assert_called_once()

    @mock.patch('openedx.features.enterprise_support.api.consent_needed_for_course')
    def test_get_enterprise_consent_url(self, needed_for_course_mock):
        """
        Verify that get_enterprise_consent_url correctly builds URLs.
        """
        needed_for_course_mock.return_value = True

        request_mock = mock.MagicMock(
            user=None,
            build_absolute_uri=lambda x: 'http://localhost:8000' + x  # Don't do it like this in prod. Ever.
        )

        course_id = 'course-v1:edX+DemoX+Demo_Course'
        return_to = 'info'

        expected_url = (
            '/enterprise/grant_data_sharing_permissions?course_id=course-v1%3AedX%2BDemoX%2BDemo_'
            'Course&failure_url=http%3A%2F%2Flocalhost%3A8000%2Fdashboard%3Fconsent_failed%3Dcou'
            'rse-v1%253AedX%252BDemoX%252BDemo_Course&next=http%3A%2F%2Flocalhost%3A8000%2Fcours'
            'es%2Fcourse-v1%3AedX%2BDemoX%2BDemo_Course%2Finfo'
        )
        actual_url = get_enterprise_consent_url(request_mock, course_id, return_to=return_to)
        self.assertEqual(actual_url, expected_url)

    def test_get_dashboard_consent_notification_no_param(self):
        """
        Test that the output of the consent notification renderer meets expectations.
        """
        request = mock.MagicMock(
            GET={}
        )
        notification_string = get_dashboard_consent_notification(
            request, None, None
        )
        self.assertEqual(notification_string, '')

    def test_get_dashboard_consent_notification_no_enrollments(self):
        request = mock.MagicMock(
            GET={'consent_failed': 'course-v1:edX+DemoX+Demo_Course'}
        )
        enrollments = []
        user = mock.MagicMock(id=1)
        notification_string = get_dashboard_consent_notification(
            request, user, enrollments,
        )
        self.assertEqual(notification_string, '')

    def test_get_dashboard_consent_notification_no_matching_enrollments(self):
        request = mock.MagicMock(
            GET={'consent_failed': 'course-v1:edX+DemoX+Demo_Course'}
        )
        enrollments = [mock.MagicMock(course_id='other_course_id')]
        user = mock.MagicMock(id=1)
        notification_string = get_dashboard_consent_notification(
            request, user, enrollments,
        )
        self.assertEqual(notification_string, '')

    def test_get_dashboard_consent_notification_no_matching_ece(self):
        request = mock.MagicMock(
            GET={'consent_failed': 'course-v1:edX+DemoX+Demo_Course'}
        )
        enrollments = [mock.MagicMock(course_id='course-v1:edX+DemoX+Demo_Course')]
        user = mock.MagicMock(id=1)
        notification_string = get_dashboard_consent_notification(
            request, user, enrollments,
        )
        self.assertEqual(notification_string, '')

    @mock.patch('openedx.features.enterprise_support.api.EnterpriseCourseEnrollment')
    def test_get_dashboard_consent_notification_no_contact_info(self, ece_mock):
        mock_get_ece = ece_mock.objects.get
        ece_mock.DoesNotExist = Exception
        mock_ece = mock_get_ece.return_value
        mock_ece.enterprise_customer_user = mock.MagicMock(
            enterprise_customer=mock.MagicMock(
                contact_email=None
            )
        )
        mock_ec = mock_ece.enterprise_customer_user.enterprise_customer
        mock_ec.name = 'Veridian Dynamics'

        request = mock.MagicMock(
            GET={'consent_failed': 'course-v1:edX+DemoX+Demo_Course'}
        )
        enrollments = [
            mock.MagicMock(
                course_id='course-v1:edX+DemoX+Demo_Course',
                course_overview=mock.MagicMock(
                    display_name='edX Demo Course',
                )
            ),
        ]
        user = mock.MagicMock(id=1)
        notification_string = get_dashboard_consent_notification(
            request, user, enrollments,
        )
        expected_message = (
            'If you have concerns about sharing your data, please contact your '
            'administrator at Veridian Dynamics.'
        )
        self.assertIn(expected_message, notification_string)
        expected_header = 'Enrollment in edX Demo Course was not complete.'
        self.assertIn(expected_header, notification_string)

    @mock.patch('openedx.features.enterprise_support.api.EnterpriseCourseEnrollment')
    def test_get_dashboard_consent_notification_contact_info(self, ece_mock):
        mock_get_ece = ece_mock.objects.get
        ece_mock.DoesNotExist = Exception
        mock_ece = mock_get_ece.return_value
        mock_ece.enterprise_customer_user = mock.MagicMock(
            enterprise_customer=mock.MagicMock(
                contact_email='v.palmer@veridiandynamics.com'
            )
        )
        mock_ec = mock_ece.enterprise_customer_user.enterprise_customer
        mock_ec.name = 'Veridian Dynamics'

        request = mock.MagicMock(
            GET={'consent_failed': 'course-v1:edX+DemoX+Demo_Course'}
        )
        enrollments = [
            mock.MagicMock(
                course_id='course-v1:edX+DemoX+Demo_Course',
                course_overview=mock.MagicMock(
                    display_name='edX Demo Course',
                )
            ),
        ]
        user = mock.MagicMock(id=1)
        notification_string = get_dashboard_consent_notification(
            request, user, enrollments,
        )
        expected_message = (
            'If you have concerns about sharing your data, please contact your '
            'administrator at Veridian Dynamics at v.palmer@veridiandynamics.com.'
        )
        self.assertIn(expected_message, notification_string)
        expected_header = 'Enrollment in edX Demo Course was not complete.'
        self.assertIn(expected_header, notification_string)
