"""Step definitions for TDD bug #1080 — execution env resolution precedence.

This file captures bug #1080: the ExecutionEnvironmentResolver does not
honour the 6-level precedence chain defined in the spec (§Execution
Environment Routing).  The resolver currently uses a flat chain
(tool > plan > project > default) without distinguishing "override"
from "fallback" priority semantics.

Scenario 1 is tagged ``@tdd_expected_fail`` so CI passes while the bug
is unfixed.  Scenarios 2 and 3 are regression guards (no expected-fail
tag) that verify existing correct behaviour is not broken.

When the fix for #1080 is implemented:

1. Remove the ``@tdd_expected_fail`` tag from Scenario 1 in the feature file.
2. Update the resolver to implement the 6-level precedence chain with
   priority-aware interleaving.
3. All three scenarios should then pass normally.
"""

from __future__ import annotations

from behave import given, then, when  # type: ignore[import-untyped]
from behave.runner import Context  # type: ignore[import-untyped]

from cleveragents.application.services.execution_environment_resolver import (
    ExecutionEnvironmentResolver,
)

# Valid priority values accepted by the step definitions.  Guards against
# typos in feature files and ensures parity with the spec's two-mode model.
_VALID_PRIORITIES: frozenset[str] = frozenset({"override", "fallback"})


@given("a precedence-aware execution environment resolver")
def step_create_precedence_resolver(context: Context) -> None:
    """Create an ExecutionEnvironmentResolver for precedence testing."""
    context.precedence_resolver = ExecutionEnvironmentResolver()
    context.precedence_resolved_env = None


@when(
    'I resolve precedence with plan env "{plan_env}" at priority '
    '"{plan_priority}" and project env "{project_env}" at priority '
    '"{project_priority}"'
)
def step_resolve_precedence_with_priorities(
    context: Context,
    plan_env: str,
    plan_priority: str,
    project_env: str,
    project_priority: str,
) -> None:
    """Resolve execution environment with the 6-level precedence chain.

    Per the spec, the correct precedence is:
        1. Plan-level with priority: override
        2. Project-level with priority: override
        3. Nearest-ancestor devcontainer
        4. Plan-level with priority: fallback
        5. Project-level with priority: fallback
        6. Host (default)

    The current ``resolver.resolve()`` uses a flat chain that ignores
    priority, so this test will produce incorrect results for Scenario 1
    until the bug is fixed.  Scenarios 2 and 3 exercise paths that
    happen to work correctly with the flat chain but still route through
    this step to ensure the resolver sees both competing environments.
    """
    # Validate priority values against the spec's two-mode model.
    assert plan_priority in _VALID_PRIORITIES, (
        f"Invalid plan_priority '{plan_priority}'; "
        f"expected one of {sorted(_VALID_PRIORITIES)}"
    )
    assert project_priority in _VALID_PRIORITIES, (
        f"Invalid project_priority '{project_priority}'; "
        f"expected one of {sorted(_VALID_PRIORITIES)}"
    )

    resolver: ExecutionEnvironmentResolver = context.precedence_resolver

    # TODO(#1080): The 6-level precedence chain is built manually here
    # because the current resolver.resolve() uses a flat priority chain
    # that ignores priority semantics.  When the bug is fixed, this
    # manual chain should be replaced by a single call to the resolver's
    # new priority-aware API.

    # Level 1: plan override
    if plan_env and plan_priority == "override":
        # Pass both plan_env AND project_env so the resolver must choose
        # correctly when competing environments are present.
        context.precedence_resolved_env = resolver.resolve(
            plan_env=plan_env,
            project_env=project_env,
        )
        return

    # Level 2: project override
    if project_env and project_priority == "override":
        # BUG: the current resolver.resolve() does not support this
        # precedence level.  Calling resolve(plan_env=..., project_env=...)
        # always returns plan_env first.  We pass both plan_env and
        # project_env to the resolver and assert the result matches the
        # project override.  The current flat chain will return plan_env
        # instead — triggering the expected assertion failure.
        context.precedence_resolved_env = resolver.resolve(
            plan_env=plan_env,
            project_env=project_env,
        )
        return

    # Level 3: devcontainer (not tested in this scenario)

    # Level 4: plan fallback
    if plan_env and plan_priority == "fallback":
        context.precedence_resolved_env = resolver.resolve(plan_env=plan_env)
        return

    # Level 5: project fallback
    if project_env and project_priority == "fallback":
        context.precedence_resolved_env = resolver.resolve(project_env=project_env)
        return

    # Level 6: host default
    context.precedence_resolved_env = resolver.resolve()


@when(
    'I resolve precedence with no plan env and project env "{project_env}" '
    'at priority "{project_priority}"'
)
def step_resolve_precedence_project_only(
    context: Context,
    project_env: str,
    project_priority: str,
) -> None:
    """Resolve with project-level environment and no plan-level environment.

    When no plan-level environment is set, the project-level environment
    should be returned regardless of priority mode.  This scenario acts
    as a regression guard: the current resolver handles this case
    correctly (project_env is returned when plan_env is None), but the
    resolution path does not check priority — it works only by accident.

    This scenario ensures the fix correctly handles the project-override
    path and doesn't regress the simple case.
    """
    # Validate the priority parameter to catch silent discards.
    assert project_priority in _VALID_PRIORITIES, (
        f"Invalid project_priority '{project_priority}'; "
        f"expected one of {sorted(_VALID_PRIORITIES)}"
    )

    resolver: ExecutionEnvironmentResolver = context.precedence_resolver
    context.precedence_resolved_env = resolver.resolve(project_env=project_env)


@then('the precedence-resolved environment should be "{expected}"')
def step_check_precedence_result(context: Context, expected: str) -> None:
    """Assert the resolved environment matches the expected value.

    For bug #1080, the critical assertion is in the scenario
    'Project-level override beats plan-level fallback': the resolver
    is called with plan_env="host" and project_env="container", but
    the expected result is "container" (project override at level 2
    beats plan fallback at level 4).  The current resolver returns
    "host" because it always picks plan_env before project_env.
    """
    assert context.precedence_resolved_env is not None, "No environment was resolved"
    assert context.precedence_resolved_env.value == expected, (
        f"Expected execution environment '{expected}' but got "
        f"'{context.precedence_resolved_env.value}'. "
        f"Bug #1080: resolver does not honour 6-level precedence chain."
    )
