"""Steps for stream router coverage (actor-first).

These steps focus on creation/error cases and lightweight operator helpers
compatible with the current ReactiveStreamRouter implementation.
"""

from __future__ import annotations

import json

from behave import given, then, when

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


class DummyAgent:
    def __init__(self, name: str):
        self.name = name

    def process_message_sync(self, message, metadata=None):  # type: ignore[override]
        return f"echo:{message}"

    def get_capabilities(self):  # pragma: no cover - not used in assertions
        return []


@given("I have a stream message for router testing")
def step_stream_message_testing(context):
    context.stream_message = StreamMessage(
        content="orig", metadata={"a": 1}, source_stream="s1", timestamp=1.0
    )
    context.original_msg = context.stream_message


@given("a reactive stream router harness")
def step_router_harness(context):
    context.router = ReactiveStreamRouter()
    context.errors = {}
    context.last_messages = []


@given("I have a replay stream configuration:")
@given("I have a hot stream configuration:")
@given("I have a stream configuration:")
@given("I have a stream configuration with agent mapper:")
@given("I have a stream configuration with missing agent:")
@given("I have a stream configuration with extract transform:")
@given("I have a stream configuration with unknown operator:")
@given("I have a stream configuration with LangGraph operator:")
@given("I have a stream configuration with equals filter:")
@given("I have a stream configuration with field condition:")
@given("I have a stream configuration with buffer and window:")
@given("I have a stream configuration with take skip merge:")
@given("I have a stream configuration with accumulate:")
@given("I have a stream configuration with switch operator:")
def step_load_stream_config(context):
    cfg_text = context.text.strip()
    cfg_dict = json.loads(cfg_text)
    cfg_dict.setdefault("operators", [])
    if "type" in cfg_dict:
        cfg_dict["type"] = StreamType(cfg_dict["type"].lower())
    context.stream_config = StreamConfig(**cfg_dict)


@given("I have a stream for router testing")
def step_stream_for_testing(context):
    context.stream_config = StreamConfig(name="test_stream")
    context.router.create_stream(context.stream_config)


@given('I have a tool agent named "{name}" with echo tool')
def step_tool_agent(context, name):
    agent = DummyAgent(name)
    context.router.register_agent(name, agent)


@when("I create the reactive stream")
@given("I create the reactive stream")
def step_create_stream(context):
    try:
        context.router.create_stream(context.stream_config)
        context.errors["create_stream"] = None
    except Exception as exc:  # pylint: disable=broad-except
        context.errors["create_stream"] = exc


@when("I try to create the reactive stream")
@when("I try to create another stream with the same name")
def step_create_stream_error(context):
    try:
        context.router.create_stream(context.stream_config)
        context.errors["create_stream"] = None
    except Exception as exc:  # pylint: disable=broad-except
        context.errors["create_stream"] = exc


@when('I send router message "{message}" to stream "{name}"')
def step_send_message(context, message, name):
    if name not in context.router.streams:
        context.router.create_stream(StreamConfig(name=name))
    collected = []
    context.router.streams[name].subscribe(collected.append)
    context.router.send_message(name, message)
    context.last_messages = collected


@when('I send router None message to stream "{name}"')
def step_send_none_message(context, name):
    step_send_message(context, None, name)


@when('I send a dictionary message with field "{field}"')
def step_send_dict_message(context, field):
    name = context.stream_config.name
    if name not in context.router.streams:
        context.router.create_stream(context.stream_config)
    collected = []
    context.router.streams[name].subscribe(collected.append)
    context.router.send_message(name, {field: "extracted"})
    context.last_messages = collected


@when("I try to send message to missing stream")
def step_send_missing_stream(context):
    try:
        context.router.send_message("missing_stream", "data")
        context.errors["send"] = None
    except Exception as exc:  # pylint: disable=broad-except
        context.errors["send"] = exc


@when("I copy the message with modifications")
def step_copy_message(context):
    msg = StreamMessage(
        content="orig", metadata={"a": 1}, source_stream="s1", timestamp=1.0
    )
    context.original_msg = msg
    context.copied_msg = msg.copy_with(content="new", metadata={"b": 2})


@when("I dispose of the stream router")
def step_dispose_router(context):
    context.router.dispose()


@when("I send a message to check timestamp")
def step_send_timestamp(context):
    name = "ts_stream"
    if name not in context.router.streams:
        context.router.create_stream(StreamConfig(name=name))
    collected = []
    context.router.streams[name].subscribe(collected.append)
    context.router.send_message(name, "payload")
    context.last_messages = collected


@then("the stream should be created successfully")
def step_stream_created(context):
    assert context.stream_config.name in context.router.streams
    assert context.errors.get("create_stream") is None


@then("I should get a stream router coverage error")
@then("I should get a stream router coverage error about missing agent")
@then("I should get a stream router coverage error about unknown operator")
@then("I should get a stream router coverage error about LangGraph bridge")
def step_stream_error(context):
    err = context.errors.get("create_stream")
    assert isinstance(err, StreamRoutingError)


@then("the field value should be extracted")
def step_field_extracted(context):
    # Directly apply transform to a dict message
    transform = context.stream_config.operators[0]["params"]["transform"]
    result = context.router._apply_transform({"data": "value"}, transform)  # pylint: disable=protected-access
    assert result == "value"


@then("the new message should have the modifications")
def step_new_message_has_mods(context):
    assert context.copied_msg.content == "new"
    assert context.copied_msg.metadata == {"b": 2}


@then("the original message should be unchanged")
def step_original_message_unchanged(context):
    assert context.original_msg.content == "orig"
    assert context.original_msg.metadata == {"a": 1}


@then("the agent should handle None gracefully")
def step_agent_handles_none(context):
    # Agent mapper returns string even on None
    mapper = context.router._create_agent_mapper(DummyAgent("none_agent"))  # pylint: disable=protected-access
    result = mapper(None)
    assert isinstance(result, StreamMessage)
    assert "echo:" in result.content


@then("the stream router should be disposed properly")
def step_disposed(context):
    assert context.router.streams == {}
    assert context.router.observables == {}
    assert context.router.stream_configs == {}


@then("the message should have a timestamp")
def step_timestamp_present(context):
    assert context.last_messages
    msg = context.last_messages[-1]
    assert getattr(msg, "timestamp", None) is not None
