"""CLI invocation step definitions for actor run positional-argument tests.

Covers the ``actor.py`` and ``actor_run.py`` CLI entry-points with
positional NAME and PROMPT arguments, config fallback, registry
resolution, error paths, config-precedence registry spy assertions,
and multiple config-file forwarding.
"""

from __future__ import annotations

import tempfile
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch

import typer
from behave import then, when

from cleveragents.cli.commands.actor import app as actor_app
from cleveragents.cli.commands.actor_run import app as actor_run_app
from features.steps.actor_run_signature_resolve_steps import (
    _get_combined_output,
    _make_app,
)


def _not_found_resolve(name: str, config: list[Any]) -> list[Any]:
    """Side-effect for _resolve_config_files that simulates registry miss."""
    typer.echo(
        f"Error: Actor '{name}' not found in registry and no --config provided.",
        err=True,
    )
    raise typer.Exit(code=2)


# ---------------------------------------------------------------------------
# actor.py run command — positional args with --config fallback
# ---------------------------------------------------------------------------


@when("I run actor run with positional args and config fallback")
def step_run_actor_positional_with_config(context: Any) -> None:
    context.prompt = "hello positional"
    context.run_result = "positional response"
    app_exec = _make_app(result=context.run_result)

    with patch(
        "cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
        return_value=app_exec,
    ):
        context.result = context.runner.invoke(
            actor_app,
            [
                "run",
                "--config",
                str(context.actor_config_path),
                "my-actor",
                context.prompt,
            ],
        )

    context.app_exec = app_exec


@when("I run actor run with name resolved from the actor registry")
def step_run_actor_name_from_registry(context: Any) -> None:
    context.prompt = "registry prompt"
    context.run_result = "registry response"
    app_exec = _make_app(result=context.run_result)

    # Mock _resolve_config_files to return the existing config path
    # (simulating successful registry resolution)
    def _mock_resolve(name: str, config: list[Any]) -> list[Any]:
        return [context.actor_config_path]

    with (
        patch(
            "cleveragents.cli.commands.actor._resolve_config_files",
            side_effect=_mock_resolve,
        ),
        patch(
            "cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
            return_value=app_exec,
        ),
    ):
        context.result = context.runner.invoke(
            actor_app,
            [
                "run",
                "local/my-actor",
                context.prompt,
            ],
        )

    context.app_exec = app_exec


@when("I run actor run with a positional prompt containing flag-like text")
def step_run_actor_prompt_with_flag_like_text(context: Any) -> None:
    context.prompt = "Fix the --output flag without changing --context"
    context.run_result = "flag-like prompt response"
    app_exec = _make_app(result=context.run_result)

    with patch(
        "cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
        return_value=app_exec,
    ):
        context.result = context.runner.invoke(
            actor_app,
            [
                "run",
                "--config",
                str(context.actor_config_path),
                "my-actor",
                context.prompt,
            ],
        )

    context.app_exec = app_exec


@when("I run actor run through registry integration path")
def step_run_actor_registry_integration_path(context: Any) -> None:
    context.prompt = "registry integration prompt"
    context.run_result = "registry integration response"
    app_exec = _make_app(result=context.run_result)

    mock_actor = MagicMock()
    mock_actor.yaml_text = "name: integration-actor\ntype: custom\n"
    mock_actor.config_blob = None

    mock_registry = MagicMock()
    mock_registry.get.return_value = mock_actor

    mock_container = MagicMock()
    mock_container.actor_registry.return_value = mock_registry

    with (
        patch(
            "cleveragents.cli.commands._resolve_actor.get_container",
            return_value=mock_container,
        ),
        patch(
            "cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
            return_value=app_exec,
        ) as mock_app_cls,
    ):
        context.result = context.runner.invoke(
            actor_app,
            [
                "run",
                "local/integration-actor",
                context.prompt,
            ],
        )

    call_kwargs = mock_app_cls.call_args.kwargs
    config_files = call_kwargs.get("config_files", [])
    if config_files:
        temp_config = Path(config_files[0])
        context.add_cleanup(lambda p=temp_config: p.unlink(missing_ok=True))

    context.app_exec = app_exec
    context.mock_registry = mock_registry


@when("I run actor run with config flag taking precedence")
def step_run_actor_config_takes_precedence(context: Any) -> None:
    context.prompt = "precedence prompt"
    context.run_result = "precedence response"
    app_exec = _make_app(result=context.run_result)

    with patch(
        "cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
        return_value=app_exec,
    ):
        context.result = context.runner.invoke(
            actor_app,
            [
                "run",
                "--config",
                str(context.actor_config_path),
                "ignored-name",
                context.prompt,
            ],
        )

    context.app_exec = app_exec


@when("I run actor run with an unknown actor name")
def step_run_actor_unknown_name(context: Any) -> None:
    with patch(
        "cleveragents.cli.commands.actor._resolve_config_files",
        side_effect=_not_found_resolve,
    ):
        context.result = context.runner.invoke(
            actor_app,
            [
                "run",
                "nonexistent/actor",
                "test prompt",
            ],
        )


# ---------------------------------------------------------------------------
# actor_run.py module — positional args
# ---------------------------------------------------------------------------


@when("I invoke the actor-run module with config flag taking precedence")
def step_invoke_actor_run_config_takes_precedence(context: Any) -> None:
    context.prompt = "actor-run precedence prompt"
    context.run_result = "actor-run precedence response"
    app_exec = _make_app(result=context.run_result)

    with patch(
        "cleveragents.cli.commands.actor_run.ReactiveCleverAgentsApp",
        return_value=app_exec,
    ):
        context.result = context.runner.invoke(
            actor_run_app,
            [
                "--config",
                str(context.actor_config_path),
                "ignored-name",
                context.prompt,
            ],
        )

    context.app_exec = app_exec


@when("I invoke the actor-run module with positional args and config fallback")
def step_invoke_actor_run_positional_with_config(context: Any) -> None:
    context.prompt = "actor-run positional"
    context.run_result = "actor-run positional response"
    app_exec = _make_app(result=context.run_result)

    with patch(
        "cleveragents.cli.commands.actor_run.ReactiveCleverAgentsApp",
        return_value=app_exec,
    ):
        context.result = context.runner.invoke(
            actor_run_app,
            [
                "--config",
                str(context.actor_config_path),
                "my-actor",
                context.prompt,
            ],
        )

    context.app_exec = app_exec


@when("I invoke the actor-run module with name from the actor registry")
def step_invoke_actor_run_name_from_registry(context: Any) -> None:
    context.prompt = "actor-run registry prompt"
    context.run_result = "actor-run registry response"
    app_exec = _make_app(result=context.run_result)

    # Mock _resolve_config_files to return the existing config path
    # (simulating successful registry resolution)
    def _mock_resolve(name: str, config: list[Any]) -> list[Any]:
        return [context.actor_config_path]

    with (
        patch(
            "cleveragents.cli.commands.actor_run._resolve_config_files",
            side_effect=_mock_resolve,
        ),
        patch(
            "cleveragents.cli.commands.actor_run.ReactiveCleverAgentsApp",
            return_value=app_exec,
        ),
    ):
        context.result = context.runner.invoke(
            actor_run_app,
            [
                "local/my-actor",
                context.prompt,
            ],
        )

    context.app_exec = app_exec


@when("I invoke the actor-run module with an unknown actor name")
def step_invoke_actor_run_unknown_name(context: Any) -> None:
    with patch(
        "cleveragents.cli.commands.actor_run._resolve_config_files",
        side_effect=_not_found_resolve,
    ):
        context.result = context.runner.invoke(
            actor_run_app,
            [
                "nonexistent/actor",
                "test prompt",
            ],
        )


# ---------------------------------------------------------------------------
# Shared assertions
# ---------------------------------------------------------------------------


@then("the actor run should exit with registry not found error")
def step_actor_run_registry_not_found(context: Any) -> None:
    assert context.result.exit_code == 2
    combined = _get_combined_output(context.result)
    assert "not found in registry" in combined


@then("the actor run should pass the exact positional prompt")
def step_actor_run_exact_prompt(context: Any) -> None:
    assert context.result.exit_code == 0
    run_args = context.app_exec.run_single_shot.call_args.args
    assert run_args[0] == context.prompt


@then("the actor registry should have been consulted with requested actor name")
def step_registry_consulted_with_actor_name(context: Any) -> None:
    context.mock_registry.get.assert_called_once_with("local/integration-actor")


# ---------------------------------------------------------------------------
# Review round 5 — P3-2: config precedence with registry spy
# ---------------------------------------------------------------------------


@when("I run actor run with config flag and a registry spy")
def step_run_actor_config_with_registry_spy(context: Any) -> None:
    context.prompt = "spy prompt"
    context.run_result = "spy response"
    app_exec = _make_app(result=context.run_result)

    mock_registry = MagicMock()
    mock_container = MagicMock()
    mock_container.actor_registry.return_value = mock_registry

    with (
        patch(
            "cleveragents.cli.commands._resolve_actor.get_container",
            return_value=mock_container,
        ),
        patch(
            "cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
            return_value=app_exec,
        ),
    ):
        context.result = context.runner.invoke(
            actor_app,
            [
                "run",
                "--config",
                str(context.actor_config_path),
                "ignored-name",
                context.prompt,
            ],
        )
    context.mock_registry = mock_registry


@when("I invoke the actor-run module with config flag and a registry spy")
def step_invoke_actor_run_config_with_registry_spy(context: Any) -> None:
    context.prompt = "actor-run spy prompt"
    context.run_result = "actor-run spy response"
    app_exec = _make_app(result=context.run_result)

    mock_registry = MagicMock()
    mock_container = MagicMock()
    mock_container.actor_registry.return_value = mock_registry

    with (
        patch(
            "cleveragents.cli.commands._resolve_actor.get_container",
            return_value=mock_container,
        ),
        patch(
            "cleveragents.cli.commands.actor_run.ReactiveCleverAgentsApp",
            return_value=app_exec,
        ),
    ):
        context.result = context.runner.invoke(
            actor_run_app,
            [
                "--config",
                str(context.actor_config_path),
                "ignored-name",
                context.prompt,
            ],
        )
    context.mock_registry = mock_registry


@then("the actor registry should not have been consulted")
def step_registry_not_consulted(context: Any) -> None:
    assert context.result.exit_code == 0
    context.mock_registry.get.assert_not_called()


# ---------------------------------------------------------------------------
# Review round 5 — P3-3: multiple config files with positional NAME
# ---------------------------------------------------------------------------


@when("I run actor run with multiple config files and a positional name")
def step_run_actor_multiple_configs(context: Any) -> None:
    context.prompt = "multi-config prompt"
    context.run_result = "multi-config response"
    app_exec = _make_app(result=context.run_result)

    second_config = Path(tempfile.gettempdir()) / "second_actor_config.yaml"
    second_config.write_text(
        "name: second\ntype: custom\nprovider: openai\nmodel: gpt-4\n",
        encoding="utf-8",
    )
    context.add_cleanup(lambda: second_config.unlink(missing_ok=True))
    context.second_actor_config_path = second_config

    with patch(
        "cleveragents.cli.commands.actor.ReactiveCleverAgentsApp",
        return_value=app_exec,
    ) as mock_app_cls:
        context.result = context.runner.invoke(
            actor_app,
            [
                "run",
                "--config",
                str(context.actor_config_path),
                "--config",
                str(second_config),
                "ignored-name",
                context.prompt,
            ],
        )
    context.mock_app_cls = mock_app_cls


@then("all config files should be passed to the application")
def step_all_configs_passed(context: Any) -> None:
    assert context.result.exit_code == 0
    call_kwargs = context.mock_app_cls.call_args
    config_files = call_kwargs[1].get(
        "config_files", call_kwargs[0][0] if call_kwargs[0] else None
    )
    assert config_files is not None
    assert len(config_files) == 2
    assert Path(config_files[0]) == context.actor_config_path
    assert Path(config_files[1]) == context.second_actor_config_path
