"""Step definitions for virtual_builtin_actors.feature (issue #10923).

Verifies the virtual built-in actor behavior: actors are resolved on-demand
from the ProviderRegistry without any database writes.
"""

from __future__ import annotations

from unittest.mock import MagicMock

from behave import given, then, when
from behave.runner import Context

from cleveragents.actor.registry import ActorRegistry
from cleveragents.core.exceptions import ValidationError
from cleveragents.domain.models.core.actor import Actor
from features.mocks.fake_provider import FakeProviderInfo, FakeProviderRegistry


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_registry_with_providers(
    providers: list[FakeProviderInfo],
) -> tuple[MagicMock, ActorRegistry]:
    """Build a real ActorRegistry with a mock service and fake provider registry.

    The mock service is configured so that ``get_actor`` raises ``NotFoundError``
    for any name (simulating an empty database), allowing virtual built-in
    resolution to take over.
    """
    from cleveragents.core.exceptions import NotFoundError

    provider_reg = FakeProviderRegistry(providers=providers)

    mock_service = MagicMock()
    mock_service.list_actors.return_value = []
    mock_service.get_default_actor.return_value = None
    mock_service.get_default_actor_name.return_value = None
    # Simulate empty DB — all get_actor calls raise NotFoundError
    mock_service.get_actor.side_effect = lambda name: (_ for _ in ()).throw(
        NotFoundError(resource_type="actor", resource_id=name)
    )

    mock_settings = MagicMock()
    mock_settings.resolve_provider_defaults.return_value = MagicMock(
        provider=None, model=None
    )

    registry = ActorRegistry(
        actor_service=mock_service,
        provider_registry=provider_reg,
        settings=mock_settings,
    )
    return mock_service, registry


# ---------------------------------------------------------------------------
# Given steps
# ---------------------------------------------------------------------------


@given("a virtual actor registry with configured providers:")
def step_virtual_registry_with_providers(context: Context) -> None:
    """Build registry with providers from the data table."""
    providers = []
    for row in context.table:
        providers.append(
            FakeProviderInfo(
                name=row["name"],
                default_model=row["model"],
            )
        )
    mock_service, registry = _make_registry_with_providers(providers)
    context.virtual_mock_service = mock_service
    context.virtual_registry = registry


@given("a virtual actor registry with no configured providers")
def step_virtual_registry_no_providers(context: Context) -> None:
    """Build registry with no configured providers."""
    mock_service, registry = _make_registry_with_providers([])
    context.virtual_no_provider_service = mock_service
    context.virtual_no_provider_registry = registry


@given('the default actor preference is set to "{name}"')
def step_set_default_preference(context: Context, name: str) -> None:
    """Pre-configure the mock service to return a stored default name."""
    context.virtual_mock_service.get_default_actor_name.return_value = name


@given('a custom DB actor named "{name}" exists')
def step_custom_db_actor_exists(context: Context, name: str) -> None:
    """Add a custom (non-built-in) actor to the service's DB list."""
    provider_part, model_part = [*name.split("/", 1), ""][:2]
    custom = Actor(
        name=name,
        provider=provider_part,
        model=model_part,
        config_blob={"custom": True},
        config_hash=Actor.compute_hash({"custom": True}),
        is_built_in=False,
    )
    # Override list_actors to include this custom actor
    context.virtual_mock_service.list_actors.return_value = [custom]


# ---------------------------------------------------------------------------
# When steps
# ---------------------------------------------------------------------------


@when("I list virtual actors")
def step_list_virtual_actors(context: Context) -> None:
    """Invoke list_actors() on the registry."""
    context.virtual_actor_list = context.virtual_registry.list_actors()


@when("I list virtual actors via no-provider registry")
def step_list_no_provider_actors(context: Context) -> None:
    context.virtual_no_provider_list = (
        context.virtual_no_provider_registry.list_actors()
    )


@when('I show virtual actor "{name}"')
def step_show_virtual_actor(context: Context, name: str) -> None:
    """Resolve an actor by name via registry.get()."""
    context.virtual_actor_result = context.virtual_registry.get(name)


@when('I try to remove virtual actor "{name}"')
def step_try_remove_virtual_actor(context: Context, name: str) -> None:
    """Attempt to remove a virtual built-in actor."""
    context.virtual_remove_error = None
    try:
        context.virtual_registry.remove(name)
    except ValidationError as exc:
        context.virtual_remove_error = exc


@when('I set default virtual actor to "{name}"')
def step_set_default_virtual_actor(context: Context, name: str) -> None:
    """Call set_default_actor on the registry with a virtual built-in name."""
    context.virtual_actor_result = context.virtual_registry.set_default_actor(name)


@when("I get the default virtual actor")
def step_get_default_virtual_actor(context: Context) -> None:
    """Call get_default_actor on the registry."""
    context.virtual_actor_result = context.virtual_registry.get_default_actor()


# ---------------------------------------------------------------------------
# Then steps
# ---------------------------------------------------------------------------


@then('the virtual actor list should contain "{name}"')
def step_virtual_list_contains(context: Context, name: str) -> None:
    actors = context.virtual_actor_list
    names = [a.name for a in actors]
    assert name in names, f"Expected '{name}' in actor list {names}"


@then('the virtual actor list should show built-in flag for "{name}"')
def step_virtual_list_builtin_flag(context: Context, name: str) -> None:
    actors = context.virtual_actor_list
    actor = next((a for a in actors if a.name == name), None)
    assert actor is not None, f"Actor '{name}' not found in list"
    assert actor.is_built_in is True, (
        f"Expected is_built_in=True for '{name}', got {actor.is_built_in}"
    )


@then("no database writes should have occurred")
def step_no_db_writes(context: Context) -> None:
    service = context.virtual_mock_service
    assert service.upsert_actor.call_count == 0, (
        f"Expected no upsert_actor calls, got {service.upsert_actor.call_count}"
    )


@then('the virtual actor result should have name "{name}"')
def step_virtual_result_name(context: Context, name: str) -> None:
    actor = context.virtual_actor_result
    assert actor is not None, "Expected an actor result but got None"
    assert actor.name == name, f"Expected name '{name}', got '{actor.name}'"


@then("the virtual actor result should be marked as built-in")
def step_virtual_result_builtin(context: Context) -> None:
    actor = context.virtual_actor_result
    assert actor.is_built_in is True, (
        f"Expected is_built_in=True, got {actor.is_built_in}"
    )


@then("the virtual actor result should be marked as default")
def step_virtual_result_default(context: Context) -> None:
    actor = context.virtual_actor_result
    assert actor.is_default is True, f"Expected is_default=True, got {actor.is_default}"


@then("a virtual actor ValidationError should be raised")
def step_virtual_validation_error(context: Context) -> None:
    assert context.virtual_remove_error is not None, (
        "Expected a ValidationError but no error was raised"
    )
    assert isinstance(context.virtual_remove_error, ValidationError)


@then('the virtual builtin error message should mention "{text}"')
def step_error_message_contains(context: Context, text: str) -> None:
    error = context.virtual_remove_error
    assert error is not None, "No error to check message for"
    assert text.lower() in str(error).lower(), (
        f"Expected '{text}' in error message, got: {error}"
    )


@then('the virtual actor default name should be stored as "{name}"')
def step_default_name_stored(context: Context, name: str) -> None:
    service = context.virtual_mock_service
    service.set_default_actor_name.assert_called_once_with(name)


@then("no actor row should be created in the database")
def step_no_actor_row_created(context: Context) -> None:
    service = context.virtual_mock_service
    assert service.upsert_actor.call_count == 0, (
        f"Expected no upsert_actor calls, got {service.upsert_actor.call_count}"
    )
    assert service.set_default_actor.call_count == 0, (
        "Expected no set_default_actor (DB path) calls for virtual built-in"
    )


@then("the virtual actor service upsert_actor should not have been called")
def step_upsert_not_called(context: Context) -> None:
    service = context.virtual_mock_service
    assert service.upsert_actor.call_count == 0, (
        f"upsert_actor was called {service.upsert_actor.call_count} time(s)"
    )


@then("the virtual actor service set_default_actor should not have been called")
def step_set_default_actor_not_called(context: Context) -> None:
    service = context.virtual_mock_service
    assert service.set_default_actor.call_count == 0, (
        f"set_default_actor was called {service.set_default_actor.call_count} time(s)"
    )


@then("the virtual no-provider actor list should be empty")
def step_no_provider_list_empty(context: Context) -> None:
    assert context.virtual_no_provider_list == [], (
        f"Expected empty list, got {context.virtual_no_provider_list}"
    )


@then('the "{name}" actor in list should not be built-in')
def step_actor_not_builtin_in_list(context: Context, name: str) -> None:
    actors = context.virtual_actor_list
    actor = next((a for a in actors if a.name == name), None)
    assert actor is not None, f"Actor '{name}' not found in list"
    assert actor.is_built_in is False, (
        f"Expected is_built_in=False for '{name}' (custom actor should win)"
    )
