import json
import unittest

from acai_aws.apigateway.router import Router

from tests.mocks.apigateway import mock_middleware, mock_request


class RouterMappingTest(unittest.TestCase):
    maxDiff = None
    base_path = 'unit-test/v1'
    schema_path = 'tests/mocks/apigateway/openapi.yml'
    base_path_schema_path = 'tests/mocks/apigateway/base_path_openapi.yml'
    basic_event = mock_request.get_basic_post()
    raise_exception_event = mock_request.get_raised_exception_post()
    mock_request = mock_request
    unhandled_exception_event = mock_request.get_unhandled_exception_post()
    expected_open_headers = {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Headers': '*'
    }
    handler_mapping = {
        'auto': 'tests/mocks/apigateway/router/mapping_handlers/auto/__init__.py',
        'basic': 'tests/mocks/apigateway/router/mapping_handlers/basic.py',
        'optional-params': 'tests/mocks/apigateway/router/mapping_handlers/optional_params.py',
        'raise-exception': 'tests/mocks/apigateway/router/mapping_handlers/raise_exception.py',
        'unhandled-exception': 'tests/mocks/apigateway/router/mapping_handlers/unhandled_exception.py',
        'nested/reqs': 'tests/mocks/apigateway/router/mapping_handlers/nested/reqs.py',
        'nested/{id}': 'tests/mocks/apigateway/router/mapping_handlers/nested/nested_id.py'
    }

    def test_basic_mapping_routing_works(self):
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.schema_path
        )
        result = router.route(self.basic_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertFalse(result['isBase64Encoded'])
        self.assertEqual(200, result['statusCode'])
        self.assertDictEqual(self.expected_open_headers, result['headers'])
        self.assertDictEqual({"router_mapping_basic": {"body_key": "body_value"}}, json_dict_response)

    def test_basic_mapping_routing_works_with_verbose_logging(self):
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.schema_path,
            verbose_logging=True
        )
        result = router.route(self.basic_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertFalse(result['isBase64Encoded'])
        self.assertEqual(200, result['statusCode'])
        self.assertDictEqual(self.expected_open_headers, result['headers'])
        self.assertDictEqual({"router_mapping_basic": {"body_key": "body_value"}}, json_dict_response)

    def test_auto_load_works(self):
        try:
            router = Router(
                base_path=self.base_path,
                handlers=self.handler_mapping,
                schema=self.schema_path
            )
            router.auto_load()
            self.assertTrue(True)
        except Exception as error:
            print(error)
            self.assertTrue(False)

    def test_basic_mapping_routing_works_no_schema_defined(self):
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping
        )
        result = router.route(self.basic_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertFalse(result['isBase64Encoded'])
        self.assertEqual(200, result['statusCode'])
        self.assertDictEqual(self.expected_open_headers, result['headers'])
        self.assertDictEqual({"router_mapping_basic": {"body_key": "body_value"}}, json_dict_response)

    def test_basic_mapping_routing_works_with_raised_exception(self):
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.schema_path,
            output_error=True
        )
        result = router.route(self.raise_exception_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertFalse(result['isBase64Encoded'])
        self.assertEqual(418, result['statusCode'])
        self.assertDictEqual(self.expected_open_headers, result['headers'])
        self.assertDictEqual({"errors": [{"key_path": "crazy_error", "message": "I am a teapot"}]}, json_dict_response)

    def test_basic_mapping_routing_works_with_unhandled_exception(self):
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.schema_path,
            output_error=True
        )
        result = router.route(self.unhandled_exception_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertFalse(result['isBase64Encoded'])
        self.assertEqual(500, result['statusCode'])
        self.assertDictEqual(self.expected_open_headers, result['headers'])
        self.assertDictEqual({'errors': [{'key_path': 'unknown', 'message': "name 'UnknownException' is not defined"}]}, json_dict_response)

    def test_basic_mapping_routing_works_and_before_all_function_called(self):
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            before_all=mock_middleware.mock_before_all,
            schema=self.schema_path
        )
        router.route(self.basic_event, None)
        self.assertTrue(mock_middleware.mock_before_all.has_been_called)

    def test_basic_mapping_routing_works_and_after_all_function_called(self):
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            after_all=mock_middleware.mock_after_all,
            schema=self.schema_path
        )
        router.route(self.basic_event, None)
        self.assertTrue(mock_middleware.mock_after_all.has_been_called)

    def test_basic_mapping_routing_works_and_with_auth_function_called(self):
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            with_auth=mock_middleware.mock_with_auth,
            schema=self.schema_path
        )
        router.route(self.basic_event, None)
        self.assertTrue(mock_middleware.mock_with_auth.has_been_called)

    def test_basic_mapping_routing_works_and_on_error_function_called(self):
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            on_error=mock_middleware.mock_on_error,
            schema=self.schema_path
        )
        router.route(self.raise_exception_event, None)
        self.assertTrue(mock_middleware.mock_on_error.has_been_called)

    def test_basic_mapping_routing_works_and_bad_on_error_function_caught(self):
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            on_error=mock_middleware.mock_on_error_exception,
            schema=self.schema_path
        )
        router.route(self.raise_exception_event, None)
        self.assertTrue(mock_middleware.mock_on_error_exception.has_been_called)

    def test_requirements_decorator_works_and_passes_proper_body_request(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'content-type': 'application/json'},
            path='unit-test/v1/nested/reqs',
            proxy='nested/reqs',
            method='post',
            body={
                'name': 'unit-test',
                'email': 'unit@email.com',
                'phone': 1234567890,
                'active': True
            }
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.schema_path
        )
        result = router.route(dynamic_event, None)
        self.assertEqual(200, result['statusCode'])

    def test_requirements_decorator_works_and_fails_improper_body_request(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'content-type': 'application/json'},
            path='unit-test/v1/nested/reqs',
            proxy='nested/reqs',
            method='post',
            body={
                'name': 'unit-test',
                'email': 'unit@email.com',
                'phone': '1234567890',
                'active': True
            }
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.schema_path
        )
        result = router.route(dynamic_event, None)
        self.assertEqual(400, result['statusCode'])
        json_dict_response = json.loads(result['body'])
        self.assertDictEqual({'errors': [{'key_path': 'phone', 'message': "'1234567890' is not of type 'number'"}]}, json_dict_response)

    def test_requirements_decorator_works_and_passes_proper_query_request(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'content-type': 'application/json'},
            path='unit-test/v1/nested/reqs',
            proxy='nested/reqs',
            method='get',
            query={
                'auth_id': 'some-id',
                'email': 'some@email.com',
                'name': 'unit-test'
            }
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.schema_path
        )
        result = router.route(dynamic_event, None)
        self.assertEqual(200, result['statusCode'])
        self.assertDictEqual(self.expected_open_headers, result['headers'])

    def test_requirements_decorator_works_and_fails_improper_query_request_missing_required(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'content-type': 'application/json'},
            path='unit-test/v1/nested/reqs',
            proxy='nested/reqs',
            method='get',
            query={
                'email': 'some@email.com',
                'name': 'unit-test'
            }
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping
        )
        result = router.route(dynamic_event, None)
        self.assertEqual(400, result['statusCode'])
        json_dict_response = json.loads(result['body'])
        self.assertDictEqual({'errors': [{'key_path': 'query_params', 'message': 'Please provide auth_id in query_params'}]}, json_dict_response)

    def test_requirements_decorator_works_and_passes_proper_headers(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'content-type': 'application/json', 'correlation-id': 'abc-123'},
            path='unit-test/v1/nested/reqs',
            proxy='nested/reqs',
            method='delete'
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping
        )
        result = router.route(dynamic_event, None)
        self.assertEqual(200, result['statusCode'])

    def test_requirements_decorator_works_and_fails_improper_headers_missing_required(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'correlation-id': 'abc-123'},
            path='unit-test/v1/nested/reqs',
            proxy='nested/reqs',
            method='delete'
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping
        )
        result = router.route(dynamic_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertEqual(400, result['statusCode'])
        self.assertDictEqual({'errors': [{'key_path': 'headers', 'message': 'Please provide content-type in headers'}]}, json_dict_response)

    def test_requirements_decorator_works_and_fails_improper_headers_unknown_header(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'unknown-id': 'abc-123'},
            path='unit-test/v1/nested/reqs',
            proxy='nested/reqs',
            method='delete'
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping
        )
        result = router.route(dynamic_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertEqual(400, result['statusCode'])
        self.assertDictEqual(
            {
                'errors': [{'key_path': 'headers', 'message': 'Please provide content-type in headers'},
                           {'key_path': 'headers', 'message': 'unknown-id is not an available headers'}]
            }, json_dict_response
        )

    def test_requirements_decorator_works_and_passes_proper_dynamic_route_request(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'content-type': 'application/json'},
            path='unit-test/v1/nested/abc-123',
            proxy='nested/abc-123',
            method='patch',
            body={
                'test_id': 'unit-test',
                'email': 'unit@email.com'
            }
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.schema_path
        )
        result = router.route(dynamic_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertEqual(200, result['statusCode'])
        self.assertDictEqual(
            {
                'router_nested_mapping_dynamic': {
                    'test_id': 'unit-test',
                    'email': 'unit@email.com'
                },
                'path_params': {'proxy': 'nested/abc-123', 'nested_id': 'abc-123'}
            },
            json_dict_response
        )

    def test_requirements_decorator_works_and_fails_improper_dynamic_route_request(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'content-type': 'application/json'},
            path='unit-test/v1/nested/abc-123',
            proxy='nested/abc-123',
            method='patch',
            body={
                'email': 'unit@email.com'
            }
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.schema_path
        )
        result = router.route(dynamic_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertEqual(400, result['statusCode'])
        self.assertDictEqual({'errors': [{'key_path': 'root', 'message': "'test_id' is a required property"}]}, json_dict_response)

    def test_basic_mapping_routing_works_with_validate_response_body(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'x-api-key': 'some-key'},
            path='unit-test/v1/auto',
            proxy='auto',
            method='patch'
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.schema_path
        )
        result = router.route(dynamic_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertEqual(200, result['statusCode'])
        self.assertDictEqual({'page_number': 1, 'data': {'id': '2'}}, json_dict_response)

    def test_auto_validate_works_and_passes_proper_dynamic_route_request(self):
        body = {
            'test_id': 'abc123',
            'object_key': {
                'key': 'value'
            },
            'array_number': [1, 2, 3],
            'array_objects': [
                {
                    'array_string_key': 'string_value',
                    'array_number_key': 0
                }
            ]
        }
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'content-type': 'application/json', 'x-api-key': 'some-key'},
            path='unit-test/v1/auto',
            proxy='auto',
            method='post',
            body=body
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.schema_path,
            auto_validate=True
        )
        result = router.route(dynamic_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertEqual(200, result['statusCode'])
        self.assertDictEqual({'router_mapping_auto': body}, json_dict_response)

    def test_auto_validate_works_and_fails_improper_dynamic_route_request(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'content-type': 'application/json', 'x-api-key': 'some-key'},
            path='unit-test/v1/auto',
            proxy='auto',
            method='post',
            body={
                'object_key': {
                    'key': 'value'
                },
                'array_number': [1, 2, 3],
                'array_objects': [
                    {
                        'array_string_key': 'string_value',
                        'array_number_key': 0
                    }
                ]
            }
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.schema_path,
            auto_validate=True
        )
        result = router.route(dynamic_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertEqual(400, result['statusCode'])
        self.assertDictEqual({'errors': [{'key_path': 'root', 'message': "'test_id' is a required property"}]}, json_dict_response)

    def test_auto_validate_works_and_passes_proper_dynamic_route_request_with_optional_params(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'x-api-key': 'some-key'},
            path='unit-test/v1/optional-params',
            proxy='optional-params',
            method='get',
            query={
                'group': 'some-group'
            }
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.schema_path,
            auto_validate=True
        )
        result = router.route(dynamic_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertEqual(200, result['statusCode'])
        self.assertDictEqual({'router_mapping_optional': True}, json_dict_response)

    def test_auto_validate_works_with_base_path_and_optional_params(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'x-api-key': 'some-key'},
            path='unit-test/v1/optional-params',
            proxy='optional-params',
            method='get',
            query={
                'group': 'some-group'
            }
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.base_path_schema_path,
            auto_validate=True
        )
        result = router.route(dynamic_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertEqual(200, result['statusCode'])
        self.assertDictEqual({'router_mapping_optional': True}, json_dict_response)

    def test_basic_mapping_routing_works_with_auto_validate_and_with_auth_function_called(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'x-api-key': 'some-key'},
            path='unit-test/v1/auto',
            proxy='auto',
            method='get'
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            with_auth=mock_middleware.mock_with_auth,
            schema=self.schema_path,
            auto_validate=True
        )
        router.route(dynamic_event, None)
        self.assertTrue(mock_middleware.mock_with_auth.has_been_called)

    def test_basic_mapping_routing_works_with_auto_validate_response_body(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'x-api-key': 'some-key'},
            path='unit-test/v1/auto',
            proxy='auto',
            method='get'
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.schema_path,
            auto_validate=True,
            validate_response=True
        )
        result = router.route(dynamic_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertEqual(200, result['statusCode'])
        self.assertDictEqual({'page_number': 1, 'data': {'id': '2'}}, json_dict_response)

    def test_basic_mapping_routing_fails_with_auto_validate_response_body(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'x-api-key': 'some-key'},
            path='unit-test/v1/auto',
            proxy='auto',
            method='put'
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.schema_path,
            auto_validate=True,
            validate_response=True
        )
        result = router.route(dynamic_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertEqual(500, result['statusCode'])
        self.assertDictEqual(
            {
                'errors': [
                    {
                        'key_path': 'root',
                        'message': "'data' is a required property"
                    },
                    {
                        'key_path': 'root',
                        'message': "Additional properties are not allowed ('bad-put' was unexpected)"
                    },
                    {
                        'key_path': 'response',
                        'message': 'There was a problem with the APIs response; does not match defined schema'
                    }
                ]
            },
            json_dict_response
        )

    def test_basic_mapping_routing_fails_with_response_body(self):
        dynamic_event = self.mock_request.get_dynamic_event(
            headers={'x-api-key': 'some-key'},
            path='unit-test/v1/auto',
            proxy='auto',
            method='delete'
        )
        router = Router(
            base_path=self.base_path,
            handlers=self.handler_mapping,
            schema=self.schema_path,
            validate_response=True
        )
        result = router.route(dynamic_event, None)
        json_dict_response = json.loads(result['body'])
        self.assertEqual(500, result['statusCode'])
        self.assertDictEqual(
            {
                'errors': [
                    {
                        'key_path': 'root',
                        'message': "'data' is a required property"
                    },
                    {
                        'key_path': 'root',
                        'message': "Additional properties are not allowed ('bad-delete' was unexpected)"
                    },
                    {
                        'key_path': 'response',
                        'message': 'There was a problem with the APIs response; does not match defined schema'
                    }
                ]
            },
            json_dict_response
        )
