"""Step definitions for tdd_plan_correct_auto_resolve.feature.

Captures bug #1025: the ``plan correct`` CLI command's
``_resolve_active_plan_id()`` fails to find plans in isolated E2E
environments (where ``CLEVERAGENTS_HOME`` is a temp directory) even
when a plan in ``Execute/COMPLETE`` state exists in the database.

The test simulates this by providing divergent container instances:
the main ``correct_decision`` function gets a container that has
plans, but the ``_resolve_active_plan_id`` path (via
``_get_lifecycle_service``) gets a container whose
``list_plans()`` returns nothing — mimicking the condition where
a fresh ``CLEVERAGENTS_HOME`` resolves to an empty database.

The assertions verify the *expected* (correct) behaviour: the
command should auto-resolve the plan and succeed.  They will
**fail** on the current codebase, proving the bug exists.  The
``@tdd_expected_fail`` tag inverts the result so CI passes.

All step text uses the ``tpcar`` prefix to avoid collisions with
other step files.

Bug: https://git.cleverthis.com/cleveragents/cleveragents-core/issues/1025
TDD: https://git.cleverthis.com/cleveragents/cleveragents-core/issues/1035
"""

from __future__ import annotations

from unittest.mock import patch

from behave import given, then, when
from behave.runner import Context
from typer.testing import CliRunner

from cleveragents.cli.commands.plan import app as plan_app
from features.mocks.tdd_plan_correct_auto_resolve_fixtures import (
    DECISION_ID,
    PATCH_CONTAINER,
    PATCH_CORRECTION_SVC,
    build_cli_args,
    make_container_side_effects,
    make_correction_svc,
)

runner = CliRunner()


# ---------------------------------------------------------------------------
# GIVEN steps
# ---------------------------------------------------------------------------


@given(
    "tpcar a container with a plan in Execute/COMPLETE"
    " and an isolated auto-resolve path"
)
def step_tpcar_container_with_isolated_resolve(context: Context) -> None:
    """Set up divergent containers to simulate the isolated-env bug.

    The first ``get_container()`` call (from ``correct_decision``)
    returns a full container with a plan in ``Execute/COMPLETE``.
    The second call (from ``_resolve_active_plan_id`` via
    ``_get_lifecycle_service``) returns an isolated container whose
    ``list_plans()`` returns no plans — reproducing the E2E failure.
    """
    context.tpcar_container_side_effects = make_container_side_effects()


@given("tpcar a CorrectionService that succeeds for the target decision in revert mode")
def step_tpcar_correction_svc_revert(context: Context) -> None:
    """Set up mock CorrectionService for revert mode."""
    context.tpcar_correction_svc = make_correction_svc(DECISION_ID, mode="revert")


@given("tpcar a CorrectionService that succeeds for the target decision in append mode")
def step_tpcar_correction_svc_append(context: Context) -> None:
    """Set up mock CorrectionService for append mode."""
    context.tpcar_correction_svc = make_correction_svc(DECISION_ID, mode="append")


# ---------------------------------------------------------------------------
# WHEN steps
# ---------------------------------------------------------------------------


@when(
    "tpcar I invoke plan correct with a decision_id and no --plan flag in revert mode"
)
def step_tpcar_invoke_revert(context: Context) -> None:
    """Invoke ``plan correct <decision_id> --mode revert`` without --plan.

    The ``--plan`` flag is intentionally omitted so the code must call
    ``_resolve_active_plan_id()`` to find the active plan.  The
    divergent container side-effects simulate the isolated-env
    condition where auto-resolve fails.
    """
    args = build_cli_args(DECISION_ID, mode="revert")

    with (
        patch(
            PATCH_CORRECTION_SVC,
            return_value=context.tpcar_correction_svc,
        ),
        patch(
            PATCH_CONTAINER,
            side_effect=context.tpcar_container_side_effects,
        ),
    ):
        context.tpcar_result = runner.invoke(plan_app, args)


@when(
    "tpcar I invoke plan correct with a decision_id and no --plan flag in append mode"
)
def step_tpcar_invoke_append(context: Context) -> None:
    """Invoke ``plan correct <decision_id> --mode append`` without --plan.

    Same auto-resolve path as revert mode — the divergent container
    side-effects exercise the same ``_resolve_active_plan_id`` bug.
    """
    args = build_cli_args(DECISION_ID, mode="append")

    with (
        patch(
            PATCH_CORRECTION_SVC,
            return_value=context.tpcar_correction_svc,
        ),
        patch(
            PATCH_CONTAINER,
            side_effect=context.tpcar_container_side_effects,
        ),
    ):
        context.tpcar_result = runner.invoke(plan_app, args)


# ---------------------------------------------------------------------------
# THEN steps
# ---------------------------------------------------------------------------


@then("tpcar the command should exit successfully")
def step_tpcar_exit_ok(context: Context) -> None:
    """Assert plan correct exits with code 0.

    This assertion will FAIL on the current codebase because the
    divergent containers cause ``_resolve_active_plan_id`` to see
    an empty plan list and raise ``typer.Abort`` — proving bug #1025.
    """
    result = context.tpcar_result
    assert result.exit_code == 0, (
        f"Bug #1025: plan correct exited with code {result.exit_code} "
        f"instead of 0 when --plan was omitted.  The auto-resolve "
        f"path failed to find the active plan in the isolated "
        f"environment.  Output: {result.output}"
    )


@then("tpcar the correction should have used the auto-resolved plan")
def step_tpcar_correction_used_resolved_plan(context: Context) -> None:
    """Assert request_correction was called (auto-resolve succeeded).

    When auto-resolve works correctly, the correction service receives
    the plan_id from the auto-resolved plan and the decision_id from
    the positional argument.
    """
    svc = context.tpcar_correction_svc
    svc.request_correction.assert_called_once()
    call_record = svc.request_correction.call_args
    kw = call_record.kwargs

    actual_target = kw.get("target_decision_id")
    assert actual_target == DECISION_ID, (
        f"Bug #1025: request_correction was called with "
        f"target_decision_id={actual_target!r} but expected "
        f"{DECISION_ID!r}."
    )
