"""Step definitions for sandbox manager strategy cast coverage.

Exercises the cast("SandboxStrategyStr", boundary_resource.sandbox_strategy)
line in get_or_create_sandbox_for_resource, which is reached whenever the
boundary resource has a non-None sandbox_strategy.
"""

from __future__ import annotations

from typing import Any
from unittest.mock import patch

from behave import given, then, when

from cleveragents.domain.models.core.resource import (
    PhysVirt,
    Resource,
    ResourceCapabilities,
    SandboxStrategy,
)
from cleveragents.infrastructure.sandbox.factory import SandboxFactory
from cleveragents.infrastructure.sandbox.manager import SandboxManager

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

# Valid ULID strings for test resources
_ULID_CHILD = "01ARZ3NDEKTSV4RRFFQ69G5FAV"
_ULID_BOUNDARY = "01BRZ3NDEKTSV4RRFFQ69G5FAV"

_STRATEGY_MAP: dict[str, SandboxStrategy] = {
    "NONE": SandboxStrategy.NONE,
    "GIT_WORKTREE": SandboxStrategy.GIT_WORKTREE,
    "COPY_ON_WRITE": SandboxStrategy.COPY_ON_WRITE,
    "TRANSACTION_ROLLBACK": SandboxStrategy.TRANSACTION_ROLLBACK,
    "SNAPSHOT": SandboxStrategy.SNAPSHOT,
    "OVERLAY": SandboxStrategy.OVERLAY,
}


def _make_resource(
    resource_id: str = _ULID_CHILD,
    location: str | None = "/tmp/repo",
    sandbox_strategy: SandboxStrategy | None = SandboxStrategy.GIT_WORKTREE,
    sandboxable: bool = True,
    parents: list[str] | None = None,
) -> Resource:
    """Create a Resource domain object for testing."""
    return Resource(
        resource_id=resource_id,
        resource_type_name="git-checkout",
        classification=PhysVirt.PHYSICAL,
        location=location,
        sandbox_strategy=sandbox_strategy,
        capabilities=ResourceCapabilities(sandboxable=sandboxable),
        parents=parents or [],
    )


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


@given("smcast a sandbox factory instance")
def step_smcast_given_factory(context: Any) -> None:
    context.smcast_factory = SandboxFactory()


@given("smcast a sandbox manager with the factory")
def step_smcast_given_manager(context: Any) -> None:
    context.smcast_manager = SandboxManager(
        factory=context.smcast_factory, cleanup_on_exit=False
    )
    context.smcast_error = None
    context.smcast_sandbox = None
    context.smcast_boundary_resource = None
    context.smcast_child_resource = None
    context.smcast_registry = None


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


@given(
    'smcast a boundary resource with strategy "{strategy}" and location "{location}"'
)
def step_smcast_given_boundary_resource(
    context: Any, strategy: str, location: str
) -> None:
    """Set up a boundary resource with the given strategy and location."""
    sandbox_strategy = _STRATEGY_MAP[strategy]

    boundary_resource = _make_resource(
        resource_id=_ULID_BOUNDARY,
        location=location,
        sandbox_strategy=sandbox_strategy,
        sandboxable=True,
    )

    child_resource = _make_resource(
        resource_id=_ULID_CHILD,
        location="/tmp/smcast-child",
        sandbox_strategy=None,
        sandboxable=False,
        parents=[_ULID_BOUNDARY],
    )

    context.smcast_boundary_resource = boundary_resource
    context.smcast_child_resource = child_resource
    context.smcast_registry = {
        _ULID_BOUNDARY: boundary_resource,
        _ULID_CHILD: child_resource,
    }


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


@when('smcast I call get_or_create_sandbox_for_resource for plan "{plan_id}"')
def step_smcast_when_call_for_resource(context: Any, plan_id: str) -> None:
    """Call get_or_create_sandbox_for_resource with the prepared boundary resource.

    The boundary cache is patched to return the boundary resource directly,
    bypassing DAG traversal.
    """
    context.smcast_error = None
    context.smcast_sandbox = None

    patcher = patch.object(
        context.smcast_manager._boundary_cache,
        "get_boundary",
        return_value=context.smcast_boundary_resource,
    )
    patcher.start()
    context.add_cleanup(patcher.stop)

    try:
        context.smcast_sandbox = (
            context.smcast_manager.get_or_create_sandbox_for_resource(
                plan_id=plan_id,
                resource=context.smcast_child_resource,
                resource_registry=context.smcast_registry,
            )
        )
    except Exception as exc:
        context.smcast_error = exc


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


@then("smcast a sandbox is returned without error")
def step_smcast_then_sandbox_returned(context: Any) -> None:
    assert context.smcast_error is None, f"Unexpected error: {context.smcast_error}"
    assert context.smcast_sandbox is not None, (
        "Expected a sandbox to be returned, but got None"
    )
