"""Step definitions for tdd_reactive_event_bus_close.feature.

TDD tests for issue #10377 / bug #10378:
ReactiveEventBus._subject (RxPY Subject) is never completed — missing
close() method causes RxPY subscriber resource leaks.
"""

from __future__ import annotations

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

from cleveragents.infrastructure.events import EventType, ReactiveEventBus
from cleveragents.infrastructure.events.models import DomainEvent


def _make_event(event_type_str: str) -> DomainEvent:
    return DomainEvent(event_type=EventType(event_type_str))


# ---------------------------------------------------------------------------
# Given
# ---------------------------------------------------------------------------


@given("a ReactiveEventBus instance for close testing")
def step_given_bus_for_close(ctx: Context) -> None:
    ctx.bus = ReactiveEventBus()
    ctx.completed: list[bool] = []
    ctx.close_exception: Exception | None = None
    ctx.emit_exception: Exception | None = None


@given("a subscriber collecting completion signal from bus stream")
def step_given_completion_subscriber(ctx: Context) -> None:
    ctx.completed = []
    ctx.bus.stream.subscribe(
        on_next=lambda _: None,
        on_error=lambda _: None,
        on_completed=lambda: ctx.completed.append(True),
    )


@given("a ReactiveEventBus instance used as a context manager")
def step_given_bus_as_context_manager(ctx: Context) -> None:
    ctx.bus = ReactiveEventBus()
    ctx.context_manager_closed: bool = False
    ctx.completed = []
    ctx.bus.stream.subscribe(
        on_next=lambda _: None,
        on_error=lambda _: None,
        on_completed=lambda: ctx.completed.append(True),
    )


# ---------------------------------------------------------------------------
# When
# ---------------------------------------------------------------------------


@when("bus.close() is called")
def step_when_close_called(ctx: Context) -> None:
    try:
        ctx.bus.close()
    except Exception as exc:
        ctx.close_exception = exc


@when("an event is emitted after close")
def step_when_emit_after_close(ctx: Context) -> None:
    try:
        ctx.bus.emit(_make_event("plan.created"))
    except RuntimeError as exc:
        ctx.emit_exception = exc


@when("the context manager exits")
def step_when_context_manager_exits(ctx: Context) -> None:
    with ctx.bus:
        pass
    ctx.context_manager_closed = True


@when("bus.close() is called twice")
def step_when_close_called_twice(ctx: Context) -> None:
    try:
        ctx.bus.close()
        ctx.bus.close()
    except Exception as exc:
        ctx.close_exception = exc


# ---------------------------------------------------------------------------
# Then
# ---------------------------------------------------------------------------


@then("the subscriber should have received the on_completed signal")
def step_then_completed_signal_received(ctx: Context) -> None:
    assert ctx.completed, (
        "Expected on_completed to be called on the RxPY stream after close(), "
        "but no completion signal was received"
    )


@then("a RuntimeError should be raised")
def step_then_runtime_error_raised(ctx: Context) -> None:
    assert isinstance(ctx.emit_exception, RuntimeError), (
        f"Expected RuntimeError when emitting after close(), "
        f"got {type(ctx.emit_exception).__name__!r}: {ctx.emit_exception}"
    )


@then("bus.close() should have been called automatically")
def step_then_close_called_automatically(ctx: Context) -> None:
    assert ctx.context_manager_closed, "Context manager did not exit as expected"
    assert ctx.bus._closed, (
        "Expected bus._closed to be True after context manager exit, "
        "indicating close() was called"
    )


@then("the RxPY stream should be completed")
def step_then_rxpy_stream_completed(ctx: Context) -> None:
    assert ctx.completed, (
        "Expected on_completed to be called on the RxPY stream after context "
        "manager exit, but no completion signal was received"
    )


@then("no exception should be raised on the second close call")
def step_then_no_exception_on_second_close(ctx: Context) -> None:
    assert ctx.close_exception is None, (
        f"Expected no exception on second close(), got: {ctx.close_exception}"
    )
