"""Step definitions for tool CLI uncovered branches."""

from __future__ import annotations

from collections import OrderedDict
from io import StringIO
from unittest.mock import MagicMock, patch

from behave import given, then, when
from rich.console import Console

from cleveragents.cli.commands.tool import _print_tool

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


class _FakeToolWithCliDict:
    """A tool object that returns a custom OrderedDict from as_cli_dict."""

    def __init__(self, data: dict):
        self._data = data

    def as_cli_dict(self) -> OrderedDict:
        return OrderedDict(self._data)


def _capture_rich_output(tool, title="Tool") -> str:
    """Call _print_tool in rich format and capture console output."""
    buf = StringIO()
    test_console = Console(file=buf, force_terminal=False, width=120)
    with patch("cleveragents.cli.commands.tool.console", test_console):
        _print_tool(tool, title=title, fmt="rich")
    return buf.getvalue()


# ===========================================================================
# Scenario: _get_tool_registry_service constructs service from container
# ===========================================================================


@given("tool cli branch the container returns a mock database url")
def step_tool_cli_branch_container_mock_db(context):
    mock_container = MagicMock()
    mock_container.database_url.return_value = "sqlite://"
    context.mock_container = mock_container


@when("tool cli branch I call _get_tool_registry_service")
def step_tool_cli_branch_call_get_service(context):
    mock_engine = MagicMock()
    mock_session_factory = MagicMock()

    with (
        patch(
            "cleveragents.cli.commands.tool.get_container",
            return_value=context.mock_container,
        )
        if hasattr(context, "_unused")
        else patch(
            "cleveragents.application.container.get_container",
            return_value=context.mock_container,
        ),
        patch(
            "sqlalchemy.create_engine",
            return_value=mock_engine,
        ) as _mock_create_engine,
        patch(
            "sqlalchemy.orm.sessionmaker",
            return_value=mock_session_factory,
        ) as _mock_sessionmaker,
        # The function does a local import of get_container, so we patch
        # it at the location where it's imported.
        patch(
            "cleveragents.application.container.get_container",
            return_value=context.mock_container,
        ),
    ):
        from cleveragents.cli.commands.tool import _get_tool_registry_service

        result = _get_tool_registry_service()
        context.service_result = result


@then("tool cli branch it should return a ToolRegistryService instance")
def step_tool_cli_branch_service_is_instance(context):
    from cleveragents.application.services.tool_registry_service import (
        ToolRegistryService,
    )

    assert isinstance(context.service_result, ToolRegistryService), (
        f"Expected ToolRegistryService, got {type(context.service_result)}"
    )


# ===========================================================================
# Scenario: _print_tool displays lifecycle dict with values
# ===========================================================================


@given("tool cli branch a tool with lifecycle hooks")
def step_tool_cli_branch_tool_with_lifecycle(context):
    context.tool_obj = _FakeToolWithCliDict(
        {
            "name": "local/lifecycle-tool",
            "description": "A tool with lifecycle",
            "source": "custom",
            "tool_type": "tool",
            "namespace": "local",
            "short_name": "lifecycle-tool",
            "timeout": 60,
            "capability": {"read_only": True},
            "lifecycle": {
                "on_start": "echo starting",
                "on_stop": "echo stopping",
                "on_error": None,
            },
        }
    )


@when("tool cli branch I print the tool in rich format")
def step_tool_cli_branch_print_lifecycle_tool(context):
    context.captured_output = _capture_rich_output(context.tool_obj)


@then("tool cli branch the output should contain lifecycle key values")
def step_tool_cli_branch_output_has_lifecycle(context):
    output = context.captured_output
    assert "on_start" in output, f"Expected 'on_start' in output:\n{output}"
    assert "echo starting" in output, f"Expected 'echo starting' in output:\n{output}"
    assert "on_stop" in output, f"Expected 'on_stop' in output:\n{output}"
    assert "echo stopping" in output, f"Expected 'echo stopping' in output:\n{output}"


# ===========================================================================
# Scenario: _print_tool displays resource slots list with dict items
# ===========================================================================


@given("tool cli branch a tool with resource slots")
def step_tool_cli_branch_tool_with_slots(context):
    context.tool_obj = _FakeToolWithCliDict(
        {
            "name": "local/slots-tool",
            "description": "A tool with resource slots",
            "source": "custom",
            "tool_type": "tool",
            "namespace": "local",
            "short_name": "slots-tool",
            "timeout": 120,
            "capability": {},
            "resource_slots": [
                {
                    "name": "database",
                    "resource_type": "postgres",
                    "access": "read",
                },
                {
                    "name": "cache",
                    "resource_type": "redis",
                    "access": "write",
                },
            ],
        }
    )


@when("tool cli branch I print the tool with slots in rich format")
def step_tool_cli_branch_print_slots_tool(context):
    context.captured_output = _capture_rich_output(context.tool_obj)


@then("tool cli branch the output should contain resource slot details")
def step_tool_cli_branch_output_has_slots(context):
    output = context.captured_output
    assert "database" in output, f"Expected 'database' in output:\n{output}"
    assert "postgres" in output, f"Expected 'postgres' in output:\n{output}"
    assert "read" in output, f"Expected 'read' in output:\n{output}"
    assert "cache" in output, f"Expected 'cache' in output:\n{output}"
    assert "redis" in output, f"Expected 'redis' in output:\n{output}"
    assert "write" in output, f"Expected 'write' in output:\n{output}"


# ===========================================================================
# Scenario: _print_tool displays both lifecycle and resource slots
# ===========================================================================


@given("tool cli branch a tool with lifecycle and resource slots")
def step_tool_cli_branch_tool_with_both(context):
    context.tool_obj = _FakeToolWithCliDict(
        {
            "name": "local/full-tool",
            "description": "A tool with both lifecycle and slots",
            "source": "custom",
            "tool_type": "tool",
            "namespace": "local",
            "short_name": "full-tool",
            "timeout": 90,
            "capability": {"writes": True, "checkpointable": False},
            "lifecycle": {
                "on_init": "setup.sh",
                "on_teardown": "cleanup.sh",
            },
            "resource_slots": [
                {
                    "name": "storage",
                    "resource_type": "s3",
                    "access": "readwrite",
                },
            ],
        }
    )


@when("tool cli branch I print the full tool in rich format")
def step_tool_cli_branch_print_full_tool(context):
    context.captured_output = _capture_rich_output(context.tool_obj)


@then("tool cli branch the output should contain both lifecycle and slot info")
def step_tool_cli_branch_output_has_both(context):
    output = context.captured_output
    # Lifecycle
    assert "on_init" in output, f"Expected 'on_init' in output:\n{output}"
    assert "setup.sh" in output, f"Expected 'setup.sh' in output:\n{output}"
    assert "on_teardown" in output, f"Expected 'on_teardown' in output:\n{output}"
    assert "cleanup.sh" in output, f"Expected 'cleanup.sh' in output:\n{output}"
    # Resource slots
    assert "storage" in output, f"Expected 'storage' in output:\n{output}"
    assert "s3" in output, f"Expected 's3' in output:\n{output}"
    assert "readwrite" in output, f"Expected 'readwrite' in output:\n{output}"


# ===========================================================================
# Scenario: remove command handles service returning False
# ===========================================================================


@given("tool cli branch a mock service where remove_tool returns False")
def step_tool_cli_branch_mock_service_remove_false(context):
    mock_service = MagicMock()
    # get_tool returns a truthy value so we pass the "not found" check
    mock_service.get_tool.return_value = {"name": "local/doomed-tool"}
    # remove_tool returns False → triggers the "Failed to remove" branch
    mock_service.remove_tool.return_value = False
    context.mock_service = mock_service


@when("tool cli branch I invoke the remove command with confirmation")
def step_tool_cli_branch_invoke_remove(context):
    from typer.testing import CliRunner

    from cleveragents.cli.commands.tool import app as tool_app

    runner = CliRunner()
    with patch(
        "cleveragents.cli.commands.tool._get_tool_registry_service",
        return_value=context.mock_service,
    ):
        result = runner.invoke(tool_app, ["remove", "--yes", "local/doomed-tool"])
    context.result = result


@then("tool cli branch the output should show failed to remove tool")
def step_tool_cli_branch_output_failed_remove(context):
    output = context.result.output
    assert "Failed to remove tool" in output, (
        f"Expected 'Failed to remove tool' in output:\n{output}"
    )
