"""Step definitions for tool_registry_service_coverage.feature.

Exercises the error-handling and delegation branches in ToolRegistryService.
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 DatabaseError, NotFoundError
from cleveragents.infrastructure.database.repositories import (
    DuplicateToolError,
    ToolInUseError,
    ToolNotFoundError,
)

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


class _MockToolRepo:
    """Minimal mock that satisfies ToolRegistryService's expectations."""

    def __init__(self) -> None:
        self._get_by_name_return: Any = None
        self._create_side_effect: Exception | None = None
        self._update_side_effect: Exception | None = None
        self._delete_side_effect: Exception | None = None
        self._delete_return: bool = True

    def create(self, tool: Any) -> Any:
        if self._create_side_effect is not None:
            raise self._create_side_effect
        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 []

    def update(self, tool: Any) -> Any:
        if self._update_side_effect is not None:
            raise self._update_side_effect
        return tool

    def delete(self, name: str) -> bool:
        if self._delete_side_effect is not None:
            raise self._delete_side_effect
        return self._delete_return


class _MockAttachmentRepo:
    """No-op attachment repository mock."""

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

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

    def list_for_resource(
        self,
        resource_id: str,
        project_name: str | None = None,
        plan_id: str | None = None,
    ) -> list[Any]:
        return []


# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------


@given("a mock-based tool registry service")
def step_mock_service(context: Context) -> None:
    context.cov_tool_repo = _MockToolRepo()
    context.cov_attachment_repo = _MockAttachmentRepo()
    context.cov_service = ToolRegistryService(
        tool_repo=context.cov_tool_repo,
        attachment_repo=context.cov_attachment_repo,
    )
    context.cov_error: Exception | None = None
    context.cov_result: Any = None


# ---------------------------------------------------------------------------
# Given: repo behaviour setup
# ---------------------------------------------------------------------------


@given("the mock tool repo create method raises DuplicateToolError")
def step_create_raises_duplicate(context: Context) -> None:
    context.cov_tool_repo._create_side_effect = DuplicateToolError("test-tool")


@given("the mock tool repo create method raises DatabaseError")
def step_create_raises_db_error(context: Context) -> None:
    context.cov_tool_repo._create_side_effect = DatabaseError(
        "Failed to create tool: disk I/O error"
    )


@given("the mock tool repo update method raises ToolNotFoundError")
def step_update_raises_not_found(context: Context) -> None:
    context.cov_tool_repo._update_side_effect = ToolNotFoundError("gone")


@given("the mock tool repo update method raises DatabaseError")
def step_update_raises_db_error(context: Context) -> None:
    context.cov_tool_repo._update_side_effect = DatabaseError(
        "Failed to update tool: disk I/O error"
    )


@given("the mock tool repo delete method returns False")
def step_delete_returns_false(context: Context) -> None:
    context.cov_tool_repo._delete_return = False


@given("the mock tool repo delete method raises ToolInUseError")
def step_delete_raises_in_use(context: Context) -> None:
    context.cov_tool_repo._delete_side_effect = ToolInUseError("test-tool", 2)


@given("the mock tool repo returns None for get_by_name")
def step_repo_returns_none(context: Context) -> None:
    context.cov_tool_repo._get_by_name_return = None


@given("the mock tool repo returns a sentinel for get_by_name")
def step_repo_returns_sentinel(context: Context) -> None:
    context.cov_tool_repo._get_by_name_return = {
        "name": "local/some-check",
        "mode": "required",
        "tool_type": "validation",
    }


# ---------------------------------------------------------------------------
# When
# ---------------------------------------------------------------------------


@when("I attempt to register the tool via the coverage service")
def step_when_register(context: Context) -> None:
    tool_config = {"name": "local/test-tool", "tool_type": "tool", "source": "builtin"}
    try:
        context.cov_result = context.cov_service.register_tool(tool_config)
        context.cov_error = None
    except Exception as exc:
        context.cov_error = exc


@when("I attempt to update a tool via the coverage service")
def step_when_update(context: Context) -> None:
    tool_config = {"name": "local/test-tool", "tool_type": "tool"}
    try:
        context.cov_result = context.cov_service.update_tool(tool_config)
        context.cov_error = None
    except Exception as exc:
        context.cov_error = exc


@when('I attempt to remove tool "{name}" via the coverage service')
def step_when_remove(context: Context, name: str) -> None:
    try:
        context.cov_result = context.cov_service.remove_tool(name)
        context.cov_error = None
    except Exception as exc:
        context.cov_error = exc


@when("I attempt to attach validation via the coverage service")
def step_when_attach(context: Context) -> None:
    try:
        context.cov_result = context.cov_service.attach_validation(
            resource_id="res-001",
            validation_name="local/some-check",
        )
        context.cov_error = None
    except Exception as exc:
        context.cov_error = exc


# ---------------------------------------------------------------------------
# Then
# ---------------------------------------------------------------------------


@then("a coverage DuplicateToolError should be raised")
def step_then_duplicate_tool_error(context: Context) -> None:
    assert context.cov_error is not None, (
        "Expected DuplicateToolError but no error was raised"
    )
    assert isinstance(context.cov_error, DuplicateToolError), (
        f"Expected DuplicateToolError, got {type(context.cov_error).__name__}: {context.cov_error}"
    )


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


@then("a coverage ToolNotFoundError should be raised")
def step_then_tool_not_found(context: Context) -> None:
    assert context.cov_error is not None, (
        "Expected ToolNotFoundError but no error was raised"
    )
    assert isinstance(context.cov_error, ToolNotFoundError), (
        f"Expected ToolNotFoundError, got {type(context.cov_error).__name__}: {context.cov_error}"
    )


@then("a coverage ToolInUseError should be raised")
def step_then_tool_in_use(context: Context) -> None:
    assert context.cov_error is not None, (
        "Expected ToolInUseError but no error was raised"
    )
    assert isinstance(context.cov_error, ToolInUseError), (
        f"Expected ToolInUseError, got {type(context.cov_error).__name__}: {context.cov_error}"
    )


@then("the removal result should be False")
def step_then_result_false(context: Context) -> None:
    assert context.cov_error is None, f"Expected no error, got {context.cov_error}"
    assert context.cov_result is False, f"Expected False, got {context.cov_result}"


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


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