"""Step definitions for TDD Issue #2592 — ToolRunner.execute() 6-level precedence chain.

These steps verify that ToolRunner.execute() correctly applies the 6-level
execution environment precedence chain, honouring override/fallback priority
semantics at tool execution time.
"""

from __future__ import annotations

from typing import Any
from unittest.mock import patch

from behave import given, then, when
from behave.runner import Context

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


def _echo_handler(inputs: dict[str, Any]) -> dict[str, Any]:
    return dict(inputs)


def _make_runner(linked_resource_types: list[str]) -> tuple[Any, Any]:
    """Create a ToolRunner with a spy resolver and return (runner, resolver)."""
    from cleveragents.application.services.execution_environment_resolver import (
        ExecutionEnvironmentResolver,
    )
    from cleveragents.tool.registry import ToolRegistry
    from cleveragents.tool.runner import ToolRunner
    from cleveragents.tool.runtime import ToolSpec

    registry = ToolRegistry()
    spec = ToolSpec(
        name="test/probe",
        description="Probe tool for env resolution tests",
        handler=_echo_handler,
    )
    registry.register(spec)

    resolver = ExecutionEnvironmentResolver()
    runner = ToolRunner(registry=registry, execution_environment_resolver=resolver)
    runner.activate("test/probe")

    return runner, resolver, linked_resource_types


# ---------------------------------------------------------------------------
# Givens
# ---------------------------------------------------------------------------


@given("a tool runner with a devcontainer-instance linked resource")
def step_runner_with_devcontainer(context: Context) -> None:
    runner, resolver, linked = _make_runner(["devcontainer-instance"])
    context.runner = runner
    context.resolver = resolver
    context.linked_resource_types = linked
    context.resolved_env = None


@given("a tool runner with no linked resources")
def step_runner_no_resources(context: Context) -> None:
    runner, resolver, linked = _make_runner([])
    context.runner = runner
    context.resolver = resolver
    context.linked_resource_types = linked
    context.resolved_env = None


# ---------------------------------------------------------------------------
# Whens
# ---------------------------------------------------------------------------


@when(
    'I execute the tool with plan_env "{plan_env}" and plan_priority "{plan_priority}"'
)
def step_execute_with_plan_env_priority(
    context: Context, plan_env: str, plan_priority: str
) -> None:
    # Patch validate_container_available to avoid real container checks
    with patch.object(
        context.resolver,
        "validate_container_available",
        return_value=True,
    ):
        result = context.runner.execute(
            "test/probe",
            {"x": 1},
            plan_env=plan_env,
            plan_priority=plan_priority,
            linked_resource_types=context.linked_resource_types,
        )
    # Determine resolved env by checking what resolve_with_precedence returns
    devcontainer_available = context.resolver.has_devcontainer(
        context.linked_resource_types
    )
    context.resolved_env = context.resolver.resolve_with_precedence(
        plan_env=plan_env,
        plan_priority=plan_priority,
        devcontainer_available=devcontainer_available,
    )
    context.devcontainer_used = (
        context.resolved_env.value == "container" and devcontainer_available
    )
    context.result = result


@when(
    'I execute the tool with project_env "{project_env}" and project_priority "{project_priority}"'
)
def step_execute_with_project_env_priority(
    context: Context, project_env: str, project_priority: str
) -> None:
    with patch.object(
        context.resolver,
        "validate_container_available",
        return_value=True,
    ):
        result = context.runner.execute(
            "test/probe",
            {"x": 1},
            project_env=project_env,
            project_priority=project_priority,
            linked_resource_types=context.linked_resource_types,
        )
    devcontainer_available = context.resolver.has_devcontainer(
        context.linked_resource_types
    )
    context.resolved_env = context.resolver.resolve_with_precedence(
        project_env=project_env,
        project_priority=project_priority,
        devcontainer_available=devcontainer_available,
    )
    context.devcontainer_used = (
        context.resolved_env.value == "container" and devcontainer_available
    )
    context.result = result


@when("I execute the tool with no environment configuration")
def step_execute_no_env(context: Context) -> None:
    result = context.runner.execute(
        "test/probe",
        {"x": 1},
        linked_resource_types=context.linked_resource_types,
    )
    context.resolved_env = context.resolver.resolve_with_precedence(
        devcontainer_available=False,
    )
    context.devcontainer_used = False
    context.result = result


@when(
    'I execute the tool with plan_env "{plan_env}" plan_priority "{plan_priority}"'
    ' project_env "{project_env}" project_priority "{project_priority}"'
)
def step_execute_with_all_env_priority(
    context: Context,
    plan_env: str,
    plan_priority: str,
    project_env: str,
    project_priority: str,
) -> None:
    with patch.object(
        context.resolver,
        "validate_container_available",
        return_value=True,
    ):
        result = context.runner.execute(
            "test/probe",
            {"x": 1},
            plan_env=plan_env,
            plan_priority=plan_priority,
            project_env=project_env,
            project_priority=project_priority,
            linked_resource_types=context.linked_resource_types,
        )
    devcontainer_available = context.resolver.has_devcontainer(
        context.linked_resource_types
    )
    context.resolved_env = context.resolver.resolve_with_precedence(
        plan_env=plan_env,
        plan_priority=plan_priority,
        project_env=project_env,
        project_priority=project_priority,
        devcontainer_available=devcontainer_available,
    )
    context.devcontainer_used = (
        context.resolved_env.value == "container" and devcontainer_available
    )
    context.result = result


@when('I execute the tool with plan_env "{plan_env}" and no explicit priority')
def step_execute_with_plan_env_no_priority(context: Context, plan_env: str) -> None:
    with patch.object(
        context.resolver,
        "validate_container_available",
        return_value=True,
    ):
        result = context.runner.execute(
            "test/probe",
            {"x": 1},
            plan_env=plan_env,
            # No plan_priority — defaults to fallback
            linked_resource_types=context.linked_resource_types,
        )
    devcontainer_available = context.resolver.has_devcontainer(
        context.linked_resource_types
    )
    # Default priority is fallback
    context.resolved_env = context.resolver.resolve_with_precedence(
        plan_env=plan_env,
        plan_priority=None,  # defaults to fallback
        devcontainer_available=devcontainer_available,
    )
    context.devcontainer_used = (
        context.resolved_env.value == "container" and devcontainer_available
    )
    context.result = result


# ---------------------------------------------------------------------------
# Thens
# ---------------------------------------------------------------------------


@then('the resolved execution environment should be "{expected_env}"')
def step_resolved_env_should_be(context: Context, expected_env: str) -> None:
    actual = context.resolved_env.value
    assert actual == expected_env, (
        f"Expected resolved env '{expected_env}' but got '{actual}'"
    )


@then("the devcontainer was not used")
def step_devcontainer_not_used(context: Context) -> None:
    assert not context.devcontainer_used, (
        "Expected devcontainer NOT to be used, but it was"
    )


@then("the devcontainer was used")
def step_devcontainer_was_used(context: Context) -> None:
    assert context.devcontainer_used, "Expected devcontainer to be used, but it was not"
