"""Step definitions for a2a_events_coverage_r3.feature.

Exercises previously-uncovered lines in src/cleveragents/a2a/events.py:
  63, 73-76, 82, 91, 100, 126, 234-238, 244, 250, 258
"""

from __future__ import annotations

from typing import Any
from unittest.mock import MagicMock

from behave import given, then, when

try:
    from cleveragents.a2a.events import (
        TASK_STATUS_UPDATE,
        A2aEventQueue,
        EventBusBridge,
    )
    from cleveragents.a2a.models import A2aEvent
except ImportError:
    pass  # module may be unavailable in isolated environments


# -----------------------------------------------------------------------
# Givens
# -----------------------------------------------------------------------


@given("aecov3 a fresh A2aEventQueue")
def step_aecov3_fresh_queue(context: Any) -> None:
    context.aecov3_queue = A2aEventQueue()
    context.aecov3_error = None


@given("aecov3 a subscriber callback that raises RuntimeError")
def step_aecov3_failing_subscriber(context: Any) -> None:
    def _boom(event: Any) -> None:
        raise RuntimeError("subscriber exploded")

    context.aecov3_sub_id = context.aecov3_queue.subscribe_local(_boom)


@given("aecov3 a mock EventBus that returns a disposable subscription")
def step_aecov3_mock_bus_disposable(context: Any) -> None:
    context.aecov3_bus = MagicMock()
    context.aecov3_subscription = MagicMock()
    # The subscription has a .dispose() method
    context.aecov3_subscription.dispose = MagicMock()
    context.aecov3_bus_callback = None

    def _fake_subscribe(callback: Any) -> MagicMock:
        context.aecov3_bus_callback = callback
        return context.aecov3_subscription

    context.aecov3_bus.subscribe.side_effect = _fake_subscribe


@given("aecov3 a mock EventBus that returns a non-disposable subscription")
def step_aecov3_mock_bus_non_disposable(context: Any) -> None:
    context.aecov3_bus = MagicMock(spec=[])
    context.aecov3_bus.subscribe = MagicMock()
    context.aecov3_subscription = "simple-token"  # no .dispose attribute
    context.aecov3_bus_callback = None

    def _fake_subscribe(callback: Any) -> str:
        context.aecov3_bus_callback = callback
        return context.aecov3_subscription

    context.aecov3_bus.subscribe.side_effect = _fake_subscribe


@given("aecov3 a mock EventBus that captures the callback")
def step_aecov3_mock_bus_capture(context: Any) -> None:
    context.aecov3_bus = MagicMock()
    context.aecov3_bus_callback = None

    def _fake_subscribe(callback: Any) -> MagicMock:
        context.aecov3_bus_callback = callback
        return MagicMock()

    context.aecov3_bus.subscribe.side_effect = _fake_subscribe


@given("aecov3 an EventBusBridge connecting the bus and queue")
def step_aecov3_bridge(context: Any) -> None:
    context.aecov3_bridge = EventBusBridge(context.aecov3_bus, context.aecov3_queue)


# -----------------------------------------------------------------------
# Whens
# -----------------------------------------------------------------------


@when("aecov3 I publish a plain dict instead of an A2aEvent")
def step_aecov3_publish_dict(context: Any) -> None:
    try:
        context.aecov3_queue.publish({"not": "an event"})  # type: ignore[arg-type]
    except Exception as exc:
        context.aecov3_error = exc


@when("aecov3 I publish a valid A2aEvent")
def step_aecov3_publish_valid(context: Any) -> None:
    context.aecov3_event = A2aEvent(
        event_type="TestEvent",
        plan_id="plan-test",
        data={"key": "value"},
    )
    try:
        context.aecov3_queue.publish(context.aecov3_event)
    except Exception as exc:
        context.aecov3_error = exc


@when("aecov3 I subscribe with a non-callable value")
def step_aecov3_subscribe_non_callable(context: Any) -> None:
    try:
        context.aecov3_queue.subscribe_local("not-a-callable")  # type: ignore[arg-type]
    except Exception as exc:
        context.aecov3_error = exc


@when("aecov3 I unsubscribe with an empty string")
def step_aecov3_unsubscribe_empty(context: Any) -> None:
    try:
        context.aecov3_queue.unsubscribe("")
    except Exception as exc:
        context.aecov3_error = exc


@when("aecov3 I unsubscribe with a non-string value")
def step_aecov3_unsubscribe_non_string(context: Any) -> None:
    try:
        context.aecov3_queue.unsubscribe(12345)  # type: ignore[arg-type]
    except Exception as exc:
        context.aecov3_error = exc


@when("aecov3 I call get_events with limit 0")
def step_aecov3_get_events_zero(context: Any) -> None:
    try:
        context.aecov3_queue.get_events(limit=0)
    except Exception as exc:
        context.aecov3_error = exc


@when("aecov3 I call get_events with a string limit")
def step_aecov3_get_events_string(context: Any) -> None:
    try:
        context.aecov3_queue.get_events(limit="ten")  # type: ignore[arg-type]
    except Exception as exc:
        context.aecov3_error = exc


@when("aecov3 I call subscribe_remote with an empty endpoint")
def step_aecov3_remote_empty(context: Any) -> None:
    try:
        context.aecov3_queue.subscribe_remote("")
    except Exception as exc:
        context.aecov3_error = exc


@when("aecov3 I call subscribe_remote with a non-string endpoint")
def step_aecov3_remote_non_string(context: Any) -> None:
    try:
        context.aecov3_queue.subscribe_remote(42)  # type: ignore[arg-type]
    except Exception as exc:
        context.aecov3_error = exc


@when("aecov3 the bridge is started")
def step_aecov3_bridge_start(context: Any) -> None:
    context.aecov3_bridge.start()


@when("aecov3 the bridge is stopped")
def step_aecov3_bridge_stop(context: Any) -> None:
    context.aecov3_bridge.stop()


@when("aecov3 the bridge receives a domain event without event_type")
def step_aecov3_domain_no_type(context: Any) -> None:
    domain_event = MagicMock(spec=[])
    # No event_type attribute at all → getattr returns None → early return
    context.aecov3_bus_callback(domain_event)


@when('aecov3 the bridge receives a domain event with string event_type "{type_str}"')
def step_aecov3_domain_string_type(context: Any, type_str: str) -> None:
    domain_event = MagicMock()
    # Use a plain string (no .value) so the `else str(event_type_name)` branch runs
    domain_event.event_type = type_str
    # Remove .value so hasattr(event_type_name, "value") is False on the str
    # Python str does not have a `.value` attribute, so this exercises line 250
    domain_event.plan_id = "plan-bridge-test"
    domain_event.details = {"info": "testing"}
    context.aecov3_bus_callback(domain_event)


# -----------------------------------------------------------------------
# Thens
# -----------------------------------------------------------------------


@then('aecov3 a TypeError should be stored with message "{msg}"')
def step_aecov3_check_type_error(context: Any, msg: str) -> None:
    assert context.aecov3_error is not None, "Expected an error but none was stored"
    assert isinstance(context.aecov3_error, TypeError), (
        f"Expected TypeError, got {type(context.aecov3_error).__name__}"
    )
    assert msg in str(context.aecov3_error), (
        f"Expected message containing '{msg}', got: {context.aecov3_error}"
    )


@then('aecov3 a ValueError should be stored with message "{msg}"')
def step_aecov3_check_value_error(context: Any, msg: str) -> None:
    assert context.aecov3_error is not None, "Expected an error but none was stored"
    assert isinstance(context.aecov3_error, ValueError), (
        f"Expected ValueError, got {type(context.aecov3_error).__name__}"
    )
    assert msg in str(context.aecov3_error), (
        f"Expected message containing '{msg}', got: {context.aecov3_error}"
    )


@then("aecov3 no error should be stored")
def step_aecov3_no_error(context: Any) -> None:
    assert context.aecov3_error is None, (
        f"Expected no error, got: {context.aecov3_error}"
    )


@then("aecov3 the event should be in the queue")
def step_aecov3_event_in_queue(context: Any) -> None:
    events = context.aecov3_queue.get_events()
    ids = [e.event_id for e in events]
    assert context.aecov3_event.event_id in ids, (
        f"Event {context.aecov3_event.event_id} not found in queue: {ids}"
    )


@then("aecov3 the subscription dispose should have been called")
def step_aecov3_dispose_called(context: Any) -> None:
    context.aecov3_subscription.dispose.assert_called_once()


@then("aecov3 the bridge subscription should be None")
def step_aecov3_subscription_none(context: Any) -> None:
    assert context.aecov3_bridge._subscription is None, (
        f"Expected None subscription, got: {context.aecov3_bridge._subscription}"
    )


@then("aecov3 the queue should be empty")
def step_aecov3_queue_empty(context: Any) -> None:
    events = context.aecov3_queue.get_events()
    assert len(events) == 0, f"Expected empty queue, got {len(events)} events"


@then("aecov3 the queue should contain a TaskStatusUpdateEvent")
def step_aecov3_queue_has_status(context: Any) -> None:
    events = context.aecov3_queue.get_events()
    types = [e.event_type for e in events]
    assert TASK_STATUS_UPDATE in types, (
        f"Expected {TASK_STATUS_UPDATE} in queue, got: {types}"
    )


@then('aecov3 the queue should contain an event with type "{expected_type}"')
def step_aecov3_queue_has_type(context: Any, expected_type: str) -> None:
    events = context.aecov3_queue.get_events()
    types = [e.event_type for e in events]
    assert expected_type in types, f"Expected '{expected_type}' in queue, got: {types}"
