"""How the handler routes incoming events to the right processor.

The handler is exercised the way the message bus drives it: build it with a map
of processors, call ``handle_event``, and observe the outcome on the in-memory
bus. A ``MarkerProcessor`` publishes an event tagged with its own name when it
runs, so the test can see *which* processor handled the event.
"""
import pytest

from messagebus import Event

from messageprocessor import EventMessageProcessorBuilder, MessageProcessorMessageHandler

from testutil import MarkerProcessor


def handled_by(message_bus):
    """The names of the processors that ran, in order, read off the bus."""
    return [event.subject for event in message_bus.published_events]


def test_routes_event_to_processor_mapped_by_subject(build_handler, message_bus):
    handler = build_handler(event_processors={"orders": MarkerProcessor("orders-processor")})

    handler.handle_event(message_bus, Event("orders", None, b"payload"))

    assert handled_by(message_bus) == ["orders-processor"]


def test_routes_event_by_subject_and_type_when_that_key_exists(build_handler, message_bus):
    handler = build_handler(event_processors={"orders_created": MarkerProcessor("created-processor")})

    handler.handle_event(message_bus, Event("orders", "created", b""))

    assert handled_by(message_bus) == ["created-processor"]


def test_prefers_subject_and_type_mapping_over_subject_only(build_handler, message_bus):
    handler = build_handler(event_processors={
        "orders": MarkerProcessor("subject-only"),
        "orders_created": MarkerProcessor("subject-and-type"),
    })

    handler.handle_event(message_bus, Event("orders", "created", b""))

    assert handled_by(message_bus) == ["subject-and-type"]


def test_falls_back_to_subject_mapping_when_no_subject_type_key(build_handler, message_bus):
    handler = build_handler(event_processors={"orders": MarkerProcessor("subject-only")})

    handler.handle_event(message_bus, Event("orders", "created", b""))

    assert handled_by(message_bus) == ["subject-only"]


def test_passes_event_data_through_to_the_processor(build_handler, message_bus):
    handler = build_handler(event_processors={"orders": MarkerProcessor("orders-processor")})

    handler.handle_event(message_bus, Event("orders", None, b"the-payload"))

    assert message_bus.published_events[0].data == b"the-payload"


def test_raises_event_not_mapped_when_no_processor_matches(build_handler, message_bus):
    handler = build_handler(event_processors={"orders": MarkerProcessor("orders-processor")})

    with pytest.raises(MessageProcessorMessageHandler.EventNotMapped) as exc_info:
        handler.handle_event(message_bus, Event("unknown", "type", b""))

    assert exc_info.value.subject == "unknown"
    assert exc_info.value.type == "type"
    assert handled_by(message_bus) == []


def test_raises_event_not_mapped_when_no_event_processors_configured(build_handler, message_bus):
    handler = build_handler(event_processors=None)

    with pytest.raises(MessageProcessorMessageHandler.EventNotMapped):
        handler.handle_event(message_bus, Event("orders", "created", b""))


def test_builds_event_processor_lazily_from_a_builder(build_handler, message_bus):
    class RecordingBuilder(EventMessageProcessorBuilder):
        def __init__(self):
            self.received_event_key = None

        def build_message_processor(self, event_key):
            self.received_event_key = event_key
            return MarkerProcessor("lazily-built")

    builder = RecordingBuilder()
    handler = build_handler(event_processors={"orders": builder})

    handler.handle_event(message_bus, Event("orders", None, b""))

    assert builder.received_event_key == "orders"
    assert handled_by(message_bus) == ["lazily-built"]


def test_counts_handled_events_per_subject_when_a_counter_is_given(build_handler, message_bus):
    counter = {}
    handler = build_handler(
        event_processors={"orders": MarkerProcessor("orders-processor")},
        subject_counter=counter,
    )

    handler.handle_event(message_bus, Event("orders", None, b""))
    handler.handle_event(message_bus, Event("orders", None, b""))

    assert counter == {"orders": 2}
