"""Step definitions for resource-type inheritance: additional coverage and edge cases.

Covers ADR-042 sections:
9.  Additional coverage
10. Coverage edge cases
"""

from __future__ import annotations

from typing import Any

from _inheritance_test_helpers import (
    _NOOP_HANDLER,
    _ensure_registry,
    _ensure_tool_registry,
    _make_child_entry,
    _make_root_entry,
    _names_from_csv,
)
from behave import given, then, when

from cleveragents.resource.inheritance import (
    ResourceTypeParentNotFoundError,
    validate_chain,
)
from cleveragents.tool.registry import ToolRegistry
from cleveragents.tool.runtime import ToolSpec

# ═══════════════════════════════════════════════════════════════
# 9. Additional coverage — GIVEN / WHEN / THEN
# ═══════════════════════════════════════════════════════════════


@given(
    'a type-inherit grandchild "{grandchild}" inheriting from "{parent}" with cli_args "{args_csv}"'
)
def step_grandchild_cli_args(
    context: Any, grandchild: str, parent: str, args_csv: str
) -> None:
    """Add a grandchild type with its own cli_args."""
    reg = _ensure_registry(context)
    cli_args = [{"name": n} for n in _names_from_csv(args_csv)]
    reg[grandchild] = _make_child_entry(parent, cli_args=cli_args)


@given('a type-inherit registry with "{name}" as root with handler "{handler}"')
def step_registry_root_with_handler(context: Any, name: str, handler: str) -> None:
    """Add a root type with a specific handler reference."""
    reg = _ensure_registry(context)
    reg[name] = _make_root_entry(handler=handler)


@when('I resolve type-inherit handler polymorphic for "{name}"')
def step_resolve_handler_polymorphic(context: Any, name: str) -> None:
    """Resolve a handler via the polymorphic resolver."""
    from cleveragents.resource.handlers.resolver import resolve_handler_polymorphic

    reg = _ensure_registry(context)
    context.type_inherit_resolved_handler = resolve_handler_polymorphic(name, reg)  # type: ignore[attr-defined]


@then("the type-inherit resolved handler is not None")
def step_resolved_handler_not_none(context: Any) -> None:
    """Assert the resolved handler is a ResourceHandler instance."""
    from cleveragents.resource.handlers.protocol import ResourceHandler

    handler = context.type_inherit_resolved_handler  # type: ignore[attr-defined]
    assert isinstance(handler, ResourceHandler), (
        f"Expected a ResourceHandler instance, got {type(handler).__name__}"
    )


@when(
    'I validate type-inherit chain for built-in "{name}" inheriting "{parent}" expecting error'
)
def step_validate_chain_builtin_error(context: Any, name: str, parent: str) -> None:
    """Run validate_chain with is_built_in=True and capture any error."""
    reg = _ensure_registry(context)
    try:
        validate_chain(name, parent, reg, is_built_in=True)
        context.type_inherit_validation_error = None  # type: ignore[attr-defined]
    except ValueError as exc:
        context.type_inherit_validation_error = exc  # type: ignore[attr-defined]


@then("the type-inherit validation error is ValueError")
def step_validation_error_value_error(context: Any) -> None:
    """Assert the validation raised a ValueError (or subclass)."""
    err = context.type_inherit_validation_error  # type: ignore[attr-defined]
    assert err is not None, "Expected an error but none was raised"
    assert isinstance(err, ValueError), (
        f"Expected ValueError, got {type(err).__name__}: {err}"
    )


# ═══════════════════════════════════════════════════════════════
# 10. Coverage edge-case steps
# ═══════════════════════════════════════════════════════════════


@then("the type-inherit chain error is ResourceTypeParentNotFoundError")
def step_chain_error_parent_not_found(context: Any) -> None:
    """Assert the chain error is ResourceTypeParentNotFoundError."""
    err = context.type_inherit_chain_error  # type: ignore[attr-defined]
    assert err is not None, "Expected an error but none was raised"
    assert isinstance(err, ResourceTypeParentNotFoundError), (
        f"Expected ResourceTypeParentNotFoundError, got {type(err).__name__}: {err}"
    )


@when('I validate type-inherit chain for "{name}" inheriting nothing')
def step_validate_chain_no_parent(context: Any, name: str) -> None:
    """Run validate_chain with inherits=None."""
    reg = _ensure_registry(context)
    context.type_inherit_validated_chain = validate_chain(name, None, reg)  # type: ignore[attr-defined]


@given('the type-inherit registry entry for "{name}" is removed')
def step_remove_registry_entry(context: Any, name: str) -> None:
    """Remove an entry from the in-memory registry."""
    reg = _ensure_registry(context)
    reg.pop(name, None)


@then('the type-inherit resolved fields should not contain "{key}"')
def step_resolved_no_key(context: Any, key: str) -> None:
    """Assert the resolved fields do NOT contain the given key."""
    resolved = context.type_inherit_resolved  # type: ignore[attr-defined]
    assert key not in resolved, (
        f"Expected key '{key}' absent, but found: {resolved.get(key)}"
    )


class _ObjEntry:
    """Minimal object-style registry entry for testing _get_inherits."""

    def __init__(self, inherits: str | None = None, **kwargs: Any) -> None:
        self.inherits = inherits
        for k, v in kwargs.items():
            setattr(self, k, v)


@given('a type-inherit registry with object-style entry "{name}" inheriting "{parent}"')
def step_registry_obj_entry(context: Any, name: str, parent: str) -> None:
    """Add an object-style (non-dict) entry to the registry."""
    reg = _ensure_registry(context)
    reg[name] = _ObjEntry(inherits=parent)


class _PydanticLikeEntry:
    """Fake entry with model_dump() for testing _to_dict branch."""

    def __init__(self, **kwargs: Any) -> None:
        self._data = kwargs

    def model_dump(self) -> dict[str, Any]:
        return dict(self._data)


@given(
    'a type-inherit registry with pydantic-style entry "{name}" having description "{desc}"'
)
def step_registry_pydantic_entry(context: Any, name: str, desc: str) -> None:
    """Add a pydantic-model-like entry to the registry."""
    reg = _ensure_registry(context)
    reg[name] = _PydanticLikeEntry(description=desc)


@given('a type-inherit parent "{name}" with no cli_args')
def step_parent_no_cli_args(context: Any, name: str) -> None:
    """Add a root type with empty cli_args."""
    reg = _ensure_registry(context)
    reg[name] = _make_root_entry(cli_args=[])


@given('a type-inherit child "{child}" inheriting from "{parent}" with no cli_args')
def step_child_no_cli_args(context: Any, child: str, parent: str) -> None:
    """Add a child type with no cli_args."""
    reg = _ensure_registry(context)
    reg[child] = _make_child_entry(parent, cli_args=[])


@when('I resolve type-inherit handler polymorphic for "{name}" expecting error')
def step_resolve_handler_polymorphic_error(context: Any, name: str) -> None:
    """Resolve handler and capture any error."""
    from cleveragents.resource.handlers.resolver import resolve_handler_polymorphic

    reg = _ensure_registry(context)
    from cleveragents.resource.handlers.resolver import HandlerResolutionError

    try:
        resolve_handler_polymorphic(name, reg)
        context.type_inherit_handler_error = None  # type: ignore[attr-defined]
    except HandlerResolutionError as exc:
        context.type_inherit_handler_error = exc  # type: ignore[attr-defined]


@then("the type-inherit handler error is HandlerResolutionError")
def step_handler_error_check(context: Any) -> None:
    """Assert the handler resolution raised HandlerResolutionError."""
    from cleveragents.resource.handlers.resolver import HandlerResolutionError

    err = context.type_inherit_handler_error  # type: ignore[attr-defined]
    assert err is not None, "Expected an error but none was raised"
    assert isinstance(err, HandlerResolutionError), (
        f"Expected HandlerResolutionError, got {type(err).__name__}: {err}"
    )


# -- ToolRegistry coverage steps -----------------------------------------


@given('a type-inherit tool registry with tool "{name}" bound to "{res_type}"')
def step_tool_registry_add(context: Any, name: str, res_type: str) -> None:
    """Add a tool to the tool registry."""
    tool_reg = _ensure_tool_registry(context)
    spec = ToolSpec(
        name=name,
        description=f"Tool {name}",
        handler=_NOOP_HANDLER,
        source="test",
        source_metadata={"resource_bindings": [{"resource_type": res_type}]},
    )
    tool_reg.register(spec)


@when('I add type-inherit tool "{name}" again expecting error')
def step_tool_registry_add_dup(context: Any, name: str) -> None:
    """Attempt to add a duplicate tool."""
    tool_reg = _ensure_tool_registry(context)
    spec = ToolSpec(
        name=name,
        description="dup",
        handler=_NOOP_HANDLER,
        source="test",
        source_metadata={},
    )
    from cleveragents.tool.runtime import ToolError

    try:
        tool_reg.register(spec)
        context.type_inherit_tool_error = None  # type: ignore[attr-defined]
    except ToolError as exc:
        context.type_inherit_tool_error = exc  # type: ignore[attr-defined]


@then("the type-inherit tool error is ToolError")
def step_tool_error_check(context: Any) -> None:
    """Assert a ToolError was raised."""
    from cleveragents.tool.runtime import ToolError

    err = context.type_inherit_tool_error  # type: ignore[attr-defined]
    assert err is not None, "Expected an error but none was raised"
    assert isinstance(err, ToolError), (
        f"Expected ToolError, got {type(err).__name__}: {err}"
    )


@given("a type-inherit empty tool registry")
def step_empty_tool_registry(context: Any) -> None:
    """Create an empty tool registry."""
    context.type_inherit_tool_registry = ToolRegistry()  # type: ignore[attr-defined]


@when('I get type-inherit tool "{name}"')
def step_tool_registry_get(context: Any, name: str) -> None:
    """Lookup a tool by name."""
    tool_reg = _ensure_tool_registry(context)
    context.type_inherit_tool_result = tool_reg.get(name)  # type: ignore[attr-defined]


@then("the type-inherit tool result is None")
def step_tool_result_none(context: Any) -> None:
    """Assert the tool lookup returned None."""
    assert context.type_inherit_tool_result is None  # type: ignore[attr-defined]


@when('I list type-inherit tools with namespace "{ns}"')
def step_tool_registry_list(context: Any, ns: str) -> None:
    """List tools filtered by namespace."""
    tool_reg = _ensure_tool_registry(context)
    context.type_inherit_tools_list = tool_reg.list_tools(namespace=ns)  # type: ignore[attr-defined]


@then("the type-inherit tools list has {count:d} entry")
def step_tools_list_count(context: Any, count: int) -> None:
    """Assert the tools list has the expected number of entries."""
    result = context.type_inherit_tools_list  # type: ignore[attr-defined]
    assert len(result) == count, f"Expected {count}, got {len(result)}"


@when('I remove type-inherit tool "{name}"')
def step_tool_registry_remove(context: Any, name: str) -> None:
    """Remove a tool from the registry."""
    tool_reg = _ensure_tool_registry(context)
    context.type_inherit_tool_remove_result = tool_reg.remove(name)  # type: ignore[attr-defined]


@then("the type-inherit tool remove result is true")
def step_tool_remove_true(context: Any) -> None:
    """Assert tool removal returned True."""
    assert context.type_inherit_tool_remove_result is True  # type: ignore[attr-defined]


@then("the type-inherit tool remove result is false")
def step_tool_remove_false(context: Any) -> None:
    """Assert tool removal returned False."""
    assert context.type_inherit_tool_remove_result is False  # type: ignore[attr-defined]


@given('a type-inherit tool registry with unbound tool "{name}"')
def step_tool_registry_unbound(context: Any, name: str) -> None:
    """Add a tool with no resource bindings."""
    tool_reg = _ensure_tool_registry(context)
    spec = ToolSpec(
        name=name,
        description=f"Unbound tool {name}",
        handler=_NOOP_HANDLER,
        source="test",
        source_metadata={},
    )
    tool_reg.register(spec)


@when('I list type-inherit tools with source "{src}"')
def step_tool_registry_list_by_source(context: Any, src: str) -> None:
    """List tools filtered by source."""
    tool_reg = _ensure_tool_registry(context)
    context.type_inherit_tools_list = tool_reg.list_tools(source=src)  # type: ignore[attr-defined]


@when("I resolve type-inherit handler with empty reference expecting error")
def step_resolve_handler_empty(context: Any) -> None:
    """Try to resolve an empty handler reference."""
    from cleveragents.resource.handlers.resolver import (
        HandlerResolutionError,
        resolve_handler,
    )

    try:
        resolve_handler("")
        context.type_inherit_handler_error = None  # type: ignore[attr-defined]
    except HandlerResolutionError as exc:
        context.type_inherit_handler_error = exc  # type: ignore[attr-defined]


@when('I resolve type-inherit handler with reference "{ref}" expecting error')
def step_resolve_handler_bad_ref(context: Any, ref: str) -> None:
    """Try to resolve a handler with a bad reference."""
    from cleveragents.resource.handlers.resolver import (
        HandlerResolutionError,
        clear_handler_cache,
        resolve_handler,
    )

    clear_handler_cache()
    try:
        resolve_handler(ref)
        context.type_inherit_handler_error = None  # type: ignore[attr-defined]
    except HandlerResolutionError as exc:
        context.type_inherit_handler_error = exc  # type: ignore[attr-defined]


# ═══════════════════════════════════════════════════════════════
# F5: CLI integration — type list / type show
# ═══════════════════════════════════════════════════════════════

from unittest.mock import MagicMock, patch  # noqa: E402

_PATCH_SVC = "cleveragents.cli.commands.resource._get_registry_service"
_PATCH_CON = "cleveragents.cli.commands.resource.console"


def _plain_console():  # type: ignore[return]
    from rich.console import Console

    return Console(
        no_color=True,
        highlight=False,
        force_terminal=False,
        width=200,
    )


def _mock_type_spec(
    name: str,
    *,
    inherits: str | None = None,
    built_in: bool = False,
) -> MagicMock:
    """Build a mock ResourceTypeSpec."""
    spec = MagicMock()
    spec.name = name
    spec.inherits = inherits
    spec.description = f"Mock {name}"
    spec.resource_kind = "physical"
    spec.sandbox_strategy = "none"
    spec.user_addable = True
    spec.built_in = built_in
    spec.cli_args = []
    spec.parent_types = []
    spec.child_types = []
    spec.handler = None
    spec.capabilities = {}
    return spec


@given('a mock service with parent "{parent}" and child "{child}"')
def step_mock_svc_list(context: Any, parent: str, child: str) -> None:
    """Set up a mock service returning two types for list_types."""
    svc = MagicMock()
    svc.list_types.return_value = [
        _mock_type_spec(parent, built_in=True),
        _mock_type_spec(child, inherits=parent),
    ]
    context.cli_mock_service = svc  # type: ignore[attr-defined]


@given('a mock service with type "{name}" inheriting "{parent}"')
def step_mock_svc_show(context: Any, name: str, parent: str) -> None:
    """Set up a mock service returning a child type for show_type."""
    svc = MagicMock()
    svc.show_type.return_value = _mock_type_spec(name, inherits=parent)
    context.cli_mock_service = svc  # type: ignore[attr-defined]


@given('the mock service resolves chain "{chain_csv}"')
def step_mock_svc_chain(context: Any, chain_csv: str) -> None:
    """Configure the mock service to return a specific chain."""
    chain = [t.strip() for t in chain_csv.split(",") if t.strip()]
    context.cli_mock_service.resolve_type_inheritance_chain.return_value = chain  # type: ignore[attr-defined]


@when('I invoke "{cmd}" via CliRunner')
def step_invoke_cli(context: Any, cmd: str) -> None:
    """Invoke a resource CLI command via CliRunner."""
    from typer.testing import CliRunner

    from cleveragents.cli.commands.resource import app

    runner = CliRunner()
    with (
        patch(_PATCH_SVC, return_value=context.cli_mock_service),  # type: ignore[attr-defined]
        patch(_PATCH_CON, _plain_console()),
    ):
        context.cli_result = runner.invoke(app, cmd.split())  # type: ignore[attr-defined]


@then('the CLI output contains "{fragment}"')
def step_cli_output_contains(context: Any, fragment: str) -> None:
    """Assert the CLI output contains the expected text."""
    output = context.cli_result.output  # type: ignore[attr-defined]
    assert fragment in output, f"Expected {fragment!r} in output, got:\n{output}"


@then("the CLI exit code is {code:d}")
def step_cli_exit_code(context: Any, code: int) -> None:
    """Assert the CLI exit code."""
    actual = context.cli_result.exit_code  # type: ignore[attr-defined]
    assert actual == code, f"Expected exit code {code}, got {actual}"
