from unittest import TestCase

import testhttpmodule
from edpdependencyinjection import DependencyLoader
from dihttpmessageprocessor import (
    AutoMappedDiRouterProcessorBuilder,
    AutoMappedWebApi,
    RouteDefinition,
)
from dihttpmessageprocessor._BaseRouteDefinition import BaseRouteDefinition
from httpmessageprocessor import RouteNotMapped
from httpmessageprocessorutil.automapped import AutoMappedMessageProcessor
from messageprocessor import MessageProcessor
from simplehttprouter import Route
from testhttpmodule.interactor import TestInteractor
from testhttpmodule.processor import (
    SimpleProcessor,
    TestBuildProcessor,
    TestProcessorWithParameter,
)


class TestAutoMappedEdgeCases(TestCase):

    def setUp(self):
        self.dependencies_loader = DependencyLoader()
        self.dependencies_loader.load_modules(modules=[testhttpmodule])
        self.dependency_context = self.dependencies_loader.build_context()

    def _build(self, route_definition=None):
        return AutoMappedDiRouterProcessorBuilder(
            dependency_context=self.dependency_context,
            route_definition=route_definition,
        )

    # --- None route_definition ---

    def test_none_route_definition_still_discovers_builders(self):
        builder = self._build(None)
        found_route, processor = builder.get_processor(Route("GET", "/build"))
        self.assertIsNotNone(found_route)
        self.assertIsInstance(processor, AutoMappedMessageProcessor)

    # --- Empty dict route_definition ---

    def test_empty_dict_still_discovers_builders(self):
        builder = self._build({})
        found_route, processor = builder.get_processor(Route("GET", "/build"))
        self.assertIsNotNone(found_route)
        self.assertIsInstance(processor, AutoMappedMessageProcessor)

    # --- Mixed routes: processor type + RouteDefinition + instance ---

    def test_mixed_route_types_in_same_builder(self):
        instance = SimpleProcessor()
        route_definition = {
            Route("GET", "/type"): SimpleProcessor,
            Route("GET", "/instance"): instance,
            Route("GET", "/definition"): RouteDefinition(interactor_type=TestInteractor),
        }
        builder = self._build(route_definition)

        _, proc_type = builder.get_processor(Route("GET", "/type"))
        self.assertIsInstance(proc_type, SimpleProcessor)

        _, proc_inst = builder.get_processor(Route("GET", "/instance"))
        self.assertIs(proc_inst, instance)

        _, proc_def = builder.get_processor(Route("GET", "/definition"))
        self.assertIsInstance(proc_def, AutoMappedMessageProcessor)

    # --- RouteNotMapped for unregistered routes ---

    def test_unregistered_route_raises_route_not_mapped(self):
        builder = self._build({Route("GET", "/exists"): SimpleProcessor})
        with self.assertRaises(RouteNotMapped):
            builder.get_processor(Route("GET", "/does-not-exist"))

    def test_wrong_method_raises_route_not_mapped(self):
        builder = self._build({Route("GET", "/path"): SimpleProcessor})
        with self.assertRaises(RouteNotMapped):
            builder.get_processor(Route("POST", "/path"))

    # --- AutoMappedWebApi backward compatibility ---

    def test_auto_mapped_web_api_is_route_definition(self):
        api = AutoMappedWebApi(interactor_type=TestInteractor)
        self.assertIsInstance(api, RouteDefinition)
        self.assertIsInstance(api, BaseRouteDefinition)

    def test_auto_mapped_web_api_behaves_like_route_definition(self):
        route_def = {
            Route("GET", "/a"): RouteDefinition(interactor_type=TestInteractor),
            Route("GET", "/b"): AutoMappedWebApi(interactor_type=TestInteractor),
        }
        builder = self._build(route_def)

        _, proc_a = builder.get_processor(Route("GET", "/a"))
        _, proc_b = builder.get_processor(Route("GET", "/b"))

        self.assertIsInstance(proc_a, AutoMappedMessageProcessor)
        self.assertIsInstance(proc_b, AutoMappedMessageProcessor)

    # --- RouteDefinition with all optional params ---

    def test_route_definition_with_custom_exception_map(self):
        route_def = {
            Route("GET", "/test"): RouteDefinition(
                interactor_type=TestInteractor,
                exception_map={ValueError: 400},
            ),
        }
        builder = self._build(route_def)
        _, processor = builder.get_processor(Route("GET", "/test"))
        self.assertIsInstance(processor, AutoMappedMessageProcessor)

    def test_route_definition_with_custom_success_status(self):
        route_def = {
            Route("POST", "/create"): RouteDefinition(
                interactor_type=TestInteractor,
                success_status=201,
            ),
        }
        builder = self._build(route_def)
        _, processor = builder.get_processor(Route("POST", "/create"))
        self.assertIsInstance(processor, AutoMappedMessageProcessor)

    # --- BaseRouteDefinition (non-RouteDefinition) in auto_mapped_definition ---

    def test_base_route_definition_with_processor_type(self):
        route_def = {
            Route("GET", "/base"): BaseRouteDefinition(
                processor_type=SimpleProcessor,
            ),
        }
        builder = self._build(route_def)
        _, processor = builder.get_processor(Route("GET", "/base"))
        self.assertIsInstance(processor, SimpleProcessor)

    def test_base_route_definition_skips_interactor_resolution(self):
        route_def = {
            Route("GET", "/base"): BaseRouteDefinition(
                processor_type=SimpleProcessor,
                params={"some_key": "some_value"},
            ),
        }
        builder = self._build(route_def)
        _, processor = builder.get_processor(Route("GET", "/base"))
        self.assertIsInstance(processor, SimpleProcessor)
        self.assertFalse(hasattr(processor, "interactor"))

    # --- Builder auto-discovery overrides ---

    def test_explicit_route_and_builder_route_coexist(self):
        route_def = {
            Route("GET", "/explicit"): RouteDefinition(interactor_type=TestInteractor),
        }
        builder = self._build(route_def)

        found_explicit, proc_explicit = builder.get_processor(Route("GET", "/explicit"))
        self.assertIsNotNone(found_explicit)
        self.assertIsInstance(proc_explicit, AutoMappedMessageProcessor)

        found_builder, proc_builder = builder.get_processor(Route("GET", "/build"))
        self.assertIsNotNone(found_builder)
        self.assertIsInstance(proc_builder, AutoMappedMessageProcessor)

    # --- Parametrized routes edge cases ---

    def test_parametrized_route_with_multiple_params(self):
        route_def = {
            Route("GET", "/a/{x}/b/{y}"): RouteDefinition(interactor_type=TestInteractor),
        }
        builder = self._build(route_def)
        found_route, processor = builder.get_processor(Route("GET", "/a/1/b/2"))
        self.assertIsNotNone(found_route)
        self.assertIsInstance(processor, AutoMappedMessageProcessor)

    def test_parametrized_processor_type_route(self):
        route_def = {
            Route("GET", "/items/{id}"): SimpleProcessor,
        }
        builder = self._build(route_def)
        found_route, processor = builder.get_processor(Route("GET", "/items/99"))
        self.assertIsInstance(processor, SimpleProcessor)

    # --- RouteDefinition with custom processor_type ---

    def test_route_definition_with_custom_processor_type(self):
        self.dependency_context.add_dependency_extra_parameter(
            parameter_name="injected_parameter",
            parameter_value="TestValue",
        )
        route_def = {
            Route("GET", "/custom"): RouteDefinition(
                interactor_type=TestInteractor,
                processor_type=TestProcessorWithParameter,
            ),
        }
        builder = self._build(route_def)
        _, processor = builder.get_processor(Route("GET", "/custom"))
        self.assertIsInstance(processor, TestProcessorWithParameter)
        self.assertIsInstance(getattr(processor, "interactor"), TestInteractor)
        self.assertEqual(getattr(processor, "injected_parameter"), "TestValue")

    # --- RouteDefinition extra kwargs forwarded to params ---

    def test_route_definition_kwargs_forwarded(self):
        self.dependency_context.add_dependency_extra_parameter(
            parameter_name="injected_parameter",
            parameter_value="FromExtra",
        )
        route_def = {
            Route("GET", "/kw"): RouteDefinition(
                interactor_type=TestInteractor,
                processor_type=TestProcessorWithParameter,
                extra_mapping={"key": "val"},
            ),
        }
        builder = self._build(route_def)
        _, processor = builder.get_processor(Route("GET", "/kw"))
        self.assertIsInstance(processor, TestProcessorWithParameter)

    # --- Builder registers itself in DI context ---

    def test_builder_is_registered_in_di_context(self):
        builder = self._build()
        resolved = self.dependency_context.get_instance(AutoMappedDiRouterProcessorBuilder)
        self.assertIs(resolved, builder)
