# pyright: reportRedeclaration=false
"""Step definitions for actor_cli_coverage_boost.feature.

Targets uncovered lines in src/cleveragents/cli/commands/actor.py:
  - Lines 180-185: _get_services()
  - Line 311:      _actor_spec_dict() with graph_descriptor
  - Line 665:      set_default() service fallback
"""

from __future__ import annotations

from typing import Any
from unittest.mock import MagicMock, patch

from behave import given, then, when
from typer.testing import CliRunner

from cleveragents.cli.commands.actor import (
    _actor_spec_dict,
    _get_services,
    _print_actor,
)
from cleveragents.cli.commands.actor import (
    app as actor_app,
)
from cleveragents.core.exceptions import NotFoundError
from cleveragents.domain.models.core.actor import Actor

# ── helpers ──────────────────────────────────────────────────────────────


def _make_actor(
    *,
    name: str = "local/test-actor",
    provider: str = "test-provider",
    model: str = "test-model",
    config: dict[str, Any] | None = None,
    graph_descriptor: dict[str, Any] | None = None,
    unsafe: bool = False,
    is_default: bool = False,
    is_built_in: bool = False,
) -> Actor:
    blob = config or {}
    return Actor(
        id=1,
        name=name,
        provider=provider,
        model=model,
        config_blob=blob,
        config_hash=Actor.compute_hash(blob),
        graph_descriptor=graph_descriptor,
        unsafe=unsafe,
        is_built_in=is_built_in,
        is_default=is_default,
    )


# ── _get_services (lines 180-185) ───────────────────────────────────────


@given("a mock container with actor_registry")
def step_container_with_registry(context):
    context.mock_container = MagicMock()
    context.mock_container.actor_service.return_value = MagicMock(
        name="actor_service_instance"
    )
    context.mock_container.actor_registry.return_value = MagicMock(
        name="actor_registry_instance"
    )
    # Ensure hasattr(container, 'actor_registry') is True
    context.mock_container.actor_registry.__name__ = "actor_registry"


@given("a mock container without actor_registry")
def step_container_without_registry(context):
    context.mock_container = MagicMock(spec=["actor_service"])
    context.mock_container.actor_service.return_value = MagicMock(
        name="actor_service_instance"
    )


@when("I call _get_services")
def step_call_get_services(context):
    with patch(
        "cleveragents.cli.commands.actor.get_container",
        return_value=context.mock_container,
    ):
        context.svc_result = _get_services()


@then("I should receive both actor_service and actor_registry")
def step_both_returned(context):
    actor_service, actor_registry = context.svc_result
    assert actor_service is not None, "actor_service should not be None"
    assert actor_registry is not None, "actor_registry should not be None"


@then("I should receive actor_service and None for registry")
def step_service_only(context):
    actor_service, actor_registry = context.svc_result
    assert actor_service is not None, "actor_service should not be None"
    assert actor_registry is None, "actor_registry should be None when not on container"


# ── _actor_spec_dict with/without graph_descriptor (line 311) ────────────


@given("an actor with a graph_descriptor")
def step_actor_with_graph(context):
    context.test_actor = _make_actor(
        name="local/graph-actor",
        provider="graph-provider",
        model="graph-model",
        graph_descriptor={"nodes": ["start", "end"], "edges": [["start", "end"]]},
    )


@given("an actor without a graph_descriptor")
def step_actor_without_graph(context):
    context.test_actor = _make_actor(
        name="local/plain-actor",
        provider="plain-provider",
        model="plain-model",
        graph_descriptor=None,
    )


@when("I call _actor_spec_dict on that actor")
def step_call_actor_spec_dict(context):
    context.spec_dict = _actor_spec_dict(context.test_actor)


@then("the result dict should contain the graph_descriptor key")
def step_dict_has_graph(context):
    assert "graph_descriptor" in context.spec_dict, (
        "Expected 'graph_descriptor' key in spec dict"
    )
    assert context.spec_dict["graph_descriptor"] == context.test_actor.graph_descriptor


@then("the result dict should not contain the graph_descriptor key")
def step_dict_no_graph(context):
    assert "graph_descriptor" not in context.spec_dict, (
        "Did not expect 'graph_descriptor' key in spec dict when graph_descriptor is None"
    )


# ── set-default via service fallback (line 665) ──────────────────────────


@given("an actor CLI test runner")
def step_cli_test_runner(context):
    context.runner = CliRunner()


@when("I run set-default via the service path")
def step_set_default_service(context):
    actor = _make_actor(
        name="local/service-default",
        provider="svc-provider",
        model="svc-model",
        is_default=True,
    )
    with patch("cleveragents.cli.commands.actor._get_services") as mock_gs:
        service = MagicMock()
        service.set_default_actor.return_value = actor
        mock_gs.return_value = (service, None)  # registry is None

        context.result = context.runner.invoke(
            actor_app, ["set-default", "local/service-default"]
        )
    context.service_mock = service
    context.test_actor = actor


@then("the service set_default_actor should be called")
def step_service_set_default_called(context):
    context.service_mock.set_default_actor.assert_called_once_with(
        "local/service-default"
    )


@then("the set-default output should contain the actor name")
def step_set_default_output_has_name(context):
    assert context.result.exit_code == 0
    assert context.test_actor.name in context.result.output


@when("I run set-default via service for a missing actor")
def step_set_default_service_not_found(context):
    with patch("cleveragents.cli.commands.actor._get_services") as mock_gs:
        service = MagicMock()
        service.set_default_actor.side_effect = NotFoundError(
            resource_type="actor", resource_id="local/missing"
        )
        mock_gs.return_value = (service, None)  # registry is None

        context.result = context.runner.invoke(
            actor_app, ["set-default", "local/missing"]
        )


@then("the set-default command should abort with an error")
def step_set_default_aborted(context):
    assert context.result.exit_code != 0
    assert "Error:" in context.result.output


# ── _print_actor non-rich format with graph_descriptor ────────────────────


@when("I print that actor in json format")
def step_print_actor_json(context):
    import contextlib
    from io import StringIO

    from rich.console import Console

    buf = StringIO()
    test_console = Console(file=buf, force_terminal=False)

    with (
        patch("cleveragents.cli.commands.actor.console", test_console),
        contextlib.redirect_stdout(buf),
    ):
        _print_actor(context.test_actor, title="Test", fmt="json")

    context.print_output = buf.getvalue()


@then("the json output should contain the graph_descriptor")
def step_json_has_graph(context):
    assert "graph_descriptor" in context.print_output
    assert "nodes" in context.print_output


# ── list_actors via service fallback ──────────────────────────────────────


@when("I run actor list via service with actors")
def step_list_service(context):
    actors = [
        _make_actor(name="local/svc-one", provider="p1", model="m1"),
        _make_actor(name="local/svc-two", provider="p2", model="m2"),
    ]
    with patch("cleveragents.cli.commands.actor._get_services") as mock_gs:
        service = MagicMock()
        service.list_actors.return_value = actors
        mock_gs.return_value = (service, None)

        context.result = context.runner.invoke(actor_app, ["list"])
    context.test_actors = actors


@then("the actor list output should contain actor names")
def step_list_has_names(context):
    assert context.result.exit_code == 0
    # Rich table may truncate long names with "…", so check for the
    # unique suffix portion of each name which is always visible.
    for a in context.test_actors:
        # e.g. "local/svc-one" → check "svc-one" or at least a prefix
        short = a.name.split("/")[-1]
        assert short[:5] in context.result.output, (
            f"Expected '{short}' fragment in output: {context.result.output!r}"
        )


@when("I run actor list via service in json format")
def step_list_service_json(context):
    actors = [
        _make_actor(name="local/json-one", provider="jp1", model="jm1"),
    ]
    with patch("cleveragents.cli.commands.actor._get_services") as mock_gs:
        service = MagicMock()
        service.list_actors.return_value = actors
        mock_gs.return_value = (service, None)

        context.result = context.runner.invoke(actor_app, ["list", "--format", "json"])
    context.test_actors = actors


@then("the actor list json output should be valid")
def step_list_json_valid(context):
    assert context.result.exit_code == 0
    # The output should be parseable JSON (may have Rich markup stripped)
    raw = context.result.output.strip()
    # The output may include ANSI codes from Rich; just verify it contains our actor
    assert "local/json-one" in raw


# ── show actor via service fallback ───────────────────────────────────────


@when("I run actor show via service path")
def step_show_service(context):
    actor = _make_actor(
        name="local/show-svc",
        provider="show-provider",
        model="show-model",
    )
    with patch("cleveragents.cli.commands.actor._get_services") as mock_gs:
        service = MagicMock()
        service.get_actor.return_value = actor
        mock_gs.return_value = (service, None)

        context.result = context.runner.invoke(actor_app, ["show", "local/show-svc"])
    context.test_actor = actor


@then("the show output should contain the actor name")
def step_show_has_name(context):
    assert context.result.exit_code == 0
    assert context.test_actor.name in context.result.output
