"""Behave steps covering actor registry behaviors."""

from __future__ import annotations

import ast
from typing import Any

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

from cleveragents.actor.registry import ActorRegistry
from cleveragents.config.settings import ProviderDefaults
from cleveragents.core.exceptions import ValidationError
from cleveragents.domain.models.core.actor import Actor
from cleveragents.providers.registry import (
    ProviderCapabilities,
    ProviderInfo,
    ProviderType,
)


class _StubSettings:
    def __init__(self, defaults: ProviderDefaults) -> None:
        self._defaults = defaults

    def resolve_provider_defaults(self) -> ProviderDefaults:
        return self._defaults


class _StubProviderRegistry:
    def __init__(self, providers: list[ProviderInfo]) -> None:
        self._providers = providers
        self.calls = 0

    def get_configured_providers(self) -> list[ProviderInfo]:
        self.calls += 1
        return list(self._providers)


class _StubActorService:
    def __init__(self) -> None:
        self.actors: dict[str, Actor] = {}
        self.default_actor_name: str | None = None
        self.upsert_payloads: list[dict[str, Any]] = []

    def upsert_actor(
        self,
        *,
        name: str,
        provider: str,
        model: str,
        config_blob: dict[str, Any],
        graph_descriptor: dict[str, Any] | None,
        unsafe: bool,
        set_default: bool,
        is_built_in: bool,
        yaml_text: str | None = None,
        schema_version: str | None = None,
        compiled_metadata: dict[str, Any] | None = None,
    ) -> Actor:
        actor = Actor(
            id=None,
            name=name,
            provider=provider,
            model=model,
            config_blob=config_blob,
            config_hash=Actor.compute_hash(config_blob),
            graph_descriptor=graph_descriptor,
            yaml_text=yaml_text,
            schema_version=schema_version or "1.0",
            compiled_metadata=compiled_metadata,
            unsafe=unsafe,
            is_built_in=is_built_in,
            is_default=False,
        )
        self.actors[name] = actor
        if set_default:
            self.set_default_actor(name)
        self.upsert_payloads.append(
            {
                "name": name,
                "provider": provider,
                "model": model,
                "config_blob": dict(config_blob),
                "graph_descriptor": graph_descriptor,
                "unsafe": unsafe,
                "set_default": set_default,
                "is_built_in": is_built_in,
            }
        )
        return actor

    def get_default_actor(self) -> Actor | None:
        if self.default_actor_name and self.default_actor_name in self.actors:
            return self.actors[self.default_actor_name]
        return None

    def set_default_actor(self, name: str) -> Actor:
        actor = self.actors.get(name)
        if actor is None:
            raise ValueError("Actor does not exist")
        self.default_actor_name = name
        for value in self.actors.values():
            value.is_default = value.name == name
        return actor

    def get_actor(self, name: str) -> Actor | None:
        return self.actors.get(name)

    def list_actors(self) -> list[Actor]:
        return list(self.actors.values())

    def remove_actor(self, name: str) -> None:
        self.actors.pop(name, None)
        if self.default_actor_name == name:
            self.default_actor_name = None


def _default_defaults() -> ProviderDefaults:
    return ProviderDefaults(
        provider=None, provider_source="test", model=None, model_source="test"
    )


def _build_registry(
    context: Context,
    providers: list[ProviderInfo],
    defaults: ProviderDefaults | None = None,
) -> None:
    context.actor_service = _StubActorService()
    context.provider_registry = _StubProviderRegistry(providers)
    context.settings = _StubSettings(defaults or _default_defaults())
    context.registry = ActorRegistry(
        actor_service=context.actor_service,
        provider_registry=context.provider_registry,
        settings=context.settings,
    )


@given('provider defaults are provider "{provider}" model "{model}"')
def step_provider_defaults(context: Context, provider: str, model: str) -> None:
    normalized_provider = provider or None
    normalized_model = model or None
    context.default_values = ProviderDefaults(
        provider=normalized_provider,
        provider_source="scenario",
        model=normalized_model,
        model_source="scenario",
    )


@given("an actor registry with configured providers:")
def step_registry_with_providers(context: Context) -> None:
    providers: list[ProviderInfo] = []
    for row in context.table:
        provider_type = ProviderType(row["type"])
        providers.append(
            ProviderInfo(
                provider_type=provider_type,
                name=row["name"],
                api_key_env_var="ENV",
                default_model=row["model"],
                capabilities=ProviderCapabilities(),
                is_configured=True,
            )
        )
    defaults = getattr(context, "default_values", _default_defaults())
    _build_registry(context, providers, defaults)


@given("an actor registry with no configured providers")
def step_registry_with_no_providers(context: Context) -> None:
    defaults = getattr(context, "default_values", _default_defaults())
    _build_registry(context, [], defaults)


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


@then("no actors should be created")
def step_no_actors_created(context: Context) -> None:
    assert not context.actor_service.actors
    assert context.generated_actors == []


@then("no default actor should be set")
def step_no_default_set(context: Context) -> None:
    assert context.actor_service.get_default_actor() is None


@then("the registry should list actors {expected}")
def step_registry_should_list(context: Context, expected: str) -> None:
    expected_list = ast.literal_eval(expected)
    actual_names = [actor.name for actor in context.registry.list_actors()]
    assert actual_names == expected_list, actual_names


@then('the default actor should be "{expected}"')
def step_default_actor(context: Context, expected: str) -> None:
    actor = context.actor_service.get_default_actor()
    assert actor is not None, "No default actor was set"
    assert actor.name == expected


@when('I remove actor "{name}"')
def step_remove_actor(context: Context, name: str) -> None:
    context.registry.remove_actor(name)


@then("the stored actors should be {expected}")
def step_stored_actors(context: Context, expected: str) -> None:
    expected_list = ast.literal_eval(expected)
    assert sorted(context.actor_service.actors.keys()) == sorted(expected_list)


@when('I attempt to upsert actor "{name}" with unsafe config')
def step_upsert_unsafe_actor(context: Context, name: str) -> None:
    try:
        context.registry.upsert_actor(
            name=name,
            config_blob={"provider": "local", "model": "unsafe", "unsafe": True},
        )
        context.error = None
    except Exception as exc:
        context.error = exc


@then("a registry validation error should be raised for unsafe actor")
def step_expect_validation_error(context: Context) -> None:
    assert isinstance(context.error, ValidationError), type(context.error)


@when(
    'I upsert actor "{name}" with provider "{provider}" model "{model}" graph descriptor {graph_descriptor} and options {options_blob}'
)
def step_upsert_actor_with_graph(
    context: Context,
    name: str,
    provider: str,
    model: str,
    graph_descriptor: str,
    options_blob: str,
) -> None:
    graph = ast.literal_eval(graph_descriptor)
    options = ast.literal_eval(options_blob)
    context.saved_actor = context.registry.upsert_actor(
        name=name,
        provider=provider,
        model=model,
        graph_descriptor=graph,
        config_blob=options,
    )


@when('I upsert actor "{name}" with raw config {config_blob}')
def step_upsert_actor_raw_config(context: Context, name: str, config_blob: str) -> None:
    blob = ast.literal_eval(config_blob)
    context.saved_actor = context.registry.upsert_actor(name=name, config_blob=blob)


@when('I retrieve actor "{name}" via the registry')
def step_get_actor_via_registry(context: Context, name: str) -> None:
    context.retrieved_actor = context.registry.get_actor(name)


@when('I set the default actor to "{name}" via the registry')
def step_set_default_actor_via_registry(context: Context, name: str) -> None:
    context.registry.set_default_actor(name)


@then(
    'the stored actor config should include provider "{provider}" model "{model}" graph descriptor {graph_descriptor} unsafe false and option "{option_key}" "{option_value}"'
)
def step_assert_canonical_blob(
    context: Context,
    provider: str,
    model: str,
    graph_descriptor: str,
    option_key: str,
    option_value: str,
) -> None:
    graph = ast.literal_eval(graph_descriptor)
    assert context.actor_service.upsert_payloads, "No actor was upserted"
    blob = context.actor_service.upsert_payloads[-1]["config_blob"]
    assert blob["provider"] == provider
    assert blob["model"] == model
    assert blob["graph_descriptor"] == graph
    assert blob.get("unsafe") is False
    assert blob.get(option_key) == option_value


@then(
    'the saved actor config should keep graph descriptor {graph_descriptor} and option "{option_key}" {option_value}'
)
def step_assert_preserved_graph_and_option(
    context: Context,
    graph_descriptor: str,
    option_key: str,
    option_value: str,
) -> None:
    graph = ast.literal_eval(graph_descriptor)
    parsed_value = ast.literal_eval(option_value)
    assert context.actor_service.upsert_payloads, "No actor was upserted"
    blob = context.actor_service.upsert_payloads[-1]["config_blob"]
    assert blob["graph_descriptor"] == graph
    assert blob.get("options", {}).get(option_key) == parsed_value
    assert blob.get("unsafe") is False


@then('the registry default actor via wrapper should be "{expected}"')
def step_assert_wrapper_default_actor(context: Context, expected: str) -> None:
    actor = context.registry.get_default_actor()
    assert actor is not None, "No default actor set via registry"
    assert actor.name == expected


@then(
    'the built-in actor payload should include graph descriptor and capabilities for "{actor_name}"'
)
def step_assert_built_in_payload(context: Context, actor_name: str) -> None:
    assert context.actor_service.upsert_payloads, "No actors were persisted"
    payload = None
    for item in context.actor_service.upsert_payloads:
        if item.get("name") == actor_name:
            payload = item
            break
    assert payload is not None, f"Actor {actor_name} not found in payloads"
    blob = payload["config_blob"]
    graph = blob.get("graph_descriptor")
    assert graph is not None, "graph_descriptor missing on built-in actor"
    assert graph.get("provider")
    assert graph.get("model")
    assert graph.get("source") == "provider-registry"
    capabilities = blob.get("capabilities")
    assert capabilities is not None, "capabilities missing on built-in actor config"
