"""ReportProcessor — pluggable parse, business and sending with built-in error
reporting. Tests drive through handle_event with a BaseSender so outcomes are
observable on the InMemoryMessageBus.
"""

import json
import logging

from messagebus import Event

from messageprocessorutil import IdentityDataSerializer

from boh.report.processor import ReportProcessor
from boh.report.sender import BaseSender

logger = logging.getLogger("test")


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

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


class _FailingInteractor:
    def execute(self, request):
        raise ValueError("db error")


# --- happy path -----------------------------------------------------------


def test_sends_serialized_result_on_success(build_handler, message_bus):
    processor = ReportProcessor(
        report_name="TestReport",
        parser=lambda data, event_type: data,
        logger=logger,
        interactor=_SimpleInteractor({"rows": [1, 2]}),
        serializer=IdentityDataSerializer(),
        sender=BaseSender(),
    )
    build_handler(event_processors={"test.report": processor}).handle_event(
        message_bus, Event("test.report", None, b'{"report-id": "r1"}')
    )

    dispatched = json.loads(message_bus.published_events[0].data)
    assert dispatched["report-id"] == "r1"
    assert dispatched["data"] == {"rows": [1, 2]}


def test_applies_custom_serializer_to_result(build_handler, message_bus):
    processor = ReportProcessor(
        report_name="TestReport",
        parser=lambda data, event_type: data,
        logger=logger,
        interactor=_SimpleInteractor({"raw": True}),
        serializer=lambda result: {"formatted": result},
        sender=BaseSender(),
    )
    build_handler(event_processors={"test.report": processor}).handle_event(
        message_bus, Event("test.report", None, b'{"report-id": "r1"}')
    )

    dispatched = json.loads(message_bus.published_events[0].data)
    assert dispatched["data"] == {"formatted": {"raw": True}}


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

    def _capturing_parser(data, event_type):
        received["data"] = data
        return data

    processor = ReportProcessor(
        report_name="TestReport",
        parser=_capturing_parser,
        logger=logger,
        interactor=_SimpleInteractor({}),
        serializer=IdentityDataSerializer(),
        sender=BaseSender(),
    )
    build_handler(event_processors={"test.report": processor}).handle_event(
        message_bus, Event("test.report", None, b'{"report-id": "r99", "store-id": "S1"}')
    )

    assert received["data"]["report-id"] == "r99"
    assert received["data"]["store-id"] == "S1"


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


def test_sends_error_event_on_business_failure(build_handler, message_bus):
    processor = ReportProcessor(
        report_name="TestReport",
        parser=lambda data, event_type: data,
        logger=logger,
        interactor=_FailingInteractor(),
        sender=BaseSender(),
    )
    build_handler(event_processors={"test.report": processor}).handle_event(
        message_bus, Event("test.report", None, b'{"report-id": "r1"}')
    )

    dispatched = json.loads(message_bus.published_events[0].data)
    assert dispatched["error"] is True
    assert dispatched["report-id"] == "r1"
    assert "db error" in dispatched["report-error-message"]


def test_sends_error_event_on_parse_failure(build_handler, message_bus):
    def _bad_parser(data, event_type):
        raise ValueError("bad payload")

    processor = ReportProcessor(
        report_name="TestReport",
        parser=_bad_parser,
        logger=logger,
        sender=BaseSender(),
    )
    build_handler(event_processors={"test.report": processor}).handle_event(
        message_bus, Event("test.report", None, b'{"report-id": "r1"}')
    )

    dispatched = json.loads(message_bus.published_events[0].data)
    assert dispatched["error"] is True
    assert "bad payload" in dispatched["report-error-message"]


def test_error_event_does_not_include_report_id_on_parse_failure(build_handler, message_bus):
    processor = ReportProcessor(
        report_name="TestReport",
        parser=lambda data, event_type: (_ for _ in ()).throw(ValueError("fail")),
        logger=logger,
        sender=BaseSender(),
    )
    build_handler(event_processors={"test.report": processor}).handle_event(
        message_bus, Event("test.report", None, b'{"report-id": "r1"}')
    )

    dispatched = json.loads(message_bus.published_events[0].data)
    assert "report-id" not in dispatched
