"""Step definitions for tdd_event_bus_exception_swallow.feature.

This test captures bug #988: ReactiveEventBus.emit() swallows exception
details.  The exception handler logs only ``type(exc).__name__`` and omits
``str(exc)`` and ``exc_info=True``, making production debugging impossible.

The scenario is tagged ``@tdd_expected_fail`` so the underlying assertion
failure (confirming the bug exists) is inverted to a CI pass.  Once the
fix for #988 is merged, remove the ``@tdd_expected_fail`` tag from the
feature file and this test will run normally as a regression guard.
"""

from __future__ import annotations

import structlog
from behave import given, then, when  # type: ignore[import-untyped]
from behave.runner import Context  # type: ignore[import-untyped]

from cleveragents.infrastructure.events.models import DomainEvent
from cleveragents.infrastructure.events.reactive import ReactiveEventBus
from cleveragents.infrastructure.events.types import EventType

# The distinctive error message that must appear in log output.
_ERROR_MESSAGE = "detailed error message for debugging"


def _failing_handler(event: DomainEvent) -> None:
    """Subscriber that always raises, simulating a real handler failure."""
    raise ValueError(_ERROR_MESSAGE)


@given("a ReactiveEventBus with a handler that raises a ValueError")
def step_given_bus_with_failing_handler(context: Context) -> None:
    """Create a ReactiveEventBus and subscribe a handler that always raises."""
    context.bus = ReactiveEventBus()
    context.bus.subscribe(EventType.PLAN_CREATED, _failing_handler)


@when("I emit an event that triggers the failing handler")
def step_when_emit_event(context: Context) -> None:
    """Emit a PLAN_CREATED event and capture structlog output."""
    with structlog.testing.capture_logs() as captured:
        context.bus.emit(DomainEvent(event_type=EventType.PLAN_CREATED))
    context.captured_logs = captured


@then("the warning log should contain the exception message text")
def step_then_log_contains_exception_message(context: Context) -> None:
    """Assert the exception message (``str(exc)``) appears in the log entry."""
    # Find the warning log entry from the emit() exception handler.
    warning_logs = [
        entry
        for entry in context.captured_logs
        if entry.get("log_level") == "warning"
        and entry.get("event") == "event_handler_failed"
    ]
    assert warning_logs, (
        f"Expected a 'event_handler_failed' warning log entry, "
        f"but captured logs were: {context.captured_logs}"
    )

    log_entry = warning_logs[0]

    # The fix for #988 must include str(exc) in the log entry — either as
    # a dedicated field (e.g. ``error_message``) or embedded in the event
    # string.  Assert that the distinctive error message text appears
    # somewhere in the log entry's values.
    log_values_str = " ".join(str(v) for v in log_entry.values())
    assert _ERROR_MESSAGE in log_values_str, (
        f"Bug #988: The exception message {_ERROR_MESSAGE!r} was not found "
        f"in the warning log entry. The handler logged only the exception "
        f"type name without the message. Log entry: {log_entry}"
    )


@then("the warning log should contain traceback information")
def step_then_log_contains_traceback(context: Context) -> None:
    """Assert the log entry includes ``exc_info`` for full traceback output.

    Bug #988 acceptance criteria require ``exc_info=True`` so that the
    traceback is available in production logs.  When structlog captures logs,
    the presence of an ``exc_info`` key (with a truthy value) confirms the
    logger was invoked with traceback forwarding enabled.
    """
    warning_logs = [
        entry
        for entry in context.captured_logs
        if entry.get("log_level") == "warning"
        and entry.get("event") == "event_handler_failed"
    ]
    assert warning_logs, (
        f"Expected a 'event_handler_failed' warning log entry, "
        f"but captured logs were: {context.captured_logs}"
    )

    log_entry = warning_logs[0]

    # structlog.testing.capture_logs() stores exc_info when the caller
    # passes exc_info=True.  Assert the key is present and truthy.
    assert log_entry.get("exc_info"), (
        f"Bug #988: The warning log entry is missing 'exc_info' (traceback). "
        f"The handler must call logger.warning(..., exc_info=True) so that "
        f"the full traceback appears in production logs. Log entry: {log_entry}"
    )
