"""JsonUtf8EventProcessor — pluggable parse, business and sending over a standard event.

All tests drive through handle_event so the real executor and lifecycle
participate. The sender captures what JsonUtf8EventProcessor passes to it so tests
assert on the data handed off, not on message bus internals.
"""

from messagebus import Event

from messageprocessorutil import JsonUtf8EventProcessor, Sender


class _RecordingSender(Sender):
    """Captures the last send / send_error / send_parser_error call so tests can assert on what was passed."""

    def __init__(self):
        self.sent_data = None
        self.error = None
        self.parser_error = None

    def send(self, message_bus, original_event, data, original_payload):
        self.sent_data = data

    def send_error(self, message_bus, original_event, data, original_payload):
        self.error = {"data": data, "original_payload": original_payload}

    def send_parser_error(self, message_bus, original_event, data, original_payload):
        self.parser_error = {"data": data, "original_payload": original_payload}


class _FailingInteractor:
    def __init__(self, exception):
        self._exception = exception

    def execute(self, request):
        raise self._exception


class _SimpleInteractor:
    def __init__(self, result):
        self._result = result

    def execute(self, request):
        return self._result


# --- parsing and business -------------------------------------------------


def test_parses_payload_and_passes_request_to_interactor(build_handler, message_bus):
    received = {}

    class _CapturingInteractor:
        def execute(self, request):
            received["request"] = request
            return {}

    processor = JsonUtf8EventProcessor(
        interactor=_CapturingInteractor(),
        parser=lambda data, event_type: {"parsed": data.get("id")},
        sender=_RecordingSender(),
    )
    build_handler(event_processors={"order.created": processor}).handle_event(
        message_bus, Event("order.created", None, b'{"id": 99}')
    )

    assert received["request"] == {"parsed": 99}


def test_returns_none_from_parse_when_no_parser_configured(build_handler, message_bus):
    received = {}

    class _CapturingInteractor:
        def execute(self, request):
            received["request"] = request
            return {}

    processor = JsonUtf8EventProcessor(
        interactor=_CapturingInteractor(),
        parser=None,
    )
    build_handler(event_processors={"order.created": processor}).handle_event(
        message_bus, Event("order.created", None, b'{"id": 99}')
    )

    assert received["request"] is None


# --- sending --------------------------------------------------------------


def test_sends_serialized_result_when_sender_is_provided(build_handler, message_bus):
    sender = _RecordingSender()
    processor = JsonUtf8EventProcessor(
        interactor=_SimpleInteractor({"total": 100}),
        parser=lambda data, event_type: data,
        serializer=lambda result: {"serialized": result},
        sender=sender,
    )
    build_handler(event_processors={"order.created": processor}).handle_event(
        message_bus, Event("order.created", None, b"{}")
    )

    assert sender.sent_data == {"serialized": {"total": 100}}


def test_uses_identity_serializer_when_no_serializer_given(build_handler, message_bus):
    sender = _RecordingSender()
    processor = JsonUtf8EventProcessor(
        interactor=_SimpleInteractor({"total": 100}),
        sender=sender,
    )
    build_handler(event_processors={"order.created": processor}).handle_event(
        message_bus, Event("order.created", None, b"{}")
    )

    assert sender.sent_data == {"total": 100}


def test_does_not_call_sender_when_none_configured(build_handler, message_bus):
    processor = JsonUtf8EventProcessor(
        interactor=_SimpleInteractor({}),
    )
    build_handler(event_processors={"order.created": processor}).handle_event(
        message_bus, Event("order.created", None, b"{}")
    )

    assert message_bus.published_events == []
    assert message_bus.event_replies == []


# --- error handling -------------------------------------------------------


def test_business_exception_is_routed_to_send_error(build_handler, message_bus):
    boom = ValueError("boom")
    sender = _RecordingSender()
    processor = JsonUtf8EventProcessor(
        interactor=_FailingInteractor(boom),
        parser=lambda data, event_type: {"parsed": data.get("id")},
        sender=sender,
    )
    build_handler(event_processors={"order.created": processor}).handle_event(
        message_bus, Event("order.created", None, b'{"id": 99}')
    )

    # The serialized error dict is handed to send_error; success send never runs.
    assert sender.error == {
        "data": {"error": "ValueError", "message": "boom"},
        "original_payload": {"id": 99},
    }
    assert sender.sent_data is None
    assert sender.parser_error is None


def test_parse_exception_is_routed_to_send_parser_error_with_raw_data(build_handler, message_bus):
    sender = _RecordingSender()
    processor = JsonUtf8EventProcessor(
        interactor=_SimpleInteractor({}),
        parser=lambda data, event_type: data,
        sender=sender,
    )
    build_handler(event_processors={"order.created": processor}).handle_event(
        message_bus, Event("order.created", None, b"not json")
    )

    # json.loads fails before any parsing: serialized error reaches send_parser_error;
    # payload is still empty since it was never assigned.
    assert sender.parser_error["data"]["error"] == "JSONDecodeError"
    assert sender.parser_error["original_payload"] == {}
    assert sender.sent_data is None
    assert sender.error is None


def test_parser_callable_exception_is_routed_to_send_parser_error(build_handler, message_bus):
    boom = ValueError("cannot parse")

    def _failing_parser(data, event_type):
        raise boom

    sender = _RecordingSender()
    processor = JsonUtf8EventProcessor(
        interactor=_SimpleInteractor({}),
        parser=_failing_parser,
        sender=sender,
    )
    build_handler(event_processors={"order.created": processor}).handle_event(
        message_bus, Event("order.created", None, b'{"id": 99}')
    )

    # The payload decoded fine so original_payload is populated; the parser's
    # failure routes to send_parser_error with the serialized error dict.
    assert sender.parser_error == {
        "data": {"error": "ValueError", "message": "cannot parse"},
        "original_payload": {"id": 99},
    }
    assert sender.sent_data is None
    assert sender.error is None


def test_business_exception_is_swallowed_when_no_sender_configured(build_handler, message_bus):
    processor = JsonUtf8EventProcessor(
        interactor=_FailingInteractor(ValueError("boom")),
        parser=lambda data, event_type: data,
    )

    # Fire-and-forget: format_exception is a no-op, so nothing escapes the handler.
    build_handler(event_processors={"order.created": processor}).handle_event(
        message_bus, Event("order.created", None, b"{}")
    )

    assert message_bus.published_events == []
    assert message_bus.event_replies == []


def test_parse_exception_is_swallowed_when_no_sender_configured(build_handler, message_bus):
    processor = JsonUtf8EventProcessor(
        interactor=_SimpleInteractor({}),
        parser=lambda data, event_type: data,
    )

    # Fire-and-forget: format_parse_exception is a no-op, so nothing escapes.
    build_handler(event_processors={"order.created": processor}).handle_event(
        message_bus, Event("order.created", None, b"not json")
    )

    assert message_bus.published_events == []
    assert message_bus.event_replies == []