"""Step definitions for actor add NAME positional argument feature."""

from __future__ import annotations

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

from behave import given, then, when

from cleveragents.cli.commands.actor import app as actor_app
from cleveragents.domain.models.core.actor import Actor


def _make_actor(
    *,
    name: str = "local/test-actor",
    provider: str = "openai",
    model: str = "gpt-4o-mini",
    config: 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=None,
        unsafe=unsafe,
        is_built_in=is_built_in,
        is_default=is_default,
    )


def _register_cleanup(context: Any, path: Path) -> None:
    context._cleanup_handlers.append(lambda: path.unlink(missing_ok=True))


@given("I have an actor JSON config file without a name field")
def step_impl(context: Any) -> None:
    context.actor_config_data = {
        "provider": "openai",
        "model": "gpt-4o-mini",
        "temperature": 0.5,
    }
    with tempfile.NamedTemporaryFile(
        delete=False, suffix=".json", mode="w", encoding="utf-8"
    ) as handle:
        json.dump(context.actor_config_data, handle)
        handle.flush()
    context.actor_config_path = Path(handle.name)
    _register_cleanup(context, context.actor_config_path)


@given("I have an actor JSON config file with a different name")
def step_impl(context: Any) -> None:
    context.actor_config_data = {
        "name": "local/config-name-actor",
        "provider": "openai",
        "model": "gpt-4o-mini",
    }
    with tempfile.NamedTemporaryFile(
        delete=False, suffix=".json", mode="w", encoding="utf-8"
    ) as handle:
        json.dump(context.actor_config_data, handle)
        handle.flush()
    context.actor_config_path = Path(handle.name)
    _register_cleanup(context, context.actor_config_path)


@when("I run actor add with NAME positional argument and config")
def step_impl(context: Any) -> None:
    context.positional_name = "local/my-actor"
    mock_actor = _make_actor(name=context.positional_name)
    with patch("cleveragents.cli.commands.actor._get_services") as mock_svc:
        registry = MagicMock()
        registry.upsert_actor.return_value = mock_actor
        mock_svc.return_value = (MagicMock(), registry)
        context.result = context.runner.invoke(
            actor_app,
            [
                "add",
                context.positional_name,
                "--config",
                str(context.actor_config_path),
            ],
        )
        context.mock_registry = registry


@when("I run actor add with NAME positional argument overriding config name")
def step_impl(context: Any) -> None:
    context.positional_name = "local/positional-name-actor"
    mock_actor = _make_actor(name=context.positional_name)
    with patch("cleveragents.cli.commands.actor._get_services") as mock_svc:
        registry = MagicMock()
        registry.upsert_actor.return_value = mock_actor
        mock_svc.return_value = (MagicMock(), registry)
        context.result = context.runner.invoke(
            actor_app,
            [
                "add",
                context.positional_name,
                "--config",
                str(context.actor_config_path),
            ],
        )
        context.mock_registry = registry


@when("I run actor add with config but no NAME positional argument")
def step_impl(context: Any) -> None:
    with patch("cleveragents.cli.commands.actor._get_services") as mock_svc:
        registry = MagicMock()
        mock_svc.return_value = (MagicMock(), registry)
        context.result = context.runner.invoke(
            actor_app,
            [
                "add",
                "--config",
                str(context.actor_config_path),
            ],
        )


@then("the actor add should succeed with the positional name")
def step_impl(context: Any) -> None:
    assert context.result.exit_code == 0, (
        f"Expected exit_code=0, got {context.result.exit_code}.\n"
        f"Output:\n{context.result.output}"
    )
    # Verify the registry was called with the positional name
    assert context.mock_registry.upsert_actor.called, (
        "Expected upsert_actor to be called on the registry"
    )
    call_kwargs = context.mock_registry.upsert_actor.call_args
    actual_name = call_kwargs.kwargs.get("name") or (
        call_kwargs.args[0] if call_kwargs.args else None
    )
    assert actual_name == context.positional_name, (
        f"Expected upsert_actor called with name={context.positional_name!r}, "
        f"got name={actual_name!r}"
    )


@then("the actor add should use the positional NAME not the config name")
def step_impl(context: Any) -> None:
    assert context.result.exit_code == 0, (
        f"Expected exit_code=0, got {context.result.exit_code}.\n"
        f"Output:\n{context.result.output}"
    )
    assert context.mock_registry.upsert_actor.called, (
        "Expected upsert_actor to be called on the registry"
    )
    call_kwargs = context.mock_registry.upsert_actor.call_args
    actual_name = call_kwargs.kwargs.get("name") or (
        call_kwargs.args[0] if call_kwargs.args else None
    )
    assert actual_name == context.positional_name, (
        f"Expected upsert_actor called with positional name={context.positional_name!r}, "
        f"but got name={actual_name!r} (config name was 'local/config-name-actor')"
    )


@then("the actor command should fail with missing argument error")
def step_impl(context: Any) -> None:
    assert context.result.exit_code != 0, (
        f"Expected non-zero exit_code, got {context.result.exit_code}.\n"
        f"Output:\n{context.result.output}"
    )
