from logging import getLogger
from unittest import TestCase

import yaml
from samplehttpmodule.messagehandlerbuilder import (
    DependencyInjectionSampleMessageHandlerBuilder,
    SampleMessageHandlerBuilder
)
from samplehttpmodule.route import add_descriptors

import edpopenapibuilder.builder.registry as _builder_registry
from edpopenapibuilder.builder.helper import build_from_message_handler_builder
from edpopenapibuilder.model import ServerModel, ServerVariableModel

_LOGGER = getLogger(__name__)


class TestBuilderHelper(TestCase):
    def setUp(self):
        add_descriptors()
        _builder_registry._DEFAULT_BUILDER = None

    def _build_yaml_from_di(self) -> dict:
        message_handler_builder = DependencyInjectionSampleMessageHandlerBuilder()
        builder = build_from_message_handler_builder(message_handler_builder)
        server = ServerModel(url='http://example.com', description='server description')
        server.add_variable('test_var', ServerVariableModel(description='Test Variable', default='test'))
        builder.get_model().add_server(server)
        result = builder.build_open_api_yaml()
        # _LOGGER.info(result)
        return yaml.safe_load(result)

    def _build_yaml_from_routes(self) -> dict:
        message_handler_builder = SampleMessageHandlerBuilder()
        builder = build_from_message_handler_builder(message_handler_builder)
        result = builder.build_open_api_yaml()
        # _LOGGER.info(result)
        return yaml.safe_load(result)

    # --- DI message handler builder tests ---

    def test_di_openapi_metadata(self):
        data = self._build_yaml_from_di()
        self.assertEqual(data['openapi'], '3.0.0')
        self.assertEqual(data['info']['title'], 'Open API Definition')

    def test_di_server(self):
        data = self._build_yaml_from_di()
        self.assertEqual(len(data['servers']), 1)
        self.assertEqual(data['servers'][0]['url'], 'http://example.com')
        self.assertEqual(data['servers'][0]['description'], 'server description')
        self.assertIn('test_var', data['servers'][0]['variables'])
        self.assertEqual(data['servers'][0]['variables']['test_var']['default'], 'test')

    def test_di_all_paths_present(self):
        paths = self._build_yaml_from_di()['paths']
        for expected in ['/oas/yaml', '/build', '/allTypes', '/allTypes/{integer_param}',
                         '/build/{param}', '/complex', '/listOfObject', '/listAliasOutput',
                         '/unionRandom', '/test-build', '/test-build/{param}']:
            self.assertIn(expected, paths)

    def test_di_all_types_get_descriptor_applied(self):
        all_types_get = self._build_yaml_from_di()['paths']['/allTypes']['get']
        self.assertEqual(all_types_get['summary'], 'All Types Route summary')
        self.assertEqual(all_types_get['tags'], ['alltypes', 'described'])

    def test_di_all_types_get_response_ref(self):
        schema = self._build_yaml_from_di()['paths']['/allTypes']['get']['responses']['200']['content']['application/json']['schema']
        self.assertEqual(schema, {'$ref': '#/components/schemas/AllTypesDto'})

    def test_di_all_types_post_request_body_ref(self):
        schema = self._build_yaml_from_di()['paths']['/allTypes']['post']['requestBody']['content']['application/json']['schema']
        self.assertEqual(schema, {'$ref': '#/components/schemas/AllTypesDto'})

    def test_di_complex_post_refs(self):
        complex_post = self._build_yaml_from_di()['paths']['/complex']['post']
        self.assertEqual(
            complex_post['requestBody']['content']['application/json']['schema'],
            {'$ref': '#/components/schemas/ComplexDto'},
        )
        self.assertEqual(
            complex_post['responses']['200']['content']['application/json']['schema'],
            {'$ref': '#/components/schemas/ComplexDto'},
        )

    def test_di_component_schemas_present(self):
        schemas = self._build_yaml_from_di()['components']['schemas']
        for name in ['AllTypesDto', 'ComplexDto', 'SimpleDto', 'ListResponse']:
            self.assertIn(name, schemas)
        for name in ['ArrayOfSimpleDto', 'ArrayOfListResponse']:
            self.assertNotIn(name, schemas)

    def test_di_all_types_dto_schema(self):
        schema = self._build_yaml_from_di()['components']['schemas']['AllTypesDto']
        self.assertEqual(schema['type'], 'object')
        self.assertCountEqual(
            schema['required'],
            ['integer_param', 'string_param', 'float_param', 'boolean_param', 'optional_param'],
        )

    def test_di_complex_dto_nested_array_ref(self):
        schemas = self._build_yaml_from_di()['components']['schemas']
        self.assertEqual(
            schemas['ComplexDto']['properties']['children'],
            {'type': 'array', 'items': {'$ref': '#/components/schemas/SimpleDto'}},
        )

    # --- Plain route builder tests ---

    def test_routes_openapi_metadata(self):
        data = self._build_yaml_from_routes()
        self.assertEqual(data['openapi'], '3.0.0')

    def test_routes_no_server(self):
        data = self._build_yaml_from_routes()
        self.assertNotIn('servers', data)

    def test_routes_all_paths_present(self):
        paths = self._build_yaml_from_routes()['paths']
        for expected in ['/build', '/allTypes', '/allTypes/{integer_param}',
                         '/build/{param}', '/complex', '/listOfObject', '/listAliasOutput',
                         '/unionRandom', '/test-build', '/test-build/{param}']:
            self.assertIn(expected, paths)

    def test_routes_all_types_get_descriptor_applied(self):
        all_types_get = self._build_yaml_from_routes()['paths']['/allTypes']['get']
        self.assertEqual(all_types_get['summary'], 'All Types Route summary')
        self.assertEqual(all_types_get['tags'], ['alltypes', 'described'])

    def test_routes_build_get_descriptor_applied(self):
        build_get = self._build_yaml_from_routes()['paths']['/build']['get']
        self.assertEqual(build_get['summary'], 'Builder Route summary')
        self.assertEqual(build_get['tags'], ['builder', 'described'])

    def test_routes_all_types_get_response_ref(self):
        schema = self._build_yaml_from_routes()['paths']['/allTypes']['get']['responses']['200']['content']['application/json']['schema']
        self.assertEqual(schema, {'$ref': '#/components/schemas/AllTypesDto'})

    def test_routes_complex_post_refs(self):
        complex_post = self._build_yaml_from_routes()['paths']['/complex']['post']
        self.assertEqual(
            complex_post['requestBody']['content']['application/json']['schema'],
            {'$ref': '#/components/schemas/ComplexDto'},
        )

    def test_routes_list_of_object_response_ref(self):
        schema = self._build_yaml_from_routes()['paths']['/listOfObject']['get']['responses']['200']['content']['application/json']['schema']
        self.assertEqual(schema, {'type': 'array', 'items': {'$ref': '#/components/schemas/ListResponse'}})

    def test_routes_component_schemas_present(self):
        schemas = self._build_yaml_from_routes()['components']['schemas']
        for name in ['AllTypesDto', 'ComplexDto', 'SimpleDto', 'ListResponse']:
            self.assertIn(name, schemas)
        for name in ['ArrayOfSimpleDto', 'ArrayOfListResponse']:
            self.assertNotIn(name, schemas)

    def test_routes_complex_dto_nested_array_ref(self):
        schemas = self._build_yaml_from_routes()['components']['schemas']
        self.assertEqual(
            schemas['ComplexDto']['properties']['children'],
            {'type': 'array', 'items': {'$ref': '#/components/schemas/SimpleDto'}},
        )
