from django.conf import settings
from django.test import RequestFactory, TestCase, override_settings
from django.contrib.sessions.middleware import SessionMiddleware
from blti.validators import BLTIRequestValidator, Roles
from blti.crypto import aes128cbc
from blti.models import BLTIData
from blti.performance import log_response_time
from blti import BLTI, BLTIException
from mock import Mock
import time


class RequestValidatorTest(TestCase):
    def setUp(self):
        self.request = RequestFactory().post(
            '/test', data=getattr(settings, 'CANVAS_LTI_V1_LAUNCH_PARAMS', {}),
            secure=True)

    def test_check_client_key(self):
        self.assertTrue(BLTIRequestValidator().check_client_key('x' * 12))
        self.assertTrue(BLTIRequestValidator().check_client_key('5' * 30))
        self.assertTrue(BLTIRequestValidator().check_client_key('-' * 20))
        self.assertTrue(BLTIRequestValidator().check_client_key('_' * 20))
        self.assertFalse(BLTIRequestValidator().check_client_key('x' * 11))
        self.assertFalse(BLTIRequestValidator().check_client_key('x' * 31))
        self.assertFalse(BLTIRequestValidator().check_client_key('*' * 40))

    def test_check_nonce(self):
        self.assertTrue(BLTIRequestValidator().check_nonce('x' * 20))
        self.assertTrue(BLTIRequestValidator().check_nonce('5' * 50))
        self.assertTrue(BLTIRequestValidator().check_nonce('-' * 20))
        self.assertTrue(BLTIRequestValidator().check_nonce('_' * 20))
        self.assertFalse(BLTIRequestValidator().check_nonce('*' * 20))
        self.assertFalse(BLTIRequestValidator().check_nonce('x' * 19))
        self.assertFalse(BLTIRequestValidator().check_nonce('x' * 51))

    def test_validate_client_key(self):
        with self.settings(LTI_CONSUMERS={}):
            self.assertFalse(
                BLTIRequestValidator().validate_client_key('X', self.request))

        with self.settings(LTI_CONSUMERS={'A': '12345'}):
            self.assertTrue(
                BLTIRequestValidator().validate_client_key('A', self.request))

    def test_get_client_secret(self):
        with self.settings(LTI_CONSUMERS={}):
            self.assertEquals(
                BLTIRequestValidator().get_client_secret('X', self.request),
                'dummy')

        with self.settings(LTI_CONSUMERS={'A': '12345'}):
            self.assertEquals(
                BLTIRequestValidator().get_client_secret('A', self.request),
                '12345')

    def test_validate_timestamp_and_nonce(self):
        self.assertTrue(
            BLTIRequestValidator().validate_timestamp_and_nonce(
                'X', time.time(), '', self.request))

        self.assertFalse(
            BLTIRequestValidator().validate_timestamp_and_nonce(
                'X', time.time() - 65, '', self.request))

        self.assertFalse(
            BLTIRequestValidator().validate_timestamp_and_nonce(
                'X', time.time() + 65, '', self.request))

        self.assertFalse(
            BLTIRequestValidator().validate_timestamp_and_nonce(
                'X', '1234567890', '', self.request))


class BLTIDataTest(TestCase):
    def test_attributes(self):
        params = getattr(settings, 'CANVAS_LTI_V1_LAUNCH_PARAMS', {})
        blti = BLTIData(**params)

        self.assertEquals(blti.link_title, 'Example App')
        self.assertEquals(blti.return_url,
                          'https://example.instructure.com/courses/123456')
        self.assertEquals(blti.canvas_course_id, '123456')
        self.assertEquals(blti.course_sis_id, '2018-spring-ABC-101-A')
        self.assertEquals(blti.course_short_name, 'ABC 101 A')
        self.assertEquals(blti.course_long_name, 'ABC 101 A: Example Course')
        self.assertEquals(blti.canvas_user_id, '123456')
        self.assertEquals(blti.user_login_id, 'javerage')
        self.assertEquals(blti.user_sis_id, 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')
        self.assertEquals(blti.user_full_name, 'James Average')
        self.assertEquals(blti.user_first_name, 'James')
        self.assertEquals(blti.user_last_name, 'Average')
        self.assertEquals(blti.user_email, 'javerage@example.edu')
        self.assertEquals(
            blti.user_avatar_url, (
                'https://example.instructure.com/images/thumbnails/123456/'
                'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'))
        self.assertEquals(blti.canvas_account_id, '12345')
        self.assertEquals(blti.account_sis_id, 'example:account')
        self.assertEquals(blti.canvas_api_domain, 'example.instructure.com')

    def test_get(self):
        params = getattr(settings, 'CANVAS_LTI_V1_LAUNCH_PARAMS', {})
        blti = BLTIData(**params)

        self.assertEquals(blti.get('custom_canvas_course_id'), '123456')
        self.assertEquals(blti.get('lis_person_contact_email_primary'),
                          'javerage@example.edu')
        self.assertEquals(blti.get('invalid_param_name'), None)


class CanvasRolesTest(TestCase):
    def setUp(self):
        params = {
            'tool_consumer_info_product_family_code': 'canvas',
            'roles': '',
            'ext_roles': ''
        }
        self.blti = BLTIData(**params)

    def test_authorize_nodata(self):
        # no blti object
        self.assertRaises(
            BLTIException, Roles().authorize, None, role='member')

        # unimplemented product code
        self.blti.data['tool_consumer_info_product_family_code'] = 'my_lms'
        self.assertRaises(
            BLTIException, Roles().authorize, self.blti, role='member')

    def test_authorize_public(self):
        self.assertEquals(None, Roles().authorize(self.blti, role='public'))
        self.assertEquals(None, Roles().authorize(self.blti, role=None))

    def test_authorize_member(self):
        self.assertRaises(
            BLTIException, Roles().authorize, self.blti, role='member')

        self.blti.data['roles'] = 'Administrator'
        self.assertEquals(None, Roles().authorize(self.blti, role='member'))

        self.blti.data['roles'] = 'urn:lti:instrole:ims/lis/Observer'
        self.assertEquals(None, Roles().authorize(self.blti, role='member'))

    def test_authorize_admin(self):
        self.assertRaises(
            BLTIException, Roles().authorize, self.blti, role='admin')

        self.blti.data['roles'] = 'Learner'
        self.assertRaises(
            BLTIException, Roles().authorize, self.blti, role='admin')

        self.blti.data['roles'] = 'urn:lti:instrole:ims/lis/Administrator'
        self.assertEquals(None, Roles().authorize(self.blti, role='admin'))

        self.blti.data['roles'] = 'urn:lti:role:ims/lis/TeachingAssistant'
        self.assertEquals(None, Roles().authorize(self.blti, role='admin'))

        self.blti.data['roles'] = 'Learner,ContentDeveloper'
        self.assertEquals(None, Roles().authorize(self.blti, role='admin'))

        # ignores ext_roles
        self.blti.data['roles'] = 'Learner'
        self.blti.data['ext_roles'] = 'urn:lti:role:ims/lis/Instructor'
        self.assertRaises(
            BLTIException, Roles().authorize, self.blti, role='admin')

    def test_authorize_specific(self):
        self.blti.data['roles'] = 'Learner'
        self.assertEquals(None, Roles().authorize(self.blti, role='Learner'))

        self.blti.data['roles'] = 'urn:lti:role:ims/lis/Learner'
        self.assertEquals(None, Roles().authorize(self.blti, role='Learner'))

        self.blti.data['roles'] = 'Learner,ContentDeveloper'
        self.assertRaises(
            BLTIException, Roles().authorize, self.blti, role='Instructor')

        # unknown role
        self.blti.data['roles'] = 'Learner'
        self.assertRaises(
            BLTIException, Roles().authorize, self.blti, role='Manager')


@override_settings(BLTI_AES_KEY='DUMMY_KEY_FOR_TESTING_1234567890',
                   BLTI_AES_IV='DUMMY_IV_TESTING')
class BLTISessionTest(TestCase):
    def setUp(self):
        self.request = RequestFactory().post(
            '/test', data=getattr(settings, 'CANVAS_LTI_V1_LAUNCH_PARAMS', {}),
            secure=True)
        SessionMiddleware().process_request(self.request)

    def test_set_session(self):
        blti = BLTI()
        self.assertFalse('blti' in self.request.session)
        blti.set_session(self.request)
        self.assertTrue('blti' in self.request.session)

    def test_get_session(self):
        blti = BLTI()
        self.assertRaises(BLTIException, blti.get_session, self.request)

        blti.set_session(self.request)
        self.assertEqual(blti.get_session(self.request), {})

    def test_encrypt_decrypt_session(self):
        data = {'abc': {'key': 123},
                'xyz': ('LTI provides a framework through which an LMS '
                        'can send some verifiable information about a '
                        'user to a third party.')}

        enc = BLTI()._encrypt_session(data)
        self.assertEquals(BLTI()._decrypt_session(enc), data)

    def test_filter_oauth_params(self):
        data = getattr(settings, 'CANVAS_LTI_V1_LAUNCH_PARAMS', {})

        self.assertEquals(len(data), 43)
        self.assertEquals(data['oauth_consumer_key'], 'XXXXXXXXXXXXXX')

        data = BLTI().filter_oauth_params(data)
        self.assertEquals(len(data), 36)
        self.assertRaises(KeyError, lambda: data['oauth_consumer_key'])


class BLTILaunchViewTest(TestCase):
    def test_launch_view(self):
        response = self.client.post('/blti/')
        self.assertEquals(response.status_code, 401)


class BLTIDecoratorTest(TestCase):
    def setUp(self):
        self.request = RequestFactory().post(
            '/test', data=getattr(settings, 'CANVAS_LTI_V1_LAUNCH_PARAMS', {}),
            secure=True)
        SessionMiddleware().process_request(self.request)

    def test_log_response_time(self):
        func = Mock()
        func.__name__ = 'test'
        decorated_func = log_response_time(func)

        with self.assertLogs(level='INFO') as cm:
            response = decorated_func(self.request, 'test1', test_id=123)

        self.assertIn(
            'INFO:blti.performance:user: None, method: '
            'django.core.handlers.wsgi.test, args: (\'test1\',), '
            'kwargs: {\'test_id\': 123}, time: ',
            cm.output[0])
