"""Step definitions for tool_registry_service_fallback_coverage.feature.

Exercises the fallback method-resolution branches in ToolRegistryService:
- register_tool: falls back from create -> add -> create (AttributeError)
- update_tool: tool_config parameter paths (dict, Tool model)
- remove_tool: falls back from delete -> remove -> delete (AttributeError)
- list_tools: delegates to list_all
- attach_validation: invalid mode raises ValidationError

All repository dependencies are lightweight mocks — no database needed.
"""

from __future__ import annotations

from typing import Any

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

from cleveragents.application.services.tool_registry_service import (
    ToolRegistryService,
)
from cleveragents.core.exceptions import ValidationError
from cleveragents.domain.models.core.tool import Tool, ToolSource

# ---------------------------------------------------------------------------
# Mock repos with specific method presence
# ---------------------------------------------------------------------------


class _AddOnlyToolRepo:
    """A repo that has add() but no create() method."""

    def __init__(self) -> None:
        self._added: Any = None

    # Deliberately no create() method

    def add(self, tool: Any) -> Any:
        self._added = tool
        return tool

    def get_by_name(self, name: str) -> Any:
        return None

    def list_all(self, **kwargs: Any) -> list[Any]:
        return []

    def update(self, tool: Any) -> Any:
        return tool

    def delete(self, name: str) -> bool:
        return True


class _NoCreateNoAddToolRepo:
    """A repo that has neither create() nor add() as callable methods.

    Has create as a non-callable (string) to trigger the final fallback.
    """

    def __init__(self) -> None:
        # create is set to a non-callable to force the fallback path
        pass

    def get_by_name(self, name: str) -> Any:
        return None

    def list_all(self, **kwargs: Any) -> list[Any]:
        return []

    def update(self, tool: Any) -> Any:
        return tool


class _RemoveOnlyToolRepo:
    """A repo that has remove() but no delete() method."""

    def __init__(self) -> None:
        self._removed: str | None = None

    # Deliberately no delete() method

    def remove(self, name: str) -> bool:
        self._removed = name
        return True

    def get_by_name(self, name: str) -> Any:
        return None

    def create(self, tool: Any) -> Any:
        return tool

    def list_all(self, **kwargs: Any) -> list[Any]:
        return []

    def update(self, tool: Any) -> Any:
        return tool


class _NoDeleteNoRemoveToolRepo:
    """A repo that has neither delete() nor remove()."""

    def __init__(self) -> None:
        pass

    def get_by_name(self, name: str) -> Any:
        return None

    def create(self, tool: Any) -> Any:
        return tool

    def list_all(self, **kwargs: Any) -> list[Any]:
        return []

    def update(self, tool: Any) -> Any:
        return tool


class _StandardMockToolRepo:
    """Standard mock with all methods present."""

    def __init__(self) -> None:
        self._last_updated: Any = None
        self._list_all_return: list[Any] = [
            {"name": "local/tool-1"},
            {"name": "local/tool-2"},
        ]
        self._get_by_name_return: Any = {"name": "local/some-check"}

    def create(self, tool: Any) -> Any:
        return tool

    def get_by_name(self, name: str) -> Any:
        return self._get_by_name_return

    def list_all(
        self,
        namespace: str | None = None,
        tool_type: str | None = None,
        source: str | None = None,
    ) -> list[Any]:
        return self._list_all_return

    def update(self, tool: Any) -> Any:
        self._last_updated = tool
        return tool

    def delete(self, name: str) -> bool:
        return True


class _MinimalAttachmentRepo:
    """Attachment repo that satisfies the service interface."""

    def attach(self, **kwargs: Any) -> dict[str, str]:
        return {"attachment_id": "fallback-att-001"}

    def detach(self, attachment_id: str) -> bool:
        return True

    def list_for_resource(self, **kwargs: Any) -> list[Any]:
        return []


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


@given("a tool registry service with a repo that only has add method")
def step_given_add_only_repo(context: Context) -> None:
    context.fb_tool_repo = _AddOnlyToolRepo()
    context.fb_attachment_repo = _MinimalAttachmentRepo()
    context.fb_service = ToolRegistryService(
        tool_repo=context.fb_tool_repo,
        attachment_repo=context.fb_attachment_repo,
    )
    context.fb_error: Exception | None = None
    context.fb_result: Any = None


@given("a tool registry service with a repo that has no create or add methods")
def step_given_no_create_no_add(context: Context) -> None:
    context.fb_tool_repo = _NoCreateNoAddToolRepo()
    context.fb_attachment_repo = _MinimalAttachmentRepo()
    context.fb_service = ToolRegistryService(
        tool_repo=context.fb_tool_repo,
        attachment_repo=context.fb_attachment_repo,
    )
    context.fb_error = None
    context.fb_result = None


@given("a tool registry service with a standard mock repo")
def step_given_standard_repo(context: Context) -> None:
    context.fb_tool_repo = _StandardMockToolRepo()
    context.fb_attachment_repo = _MinimalAttachmentRepo()
    context.fb_service = ToolRegistryService(
        tool_repo=context.fb_tool_repo,
        attachment_repo=context.fb_attachment_repo,
    )
    context.fb_error = None
    context.fb_result = None


@given("a tool registry service with a repo that only has remove method")
def step_given_remove_only_repo(context: Context) -> None:
    context.fb_tool_repo = _RemoveOnlyToolRepo()
    context.fb_attachment_repo = _MinimalAttachmentRepo()
    context.fb_service = ToolRegistryService(
        tool_repo=context.fb_tool_repo,
        attachment_repo=context.fb_attachment_repo,
    )
    context.fb_error = None
    context.fb_result = None


@given("a tool registry service with a repo that has no delete or remove methods")
def step_given_no_delete_no_remove(context: Context) -> None:
    context.fb_tool_repo = _NoDeleteNoRemoveToolRepo()
    context.fb_attachment_repo = _MinimalAttachmentRepo()
    context.fb_service = ToolRegistryService(
        tool_repo=context.fb_tool_repo,
        attachment_repo=context.fb_attachment_repo,
    )
    context.fb_error = None
    context.fb_result = None


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


@when("I register a tool via the fallback service")
def step_when_register_fallback(context: Context) -> None:
    tool = {"name": "local/fallback-tool", "source": "builtin"}
    try:
        context.fb_result = context.fb_service.register_tool(tool)
        context.fb_error = None
    except Exception as exc:
        context.fb_error = exc


@when("I register a tool via the fallback service and expect error")
def step_when_register_fallback_error(context: Context) -> None:
    tool = {"name": "local/fallback-tool", "source": "builtin"}
    try:
        context.fb_result = context.fb_service.register_tool(tool)
        context.fb_error = None
    except Exception as exc:
        context.fb_error = exc


@when('I update tool "{name}" with dict config via the fallback service')
def step_when_update_dict_config(context: Context, name: str) -> None:
    try:
        context.fb_result = context.fb_service.update_tool(
            name,
            tool_config={"description": "Updated description"},
        )
        context.fb_error = None
    except Exception as exc:
        context.fb_error = exc


@when('I update tool "{name}" with Tool model config via the fallback service')
def step_when_update_tool_model(context: Context, name: str) -> None:
    try:
        tool_model = Tool(
            name="local/my-tool",
            description="A tool model",
            source=ToolSource.BUILTIN,
            tool_type="tool",
            timeout=300,
        )
        context.fb_result = context.fb_service.update_tool(name, tool_config=tool_model)
        context.fb_error = None
    except Exception as exc:
        context.fb_error = exc


@when("I update with dict tool and dict config via the fallback service")
def step_when_update_dict_tool_dict_config(context: Context) -> None:
    try:
        tool_dict = {"name": "local/dict-tool", "source": "builtin"}
        context.fb_result = context.fb_service.update_tool(
            tool_dict,
            tool_config={"description": "Updated"},
        )
        context.fb_error = None
    except Exception as exc:
        context.fb_error = exc


@when('I remove tool "{name}" via the fallback service')
def step_when_remove_fallback(context: Context, name: str) -> None:
    try:
        context.fb_result = context.fb_service.remove_tool(name)
        context.fb_error = None
    except Exception as exc:
        context.fb_error = exc


@when('I remove tool "{name}" via the fallback service and expect error')
def step_when_remove_fallback_error(context: Context, name: str) -> None:
    try:
        context.fb_result = context.fb_service.remove_tool(name)
        context.fb_error = None
    except Exception as exc:
        context.fb_error = exc


@when('I list tools with namespace "{ns}" and type "{tt}" via the fallback service')
def step_when_list_tools(context: Context, ns: str, tt: str) -> None:
    try:
        context.fb_result = context.fb_service.list_tools(namespace=ns, tool_type=tt)
        context.fb_error = None
    except Exception as exc:
        context.fb_error = exc


@when('I attach validation with invalid mode "{mode}" via the fallback service')
def step_when_attach_invalid_mode(context: Context, mode: str) -> None:
    try:
        context.fb_result = context.fb_service.attach_validation(
            validation_name="local/check",
            resource_id="res-001",
        )
        context.fb_error = None
    except Exception as exc:
        context.fb_error = exc


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


@then("the tool should be registered via the add method")
def step_then_registered_via_add(context: Context) -> None:
    assert context.fb_result is not None, "Expected a result from register_tool"
    assert hasattr(context.fb_tool_repo, "_added"), "Expected _added attribute on repo"
    assert context.fb_tool_repo._added is not None, (
        "Expected tool to be passed to add()"
    )


@then("no error should be raised by the fallback service")
def step_then_no_fallback_error(context: Context) -> None:
    assert context.fb_error is None, (
        f"Expected no error, got {type(context.fb_error).__name__}: {context.fb_error}"
    )


@then("a fallback AttributeError should be raised")
def step_then_attribute_error(context: Context) -> None:
    assert context.fb_error is not None, "Expected an error but no error was raised"
    assert isinstance(context.fb_error, AttributeError), (
        f"Expected AttributeError, got {type(context.fb_error).__name__}: "
        f"{context.fb_error}"
    )


@then('the updated tool should have name "{name}"')
def step_then_updated_has_name(context: Context, name: str) -> None:
    assert context.fb_result is not None, "Expected a result from update_tool"
    if isinstance(context.fb_result, dict):
        assert context.fb_result.get("name") == name, (
            f"Expected name '{name}', got {context.fb_result.get('name')}"
        )
    else:
        assert getattr(context.fb_result, "name", None) == name


@then("the update should use the Tool model directly")
def step_then_update_uses_model(context: Context) -> None:
    assert context.fb_result is not None, "Expected a result"
    assert isinstance(context.fb_result, Tool), (
        f"Expected Tool instance, got {type(context.fb_result).__name__}"
    )


@then("the updated tool should be the original dict")
def step_then_updated_is_dict(context: Context) -> None:
    assert context.fb_result is not None, "Expected a result"
    assert isinstance(context.fb_result, dict), (
        f"Expected dict, got {type(context.fb_result).__name__}"
    )


@then("the tool should be removed via the remove method")
def step_then_removed_via_remove(context: Context) -> None:
    assert context.fb_result is True, (
        f"Expected True from remove_tool, got {context.fb_result}"
    )
    assert hasattr(context.fb_tool_repo, "_removed"), (
        "Expected _removed attribute on repo"
    )
    assert context.fb_tool_repo._removed is not None, (
        "Expected tool name to be passed to remove()"
    )


@then("the list result should be returned from list_all")
def step_then_list_result(context: Context) -> None:
    assert context.fb_result is not None, "Expected a list result"
    assert isinstance(context.fb_result, list), (
        f"Expected list, got {type(context.fb_result).__name__}"
    )
    assert len(context.fb_result) == 2, (
        f"Expected 2 items, got {len(context.fb_result)}"
    )


@then('a fallback ValidationError should be raised with message containing "{text}"')
def step_then_validation_error(context: Context, text: str) -> None:
    assert context.fb_error is not None, (
        "Expected ValidationError but no error was raised"
    )
    assert isinstance(context.fb_error, ValidationError), (
        f"Expected ValidationError, got {type(context.fb_error).__name__}: "
        f"{context.fb_error}"
    )
    assert text in str(context.fb_error), (
        f"Expected '{text}' in error message, got '{context.fb_error}'"
    )
