"""Step definitions for container_handler.feature.

Tests for the container infrastructure resource handler module
(cleveragents.resource.handlers.container).
"""

from __future__ import annotations

from typing import Any
from unittest.mock import MagicMock

from behave import given, then, when  # type: ignore[attr-defined]

from cleveragents.domain.models.core.resource import (
    PhysVirt,
    Resource,
    ResourceCapabilities,
)
from cleveragents.resource.handlers._base import EMPTY_CONTENT_HASH
from cleveragents.resource.handlers.protocol import ResourceHandler

# ── Handler class registry ───────────────────────────────────


def _get_handler_class(class_name: str) -> type:
    """Import and return a handler class from the container module."""
    from cleveragents.resource.handlers import container as _container_mod

    cls = getattr(_container_mod, class_name, None)
    if cls is None:
        raise AssertionError(
            f"Class '{class_name}' not found in "
            "cleveragents.resource.handlers.container"
        )
    return cls


def _make_container_resource(
    resource_type: str,
    location: str | None = None,
) -> Resource:
    """Create a minimal container resource for testing."""
    return Resource(
        resource_id="01KJ5C5TPMP8GGX3QC83E2MAQS",
        resource_type_name=resource_type,
        classification=PhysVirt.PHYSICAL,
        location=location,
        properties={},
        capabilities=ResourceCapabilities(
            readable=True,
            writable=False,
            sandboxable=False,
            checkpointable=False,
        ),
    )


# ── When steps ───────────────────────────────────────────────


@when("I import the container handler module")
def step_import_container_module(context: Any) -> None:
    """Attempt to import the container handler module."""
    try:
        import cleveragents.resource.handlers.container as _mod

        context.import_error = None  # type: ignore[attr-defined]
        context.container_module = _mod  # type: ignore[attr-defined]
    except Exception as exc:
        context.import_error = exc  # type: ignore[attr-defined]


@when('I import "{class_name}" from the container handler module')
def step_import_class_from_container_module(context: Any, class_name: str) -> None:
    """Attempt to import a specific class from the container handler module."""
    try:
        cls = _get_handler_class(class_name)
        context.imported_class = cls  # type: ignore[attr-defined]
        context.import_error = None  # type: ignore[attr-defined]
    except Exception as exc:
        context.import_error = exc  # type: ignore[attr-defined]
        context.imported_class = None  # type: ignore[attr-defined]


@when('I resolve the handler "<handler_ref>"')
def step_resolve_handler_ref_template(context: Any, handler_ref: str) -> None:
    """Resolve a handler by reference string (template step)."""
    step_resolve_handler_ref(context, handler_ref)


@when('I resolve the handler "{handler_ref}"')
def step_resolve_handler_ref(context: Any, handler_ref: str) -> None:
    """Resolve a handler by reference string."""
    from cleveragents.resource.handlers.resolver import (
        HandlerResolutionError,
        clear_handler_cache,
        resolve_handler,
    )

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


@when('I resolve the handler for resource type "{resource_type}"')
def step_resolve_handler_for_type(context: Any, resource_type: str) -> None:
    """Resolve a handler for a resource type via the registry."""
    from cleveragents.application.services._resource_registry_data import BUILTIN_TYPES
    from cleveragents.resource.handlers.resolver import (
        HandlerResolutionError,
        clear_handler_cache,
        resolve_handler,
    )

    clear_handler_cache()
    handler_ref = None
    for type_def in BUILTIN_TYPES:
        if type_def["name"] == resource_type:
            handler_ref = type_def.get("handler")
            break

    if handler_ref is None:
        context.resolution_error = AssertionError(  # type: ignore[attr-defined]
            f"Resource type '{resource_type}' not found in BUILTIN_TYPES"
        )
        context.resolved_handler = None  # type: ignore[attr-defined]
        return

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


@when("I call resolve on the container handler")
def step_call_resolve(context: Any) -> None:
    """Call resolve() on the handler and capture any exception."""
    mock_sandbox = MagicMock()
    try:
        context.handler.resolve(  # type: ignore[attr-defined]
            resource=context.container_resource,  # type: ignore[attr-defined]
            plan_id="test-plan-01",
            slot_name="test-slot",
            sandbox_manager=mock_sandbox,
            access="read_only",
        )
        context.raised_exception = None  # type: ignore[attr-defined]
    except Exception as exc:
        context.raised_exception = exc  # type: ignore[attr-defined]


@when("I call read on the container handler")
def step_call_read(context: Any) -> None:
    """Call read() on the handler and capture any exception."""
    try:
        context.handler.read(  # type: ignore[attr-defined]
            resource=context.container_resource,  # type: ignore[attr-defined]
        )
        context.raised_exception = None  # type: ignore[attr-defined]
    except Exception as exc:
        context.raised_exception = exc  # type: ignore[attr-defined]


@when('I call write on the container handler with data b"test"')
def step_call_write(context: Any) -> None:
    """Call write() on the handler and capture any exception."""
    try:
        context.handler.write(  # type: ignore[attr-defined]
            resource=context.container_resource,  # type: ignore[attr-defined]
            path="",
            data=b"test",
        )
        context.raised_exception = None  # type: ignore[attr-defined]
    except Exception as exc:
        context.raised_exception = exc  # type: ignore[attr-defined]


@when("I call delete on the container handler")
def step_call_delete(context: Any) -> None:
    """Call delete() on the handler and capture any exception."""
    try:
        context.handler.delete(  # type: ignore[attr-defined]
            resource=context.container_resource,  # type: ignore[attr-defined]
        )
        context.raised_exception = None  # type: ignore[attr-defined]
    except Exception as exc:
        context.raised_exception = exc  # type: ignore[attr-defined]


@when("I call list_children on the container handler")
def step_call_list_children(context: Any) -> None:
    """Call list_children() on the handler and capture any exception."""
    try:
        context.handler.list_children(  # type: ignore[attr-defined]
            resource=context.container_resource,  # type: ignore[attr-defined]
        )
        context.raised_exception = None  # type: ignore[attr-defined]
    except Exception as exc:
        context.raised_exception = exc  # type: ignore[attr-defined]


@when('I call diff on the container handler with other_location "/other"')
def step_call_diff(context: Any) -> None:
    """Call diff() on the handler and capture any exception."""
    try:
        context.handler.diff(  # type: ignore[attr-defined]
            resource=context.container_resource,  # type: ignore[attr-defined]
            other_location="/other",
        )
        context.raised_exception = None  # type: ignore[attr-defined]
    except Exception as exc:
        context.raised_exception = exc  # type: ignore[attr-defined]


@when("I call discover_children on the container handler")
def step_call_discover_children(context: Any) -> None:
    """Call discover_children() on the handler and capture any exception."""
    try:
        context.handler.discover_children(  # type: ignore[attr-defined]
            resource=context.container_resource,  # type: ignore[attr-defined]
        )
        context.raised_exception = None  # type: ignore[attr-defined]
    except Exception as exc:
        context.raised_exception = exc  # type: ignore[attr-defined]


@when("I call create_checkpoint on the container handler")
def step_call_create_checkpoint(context: Any) -> None:
    """Call create_checkpoint() on the handler and capture any exception."""
    mock_sandbox = MagicMock()
    try:
        context.handler.create_checkpoint(  # type: ignore[attr-defined]
            resource=context.container_resource,  # type: ignore[attr-defined]
            plan_id="test-plan-01",
            sandbox_manager=mock_sandbox,
        )
        context.raised_exception = None  # type: ignore[attr-defined]
    except Exception as exc:
        context.raised_exception = exc  # type: ignore[attr-defined]


@when("I call rollback_to on the container handler")
def step_call_rollback_to(context: Any) -> None:
    """Call rollback_to() on the handler and capture any exception."""
    mock_sandbox = MagicMock()
    try:
        context.handler.rollback_to(  # type: ignore[attr-defined]
            resource=context.container_resource,  # type: ignore[attr-defined]
            plan_id="test-plan-01",
            checkpoint_id="ckpt-001",
            sandbox_manager=mock_sandbox,
        )
        context.raised_exception = None  # type: ignore[attr-defined]
    except Exception as exc:
        context.raised_exception = exc  # type: ignore[attr-defined]


@when("I call content_hash on the container handler")
def step_call_content_hash(context: Any) -> None:
    """Call content_hash() on the handler and capture the result."""
    try:
        context.content_hash_result = context.handler.content_hash(  # type: ignore[attr-defined]
            context.container_resource  # type: ignore[attr-defined]
        )
        context.raised_exception = None  # type: ignore[attr-defined]
    except Exception as exc:
        context.raised_exception = exc  # type: ignore[attr-defined]
        context.content_hash_result = None  # type: ignore[attr-defined]


# ── Given steps ──────────────────────────────────────────────


@given('a "{class_name}" instance')
def step_handler_instance(context: Any, class_name: str) -> None:
    """Create a handler instance by class name."""
    cls = _get_handler_class(class_name)
    context.handler = cls()  # type: ignore[attr-defined]
    context.handler_class_name = class_name  # type: ignore[attr-defined]


@given('a container resource of type "{resource_type}" with location "{location}"')
def step_container_resource_with_location(
    context: Any, resource_type: str, location: str
) -> None:
    """Create a container resource with a specific location."""
    context.container_resource = _make_container_resource(  # type: ignore[attr-defined]
        resource_type=resource_type,
        location=location,
    )


@given('a container resource of type "{resource_type}" with no location')
def step_container_resource_no_location(context: Any, resource_type: str) -> None:
    """Create a container resource with no location."""
    context.container_resource = _make_container_resource(  # type: ignore[attr-defined]
        resource_type=resource_type,
        location=None,
    )


# ── Then steps ───────────────────────────────────────────────


@then("the container handler module import should succeed without errors")
def step_import_succeeded(context: Any) -> None:
    """Assert that the import succeeded."""
    assert context.import_error is None, (  # type: ignore[attr-defined]
        f"Import failed with: {context.import_error}"  # type: ignore[attr-defined]
    )


@then("the class should be importable")
def step_class_importable(context: Any) -> None:
    """Assert that the class was imported successfully."""
    assert context.import_error is None, (  # type: ignore[attr-defined]
        f"Class import failed with: {context.import_error}"  # type: ignore[attr-defined]
    )
    assert context.imported_class is not None  # type: ignore[attr-defined]


@then("the instance should satisfy the ResourceHandler protocol")
def step_satisfies_protocol(context: Any) -> None:
    """Assert that the handler instance satisfies ResourceHandler protocol."""
    assert isinstance(context.handler, ResourceHandler), (  # type: ignore[attr-defined]
        f"{context.handler_class_name} does not satisfy ResourceHandler protocol"  # type: ignore[attr-defined]
    )


@then("the handler should resolve without HandlerResolutionError")
def step_handler_resolved(context: Any) -> None:
    """Assert that the handler resolved without error."""
    assert context.resolution_error is None, (  # type: ignore[attr-defined]
        f"Handler resolution failed: {context.resolution_error}"  # type: ignore[attr-defined]
    )
    assert context.resolved_handler is not None  # type: ignore[attr-defined]


@then('the handler type_label should be "{expected_label}"')
def step_handler_type_label(context: Any, expected_label: str) -> None:
    """Assert the handler's _type_label class variable."""
    actual = getattr(context.handler, "_type_label", None)  # type: ignore[attr-defined]
    assert actual == expected_label, (
        f"Expected _type_label='{expected_label}', got '{actual}'"
    )


@then("a container handler NotImplementedError should be raised")
def step_not_implemented_raised(context: Any) -> None:
    """Assert that a NotImplementedError was raised."""
    exc = context.raised_exception  # type: ignore[attr-defined]
    assert exc is not None, "Expected NotImplementedError but no exception was raised"
    assert isinstance(exc, NotImplementedError), (
        f"Expected NotImplementedError but got {type(exc).__name__}: {exc}"
    )


@then("the content_hash result should be the EMPTY_CONTENT_HASH sentinel")
def step_content_hash_is_empty(context: Any) -> None:
    """Assert that content_hash returned the EMPTY_CONTENT_HASH sentinel."""
    result = context.content_hash_result  # type: ignore[attr-defined]
    assert result == EMPTY_CONTENT_HASH, (
        f"Expected EMPTY_CONTENT_HASH but got: {result!r}"
    )


@then("the content_hash result should be a non-empty hex string")
def step_content_hash_is_hex(context: Any) -> None:
    """Assert that content_hash returned a non-empty hex string."""
    result = context.content_hash_result  # type: ignore[attr-defined]
    assert result is not None, "content_hash returned None"
    assert isinstance(result, str), f"Expected str, got {type(result)}"
    assert len(result) > 0, "content_hash returned empty string"
    assert result != EMPTY_CONTENT_HASH, (
        "content_hash returned EMPTY_CONTENT_HASH but expected identity hash"
    )
    # Verify it's a valid hex string
    try:
        int(result, 16)
    except ValueError as exc:
        raise AssertionError(
            f"content_hash result is not a valid hex string: {result!r}"
        ) from exc
