from dataclasses import dataclass
from typing import Optional
from unittest import TestCase

from httpmessageprocessorutil.automapped import AutoMappedMessageProcessor
from simplehttprouter import Route

import edpopenapibuilder.builder.registry as _builder_registry
from edpopenapibuilder.builder.descriptors import path_descriptor, payload_descriptor
from edpopenapibuilder.builder.impl import OpenApiModelBuilderImpl


@dataclass
class _Req:
    name: Optional[str] = None


class _BaseInteractor:
    def execute(self, request: _Req) -> _Req:
        return request


class _ChildInteractor(_BaseInteractor):
    pass


class _BaseProcessor(AutoMappedMessageProcessor):
    pass


class _ChildProcessor(_BaseProcessor):
    pass


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

    def test_update_only_route_does_not_block_processor_create(self):
        """Regression: a catch-all route descriptor with only `update` must not
        short-circuit a processor descriptor's `create` callback."""
        calls = []

        def route_update(_b, _p, path):
            calls.append("route_update")
            path.tags.append("secured")
            return path

        def processor_create(builder, parameters):
            calls.append("processor_create")
            path = builder.build_interactor_path(parameters)
            path.tags.append("from_processor_create")
            return path

        builder = OpenApiModelBuilderImpl()
        builder.descriptors.add_route_descriptor(
            Route(".+", ".+"),
            path_descriptor(update=route_update),
        )
        builder.descriptors.add_processor_descriptor(
            _ChildProcessor,
            path_descriptor(create=processor_create),
        )

        processor = _ChildProcessor(request_type=_Req, interactor=_ChildInteractor())
        path = builder.add_processor_path(route=Route("POST", "/items"), processor=processor)

        self.assertIn("from_processor_create", path.tags)
        self.assertIn("secured", path.tags)
        self.assertEqual(calls, ["processor_create", "route_update"])

    def test_route_create_still_takes_priority_over_processor_create(self):
        def route_create(builder, parameters):
            path = builder.build_interactor_path(parameters)
            path.tags.append("from_route_create")
            return path

        def processor_create(builder, parameters):
            path = builder.build_interactor_path(parameters)
            path.tags.append("from_processor_create")
            return path

        builder = OpenApiModelBuilderImpl()
        builder.descriptors.add_route_descriptor(
            Route("POST", "/items"),
            path_descriptor(create=route_create),
        )
        builder.descriptors.add_processor_descriptor(
            _ChildProcessor,
            path_descriptor(create=processor_create),
        )

        processor = _ChildProcessor(request_type=_Req, interactor=_ChildInteractor())
        path = builder.add_processor_path(route=Route("POST", "/items"), processor=processor)

        self.assertIn("from_route_create", path.tags)
        self.assertNotIn("from_processor_create", path.tags)

    def test_interactor_create_runs_when_route_and_processor_have_no_create(self):
        def interactor_create(builder, parameters):
            path = builder.build_interactor_path(parameters)
            path.tags.append("from_interactor_create")
            return path

        builder = OpenApiModelBuilderImpl()
        builder.descriptors.add_route_descriptor(
            Route(".+", ".+"),
            path_descriptor(update=lambda _b, _p, path: path),
        )
        builder.descriptors.add_interactor_descriptor(
            _ChildInteractor,
            path_descriptor(create=interactor_create),
        )

        processor = _BaseProcessor(request_type=_Req, interactor=_ChildInteractor())
        path = builder.add_processor_path(route=Route("POST", "/items"), processor=processor)

        self.assertIn("from_interactor_create", path.tags)


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

    def test_all_update_callbacks_fire_in_priority_order(self):
        order = []

        def route_update(_b, _p, path):
            order.append("route")
            path.tags.append("route")
            return path

        def processor_update(_b, _p, path):
            order.append("processor")
            path.tags.append("processor")
            return path

        def interactor_update(_b, _p, path):
            order.append("interactor")
            path.tags.append("interactor")
            return path

        builder = OpenApiModelBuilderImpl()
        builder.descriptors.add_route_descriptor(
            Route(".+", ".+"),
            path_descriptor(update=route_update),
        )
        builder.descriptors.add_processor_descriptor(
            _ChildProcessor,
            path_descriptor(update=processor_update),
        )
        builder.descriptors.add_interactor_descriptor(
            _ChildInteractor,
            path_descriptor(update=interactor_update),
        )

        processor = _ChildProcessor(request_type=_Req, interactor=_ChildInteractor())
        path = builder.add_processor_path(route=Route("POST", "/items"), processor=processor)

        self.assertEqual(order, ["route", "processor", "interactor"])
        self.assertEqual(path.tags, ["route", "processor", "interactor"])

    def test_multiple_route_descriptors_all_fire(self):
        order = []

        def exact_update(_b, _p, path):
            order.append("exact")
            return path

        def regex_update(_b, _p, path):
            order.append("regex")
            return path

        builder = OpenApiModelBuilderImpl()
        builder.descriptors.add_route_descriptor(
            Route(".+", ".+"),
            path_descriptor(update=regex_update),
        )
        builder.descriptors.add_route_descriptor(
            Route("POST", "/items"),
            path_descriptor(update=exact_update),
        )

        processor = _BaseProcessor(request_type=_Req, interactor=_BaseInteractor())
        builder.add_processor_path(route=Route("POST", "/items"), processor=processor)

        self.assertEqual(order, ["exact", "regex"])

    def test_add_interactor_path_runs_route_and_interactor_updates(self):
        order = []

        builder = OpenApiModelBuilderImpl()
        builder.descriptors.add_route_descriptor(
            Route(".+", ".+"),
            path_descriptor(update=lambda _b, _p, path: (order.append("route") or path)),
        )
        builder.descriptors.add_interactor_descriptor(
            _BaseInteractor,
            path_descriptor(update=lambda _b, _p, path: (order.append("interactor") or path)),
        )

        from edpopenapibuilder.builder.dto import build_params
        params = build_params(route=Route("GET", "/thing"))
        params["interactor_type"] = _BaseInteractor
        params["request_type"] = _Req
        builder.add_interactor_path(params)

        self.assertEqual(order, ["route", "interactor"])


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

    def test_update_only_route_does_not_block_processor_build_params(self):
        from edpopenapibuilder.builder.dto import build_params

        def processor_build_params(builder, route, processor):
            params = build_params(route=route)
            params["interactor_type"] = _BaseInteractor
            params["request_type"] = _Req
            params["processor_type"] = type(processor)
            params["processor_instance"] = processor
            params["from_processor_build_params"] = True
            return params

        builder = OpenApiModelBuilderImpl()
        builder.descriptors.add_route_descriptor(
            Route(".+", ".+"),
            path_descriptor(update=lambda _b, _p, path: path),
        )
        builder.descriptors.add_processor_descriptor(
            _BaseProcessor,
            path_descriptor(build_params=processor_build_params),
        )

        processor = _BaseProcessor(request_type=_Req, interactor=_BaseInteractor())
        result = builder.build_processor_params(
            route=Route("POST", "/items"),
            processor=processor,
        )

        self.assertTrue(result.get("from_processor_build_params"))


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

    def test_payload_create_first_hit_update_runs_all(self):
        order = []

        def base_update(_b, _p, schema, _path):
            order.append("base_update")
            return schema

        def regex_update(_b, _p, schema, _path):
            order.append("regex_update")
            return schema

        def base_create(builder, parameters, path_model):
            order.append("base_create")
            return builder.build_schema_model(
                schema_type=parameters["request_type"],
                parameters=parameters,
                path_model=path_model,
            )

        builder = OpenApiModelBuilderImpl()
        builder.descriptors.add_payload_descriptor(
            _Req,
            payload_descriptor(create=base_create, update=base_update),
        )
        builder.descriptors.add_payload_descriptor(
            r"_Req",
            payload_descriptor(update=regex_update),
        )

        processor = _BaseProcessor(request_type=_Req, interactor=_BaseInteractor())
        builder.add_processor_path(route=Route("POST", "/items"), processor=processor)

        self.assertIn("base_create", order)
        self.assertIn("base_update", order)
        self.assertIn("regex_update", order)
