"""Step definitions for actor_registry_new_coverage.feature."""

from __future__ import annotations

from typing import Any
from unittest.mock import MagicMock

from behave import given, then, when  # type: ignore[import-untyped]
from behave.runner import Context  # type: ignore[import-untyped]

from cleveragents.actor.config import ActorConfiguration
from cleveragents.actor.registry import ActorRegistry
from cleveragents.core.exceptions import ValidationError
from cleveragents.domain.models.core import Actor
from cleveragents.providers.registry import ProviderCapabilities, ProviderInfo


def _make_actor(name: str, provider: str = "openai", model: str = "gpt-4") -> Actor:
    actor = MagicMock(spec=Actor)
    actor.name = name
    actor.provider = provider
    actor.model = model
    return actor


def _make_provider_info(
    name: str, model: str, provider_type_value: str = "openai"
) -> ProviderInfo:
    info = MagicMock(spec=ProviderInfo)
    info.name = name
    info.default_model = model
    type_mock = MagicMock()
    type_mock.value = provider_type_value
    info.provider_type = type_mock
    info.capabilities = ProviderCapabilities(
        supports_streaming=True,
        supports_tool_calls=True,
        supports_vision=False,
        supports_json_mode=True,
    )
    return info


def _make_settings(provider: str | None = None, model: str | None = None) -> MagicMock:
    settings = MagicMock()
    defaults = MagicMock()
    defaults.provider = provider
    defaults.model = model
    settings.resolve_provider_defaults.return_value = defaults
    return settings


@given("a stubbed actor registry")
def step_stubbed_registry(context: Context) -> None:
    context.actor_service = MagicMock()
    context.provider_registry = MagicMock()
    context.settings = _make_settings()
    context.registry = ActorRegistry(
        actor_service=context.actor_service,
        provider_registry=context.provider_registry,
        settings=context.settings,
    )


@then("the registry should have an actor service")
def step_has_actor_service(context: Context) -> None:
    assert context.registry._actor_service is context.actor_service


@then("the registry should have a provider registry")
def step_has_provider_registry(context: Context) -> None:
    assert context.registry._provider_registry is context.provider_registry


@then("the registry should have settings")
def step_has_settings(context: Context) -> None:
    assert context.registry._settings is context.settings


@when('I format actor name for provider "{provider}" and model "{model}"')
def step_format_name(context: Context, provider: str, model: str) -> None:
    context.result = context.registry._actor_name(provider, model)


@then('the actor name should be "{expected}"')
def step_assert_actor_name(context: Context, expected: str) -> None:
    assert context.result == expected


@when(
    'I build a graph descriptor for provider "{provider}" model "{model}" source "{source}"'
)
def step_build_descriptor(
    context: Context, provider: str, model: str, source: str
) -> None:
    context.descriptor = context.registry._build_graph_descriptor(
        provider=provider, model=model, source=source
    )


@then("the descriptor should contain provider model and source")
def step_assert_descriptor(context: Context) -> None:
    d = context.descriptor
    assert d["provider"] == "openai"
    assert d["model"] == "gpt-4"
    assert d["source"] == "test"


@when("I build a canonical blob from a config and raw blob")
def step_build_canonical(context: Context) -> None:
    config = ActorConfiguration(
        provider="openai", model="gpt-4", options={"temperature": 0.7}
    )
    raw: dict[str, Any] = {"extra": "data"}
    context.blob = context.registry._canonical_blob(
        config, raw, source="custom", default_graph=None
    )


@then("the blob should contain provider model and source keys")
def step_assert_canonical_blob(context: Context) -> None:
    b = context.blob
    assert "provider" in b
    assert "model" in b
    assert "source" in b


@given("a stubbed actor registry with no configured providers")
def step_registry_no_providers(context: Context) -> None:
    context.actor_service = MagicMock()
    context.provider_registry = MagicMock()
    context.provider_registry.get_configured_providers.return_value = []
    context.settings = _make_settings()
    context.registry = ActorRegistry(
        actor_service=context.actor_service,
        provider_registry=context.provider_registry,
        settings=context.settings,
    )


@when("I ensure built-in actors")
def step_ensure_built_ins(context: Context) -> None:
    context.result = context.registry.ensure_built_in_actors()


@then("the registry result should be an empty list")
def step_assert_empty_list(context: Context) -> None:
    assert context.result == []


@given("a stubbed actor registry with configured providers")
def step_registry_with_providers(context: Context) -> None:
    context.actor_service = MagicMock()
    info = _make_provider_info("openai", "gpt-4")
    context.provider_registry = MagicMock()
    context.provider_registry.get_configured_providers.return_value = [info]
    context.actor_service.get_default_actor.return_value = None

    actor = _make_actor("openai/gpt-4")
    context.actor_service.upsert_actor.return_value = actor

    context.settings = _make_settings()
    context.registry = ActorRegistry(
        actor_service=context.actor_service,
        provider_registry=context.provider_registry,
        settings=context.settings,
    )


@then("the result should contain created actors")
def step_assert_actors_created(context: Context) -> None:
    assert len(context.result) > 0


@then("the default actor should be set")
def step_assert_default_set(context: Context) -> None:
    context.actor_service.set_default_actor.assert_called()


@given("a stubbed actor registry with configured providers and provider defaults")
def step_registry_with_defaults(context: Context) -> None:
    context.actor_service = MagicMock()
    info = _make_provider_info("anthropic", "claude-3", "anthropic")
    context.provider_registry = MagicMock()
    context.provider_registry.get_configured_providers.return_value = [info]
    context.actor_service.get_default_actor.return_value = None

    actor = _make_actor("anthropic/claude-3", provider="anthropic", model="claude-3")
    context.actor_service.upsert_actor.return_value = actor

    context.settings = _make_settings(provider="anthropic", model="claude-3")
    context.registry = ActorRegistry(
        actor_service=context.actor_service,
        provider_registry=context.provider_registry,
        settings=context.settings,
    )


@then("the preferred provider should be set as default")
def step_assert_preferred_default(context: Context) -> None:
    context.actor_service.set_default_actor.assert_called()


@when('I upsert a custom actor with name "local/test" and valid config')
def step_upsert_custom(context: Context) -> None:
    actor = _make_actor("local/test")
    context.actor_service.upsert_actor.return_value = actor
    context.result = context.registry.upsert_actor(
        name="local/test",
        config_blob={"provider": "openai", "model": "gpt-4"},
    )


@then("the actor should be persisted via the service")
def step_assert_persisted(context: Context) -> None:
    assert context.actor_service.upsert_actor.called


@when("I upsert an actor with unsafe config but no unsafe flag")
def step_upsert_unsafe_no_flag(context: Context) -> None:
    context.error = None
    try:
        context.registry.upsert_actor(
            name="local/unsafe",
            config_blob={"provider": "openai", "model": "gpt-4", "unsafe": True},
        )
    except ValidationError as exc:
        context.error = exc


@then("a ValidationError should be raised")
def step_assert_validation_error(context: Context) -> None:
    assert context.error is not None, "Expected ValidationError but none raised"
    assert isinstance(context.error, ValidationError)


@when('I get actor "local/test"')
def step_get_actor(context: Context) -> None:
    context.registry.get_actor("local/test")


@then("the actor service get_actor should be called")
def step_assert_get_called(context: Context) -> None:
    context.actor_service.get_actor.assert_called_with("local/test")


@when("I list actors")
def step_list_actors(context: Context) -> None:
    context.actor_service.list_actors.return_value = []
    context.registry.list_actors()


@then("the actor service list_actors should be called")
def step_assert_list_called(context: Context) -> None:
    context.actor_service.list_actors.assert_called()


@when('I remove actor "local/test"')
def step_remove_actor(context: Context) -> None:
    context.registry.remove_actor("local/test")


@then("the actor service remove_actor should be called")
def step_assert_remove_called(context: Context) -> None:
    context.actor_service.remove_actor.assert_called_with("local/test")


@when('I set default actor "local/test"')
def step_set_default(context: Context) -> None:
    context.actor_service.set_default_actor.return_value = _make_actor("local/test")
    context.registry.set_default_actor("local/test")


@then("the actor service set_default_actor should be called")
def step_assert_set_default_called(context: Context) -> None:
    context.actor_service.set_default_actor.assert_called_with("local/test")


@when("I get default actor")
def step_get_default(context: Context) -> None:
    context.actor_service.get_default_actor.return_value = None
    context.registry.get_default_actor()


@then("the actor service get_default_actor should be called")
def step_assert_get_default_called(context: Context) -> None:
    context.actor_service.get_default_actor.assert_called()
