"""
Additional step definitions to cover remaining ReactiveStreamRouter branches.
"""

from __future__ import annotations

import rx
from behave import given, then, when
from behave.runner import Context
from rx.subject import ReplaySubject, Subject

from cleveragents.reactive.stream_router import (
    StreamConfig,
    StreamMessage,
    StreamRoutingError,
    StreamType,
)


@when("I copy the message with explicit metadata override")
def step_copy_with_override(context: Context) -> None:
    # assumes context.original_message provided by other steps
    override = {"override": True}
    context.copied_message = context.original_message.copy_with(metadata=override)


@then("the copy should use the provided metadata")
def step_verify_copy_override(context: Context) -> None:
    assert context.copied_message.metadata == {"override": True}


@when('I create a hot stream named "hot_stream" with initial value 5')
def step_create_hot_stream(context: Context) -> None:
    config = StreamConfig(name="hot_stream", type=StreamType.HOT, initial_value=5)
    stream = context.stream_router.create_stream(config)
    context.created_stream = stream


@then("the hot stream should store the initial value")
def step_verify_hot_stream(context: Context) -> None:
    assert "hot_stream" in context.stream_router.streams
    hot_stream = context.created_stream
    # BehaviorSubject holds the last value; get value via .value
    assert hasattr(hot_stream, "value")
    assert hot_stream.value == 5


@when('I create a replay stream named "replay_stream" with buffer size 3')
def step_create_replay_stream(context: Context) -> None:
    config = StreamConfig(
        name="replay_stream", type=StreamType.REPLAY, buffer_size=3, initial_value=1
    )
    stream = context.stream_router.create_stream(config)
    context.created_stream = stream


@then("the replay stream should be created with buffer size 3")
def step_verify_replay_stream(context: Context) -> None:
    assert "replay_stream" in context.stream_router.streams
    replay_stream = context.created_stream
    assert isinstance(replay_stream, ReplaySubject)


@when('I build a langgraph operator "graph_execute" without bridge')
def step_langgraph_without_bridge(context: Context) -> None:
    context.error = None
    try:
        context.stream_router._create_operator({"type": "graph_execute", "params": {}})
    except Exception as exc:  # pylint: disable=broad-except
        context.error = exc


@then("I should get a stream routing error about missing langgraph bridge")
def step_verify_langgraph_error(context: Context) -> None:
    assert isinstance(context.error, StreamRoutingError)
    assert "LangGraph operator" in str(context.error)


@when('I build a map operator targeting unknown agent "ghost"')
def step_map_unknown_agent(context: Context) -> None:
    context.error = None
    try:
        context.stream_router._create_operator(
            {"type": "map", "params": {"agent": "ghost"}}
        )
    except Exception as exc:  # pylint: disable=broad-except
        context.error = exc


@then("I should get a stream routing error about missing agent")
def step_verify_missing_agent(context: Context) -> None:
    assert isinstance(context.error, StreamRoutingError)
    assert "not found" in str(context.error)


@when("I build debounce, throttle, and delay operators")
def step_build_temporal_ops(context: Context) -> None:
    context.debounce_op = context.stream_router._create_operator(
        {"type": "debounce", "params": {"duration": 0.03}}
    )
    context.throttle_op = context.stream_router._create_operator(
        {"type": "throttle", "params": {"duration": 0.1}}
    )
    context.delay_op = context.stream_router._create_operator(
        {"type": "delay", "params": {"duration": 0.01}}
    )


@then("the temporal operators should be created successfully")
def step_verify_temporal_ops(context: Context) -> None:
    assert context.debounce_op is not None
    assert context.throttle_op is not None
    assert context.delay_op is not None


@when("I build buffer and window operators with count 2")
def step_build_buffer_window(context: Context) -> None:
    context.buffer_op = context.stream_router._create_operator(
        {"type": "buffer", "params": {"count": 2, "timeout": 0.1}}
    )
    context.window_op = context.stream_router._create_operator(
        {"type": "window", "params": {"count": 2}}
    )


@then("the buffer and window operators should be created successfully")
def step_verify_buffer_window(context: Context) -> None:
    assert context.buffer_op is not None
    assert context.window_op is not None


@when("I build take and skip operators")
def step_build_take_skip(context: Context) -> None:
    context.take_op = context.stream_router._create_operator(
        {"type": "take", "params": {"count": 1}}
    )
    context.skip_op = context.stream_router._create_operator(
        {"type": "skip", "params": {"count": 1}}
    )


@then("the take and skip operators should be created successfully")
def step_verify_take_skip(context: Context) -> None:
    assert context.take_op is not None
    assert context.skip_op is not None


@given('I have two source streams named "merge_x" and "merge_y"')
def step_create_merge_sources_additional(context: Context) -> None:
    context.stream_router.create_stream("merge_x")
    context.stream_router.create_stream("merge_y")


@when("I create a merge operator for those streams")
def step_build_merge_operator(context: Context) -> None:
    context.merge_op = context.stream_router._create_operator(
        {"type": "merge", "params": {"streams": ["merge_x", "merge_y"]}}
    )


@then("the merge operator should be usable")
def step_verify_merge_operator(context: Context) -> None:
    captured: list[str] = []
    rx.merge(rx.just("a"), rx.just("b")).pipe(context.merge_op).subscribe(
        captured.append
    )
    assert captured == ["a", "b"]


@when("I build an accumulate operator with sum")
def step_build_accumulate_sum(context: Context) -> None:
    context.accumulate_op = context.stream_router._create_operator(
        {"type": "accumulate", "params": {"accumulator": {"op": "sum"}}}
    )


@then("the accumulate operator should sum incoming numbers")
def step_verify_accumulate_sum(context: Context) -> None:
    captured: list[int] = []
    rx.from_([1, 2]).pipe(context.accumulate_op).subscribe(captured.append)
    assert captured[-1] == 3


@when("I apply an extract_field transform to a dictionary message")
def step_apply_extract_transform(context: Context) -> None:
    context.extract_result = context.stream_router._apply_transform(
        {"foo": 42}, {"type": "extract_field", "field": "foo"}
    )


@then("the extracted field should be returned")
def step_verify_extract_transform(context: Context) -> None:
    assert context.extract_result == 42


@when("I evaluate empty and equals conditions against messages")
def step_evaluate_conditions(context: Context) -> None:
    context.condition_empty = context.stream_router._evaluate_condition({}, {})
    context.condition_equals = context.stream_router._evaluate_condition(
        StreamMessage(content="value"), {"equals": "value"}
    )


@then("the empty condition should be true and equals should match")
def step_verify_conditions(context: Context) -> None:
    assert context.condition_empty is True
    assert context.condition_equals is True


class _AgentSuccess:
    def process_message_sync(self, content, metadata):  # type: ignore[override]
        return f"ok:{content}"


class _AgentFailure:
    def process_message_sync(self, content, metadata):  # type: ignore[override]
        raise RuntimeError("fail")


@when("I map messages through agent mapper success and failure paths")
def step_agent_mapper_paths(context: Context) -> None:
    success_mapper = context.stream_router._create_agent_mapper(_AgentSuccess())
    failure_mapper = context.stream_router._create_agent_mapper(_AgentFailure())
    context.success_result = success_mapper("hi").content
    context.failure_result = failure_mapper("hi").content


@then("the mapper should emit processed content and blank on error")
def step_verify_agent_mapper_paths(context: Context) -> None:
    assert context.success_result == "ok:hi"
    assert context.failure_result == ""


@when("I apply append accumulator to non-list accumulator")
def step_append_non_list(context: Context) -> None:
    context.append_result = context.stream_router._apply_accumulator(
        0, "x", {"op": "append"}
    )


@then("the accumulator should remain unchanged")
def step_verify_append_non_list(context: Context) -> None:
    assert context.append_result == 0


@when("I setup subscriptions including a missing source")
def step_setup_subscriptions_missing(context: Context) -> None:
    config = StreamConfig(name="target_sub", subscriptions=["missing_source"])
    context.stream_router.create_stream(config)
    context.subscription_count = len(context.stream_router.subscriptions)


@then("no subscription should be added for the missing source")
def step_verify_missing_subscription(context: Context) -> None:
    assert context.subscription_count == 0


@given('I have a stream "source_split" and target "target_split"')
def step_create_split_streams(context: Context) -> None:
    context.stream_router.create_stream("source_split")
    context.stream_router.create_stream("target_split")


@when("I split the source stream with a matching condition")
def step_split_with_condition(context: Context) -> None:
    condition = {"target": "target_split", "condition": {"equals": "route"}}
    context.stream_router.split_stream("source_split", [condition])
    captured: list[str] = []
    context.stream_router.streams["target_split"].subscribe(
        lambda v: captured.append(v.content)
    )
    context.stream_router.send_message("source_split", "route")
    context.split_captured = captured


@then("the target stream should receive the routed message")
def step_verify_split_routing(context: Context) -> None:
    assert context.split_captured == ["route"]


@when("I subscribe to output and error streams and emit messages")
def step_subscribe_output_error(context: Context) -> None:
    output_captured: list[str] = []
    error_captured: list[str] = []
    context.stream_router.subscribe_to_output(
        lambda v: output_captured.append(v.content)
    )
    context.stream_router.subscribe_to_error(lambda v: error_captured.append(v.content))
    context.stream_router.send_message("__output__", "out_msg")
    context.stream_router.send_message("__error__", "err_msg")
    context.output_captured = output_captured
    context.error_captured = error_captured


@then("the observers should receive the respective messages")
def step_verify_output_error(context: Context) -> None:
    assert context.output_captured == ["out_msg"]
    assert context.error_captured == ["err_msg"]


@given("I have a stream router with disposable streams and subscriptions that may fail")
def step_router_with_failing_disposables(context: Context) -> None:
    failing_stream = Subject()

    def _dispose_fail():
        raise RuntimeError("dispose failure")

    failing_stream.dispose = _dispose_fail  # type: ignore[attr-defined]
    context.stream_router.streams["fail_stream"] = failing_stream
    context.stream_router.observables["fail_stream"] = failing_stream
    context.stream_router.stream_configs["fail_stream"] = StreamConfig(
        name="fail_stream"
    )

    class _Sub:
        def dispose(self):
            raise RuntimeError("subscription dispose failure")

    context.stream_router.subscriptions.append(_Sub())


@when("I dispose the stream router with failing disposables")
def step_dispose_with_failures(context: Context) -> None:
    context.stream_router.dispose()


@then("the stream router internals should be cleared even on dispose errors")
def step_verify_dispose_with_failures(context: Context) -> None:
    assert context.stream_router.streams == {}
    assert context.stream_router.subscriptions == []
