"""
Step definitions for uncovered stream_router paths.
"""

from typing import Any

import rx
from behave import given, then, when
from behave.runner import Context

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


@given("the CleverAgents reactive system is available")
def step_system_available(context: Context) -> None:
    context.stream_router = ReactiveStreamRouter()


@given("I have a base stream message with context metadata")
def step_base_stream_message(context: Context) -> None:
    context.original_message = StreamMessage(
        content={"value": 1},
        metadata={"context": {"trace": 1}, "other": {"a": 1}},
        source_stream="base",
        timestamp=1.0,
    )


@when("I copy the message without metadata override")
def step_copy_message(context: Context) -> None:
    context.copied_message = context.original_message.copy_with()


@then("the metadata should be deep-copied except context")
def step_verify_copy(context: Context) -> None:
    assert context.copied_message.metadata["other"] == {"a": 1}
    assert (
        context.copied_message.metadata["other"]
        is not context.original_message.metadata["other"]
    )
    assert (
        context.copied_message.metadata["context"]
        is context.original_message.metadata["context"]
    )


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


@then("I should get a stream routing error about missing map params")
def step_verify_map_error(context: Context) -> None:
    assert isinstance(context.error, StreamRoutingError)
    assert "Map operator requires" in str(context.error)


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


@then("I should get a stream routing error about buffer count")
def step_verify_buffer_error(context: Context) -> None:
    assert isinstance(context.error, StreamRoutingError)
    assert "Buffer operator requires" in str(context.error)


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


@then("I should get a stream routing error about window count")
def step_verify_window_error(context: Context) -> None:
    assert isinstance(context.error, StreamRoutingError)
    assert "Window operator requires" in str(context.error)


@when("I evaluate a field condition against a message without that field")
def step_eval_missing_field(context: Context) -> None:
    context.condition_result = context.stream_router._evaluate_condition(
        {}, {"field": "missing"}
    )


@then("the condition result should be false")
def step_verify_condition_false(context: Context) -> None:
    assert context.condition_result is False


@when("I apply the append accumulator with list initialization")
def step_apply_append_accumulator(context: Context) -> None:
    context.acc_result = context.stream_router._apply_accumulator(
        None, "item", {"op": "append", "init": "list"}
    )


@then("the accumulator should return a list with the item")
def step_verify_append_accumulator(context: Context) -> None:
    assert context.acc_result == ["item"]


@when("I send a message to an unknown stream")
def step_send_unknown_stream(context: Context) -> None:
    context.error = None
    try:
        context.stream_router.send_message("unknown_stream", "data")
    except Exception as exc:  # pylint: disable=broad-except
        context.error = exc


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


@when("I split a stream from an unknown source")
def step_split_unknown_source(context: Context) -> None:
    context.error = None
    try:
        context.stream_router.split_stream("unknown_source", [])
    except Exception as exc:  # pylint: disable=broad-except
        context.error = exc


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


@when('I create a stream from the string name "string_stream"')
def step_create_stream_from_string(context: Context) -> None:
    context.created_stream = context.stream_router.create_stream("string_stream")


@then("the stream should exist in the router")
def step_stream_exists(context: Context) -> None:
    assert "string_stream" in context.stream_router.streams
    assert context.stream_router.stream_configs["string_stream"].name == "string_stream"


@given('I have already created a stream named "dup_stream"')
def step_existing_stream(context: Context) -> None:
    context.stream_router.create_stream("dup_stream")


@when('I try to create the stream "dup_stream" again')
def step_create_duplicate_stream(context: Context) -> None:
    context.error = None
    try:
        context.stream_router.create_stream("dup_stream")
    except Exception as exc:  # pylint: disable=broad-except
        context.error = exc


@then("I should get a stream routing error about duplicate stream")
def step_verify_duplicate_stream_error(context: Context) -> None:
    assert isinstance(context.error, StreamRoutingError)
    assert "already exists" in str(context.error)


@when('I map a message using function param "noop_function"')
def step_map_with_function_param(context: Context) -> None:
    context.stream_router._builtin_noop_function = lambda x: x
    operator = context.stream_router._create_operator(
        {"type": "map", "params": {"function": "noop_function"}}
    )
    captured: list[Any] = []
    rx.just(5).pipe(operator).subscribe(lambda v: captured.append(v))
    context.map_result = captured[0] if captured else None


@then("the mapped result should equal the original message")
def step_verify_map_function_result(context: Context) -> None:
    assert context.map_result == 5


@when("I build a transform operator with an invalid function string")
def step_transform_invalid_fn(context: Context) -> None:
    context.error = None
    try:
        context.stream_router._create_operator(
            {"type": "transform", "params": {"fn": "lambda x: x +"}}
        )
    except Exception as exc:  # pylint: disable=broad-except
        context.error = exc


@then("I should get a stream routing error about unregistered transform fn")
def step_verify_transform_invalid_fn(context: Context) -> None:
    assert isinstance(context.error, StreamRoutingError)
    assert "Unknown transform" in str(context.error)


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


@then("I should get a stream routing error about missing filter condition")
def step_verify_filter_condition_error(context: Context) -> None:
    assert isinstance(context.error, StreamRoutingError)
    assert "Filter operator requires" in str(context.error)


@when("I run a switch operator with default operators against a message")
def step_switch_default_operators(context: Context) -> None:
    default_ops = [
        {
            "type": "map",
            "params": {"transform": {"type": "replace", "target": "x", "value": 10}},
        }
    ]
    switch_op = context.stream_router._create_operator(
        {"type": "switch", "params": {"cases": [], "default_operators": default_ops}}
    )
    message = {"x": 1}
    captured = []
    switch_op(rx.just(message)).subscribe(lambda v: captured.append(v))
    if captured and isinstance(captured[0], dict):
        context.switch_result = captured[0].get("x")
    else:
        context.switch_result = None


@then("the switch operator should output the transformed value")
def step_verify_switch_default_ops(context: Context) -> None:
    assert context.switch_result == 10


@when("I run a switch operator that targets a default stream subject")
def step_switch_default_stream(context: Context) -> None:
    context.stream_router.create_stream(
        StreamConfig(name="default_target", type=StreamType.COLD)
    )
    switch_op = context.stream_router._create_operator(
        {"type": "switch", "params": {"cases": [], "default": "default_target"}}
    )
    message = StreamMessage(content="payload")
    emitted = []
    context.stream_router.streams["default_target"].subscribe(
        lambda v: emitted.append(v.content if hasattr(v, "content") else v)
    )
    switch_op(rx.just(message)).subscribe(lambda _v: None)  # drive operator
    # manually emit to default stream to exercise branch
    context.stream_router.streams["default_target"].on_next(message)
    context.default_stream_value = emitted[0] if emitted else None


@then("the default stream should emit its initial value")
def step_verify_switch_default_stream(context: Context) -> None:
    assert context.default_stream_value == "payload"


@when("I apply a replace transform to a dictionary message")
def step_apply_replace_transform(context: Context) -> None:
    context.replace_result = context.stream_router._apply_transform(
        {"foo": 1}, {"type": "replace", "target": "foo", "value": 2}
    )


@then("the dictionary field should be updated")
def step_verify_replace_transform(context: Context) -> None:
    assert context.replace_result["foo"] == 2


@when("I apply the sum accumulator to numeric messages")
def step_apply_sum_accumulator(context: Context) -> None:
    acc = context.stream_router._apply_accumulator(None, 1, {"op": "sum"})
    context.sum_result = context.stream_router._apply_accumulator(acc, 2, {"op": "sum"})


@then("the accumulator should produce the summed value")
def step_verify_sum_accumulator(context: Context) -> None:
    assert context.sum_result == 3


@given('I have two source streams named "merge_a" and "merge_b"')
def step_create_merge_sources(context: Context) -> None:
    context.stream_router.create_stream("merge_a")
    context.stream_router.create_stream("merge_b")


@when('I merge the streams into a new target "merge_output"')
def step_merge_streams(context: Context) -> None:
    context.stream_router.merge_streams(["merge_a", "merge_b"], "merge_output")


@then("the merged target stream should exist")
def step_verify_merge_target(context: Context) -> None:
    assert "merge_output" in context.stream_router.streams


@given("I have a stream router with disposable streams and subscriptions")
def step_router_with_disposables(context: Context) -> None:
    from rx.subject import Subject

    disposable_stream = Subject()
    disposable_stream.dispose = lambda: setattr(context, "disposed_called", True)  # type: ignore[attr-defined]
    context.stream_router.streams["disposable"] = disposable_stream
    context.stream_router.observables["disposable"] = disposable_stream
    context.stream_router.stream_configs["disposable"] = StreamConfig(name="disposable")
    context.stream_router.subscriptions.append(
        lambda: setattr(context, "subscription_disposed", True)
    )


@when("I dispose the stream router")
def step_dispose_router(context: Context) -> None:
    context.stream_router.dispose()


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