"""Step definitions for TDD Issue #9131 — invariant_enforced decisions not
propagated to child plans on subplan spawn.

These steps exercise ``SubplanService.spawn()`` and verify that it propagates
``invariant_enforced`` decisions from the parent plan to each child plan's
decision tree.

On ``master`` (before the fix), ``spawn()`` creates child Plan objects but
does NOT record ``invariant_enforced`` decisions on them.  The assertions in
these steps will **fail** until the bug is fixed.
"""

from __future__ import annotations

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

from cleveragents.application.services.decision_service import DecisionService
from cleveragents.application.services.subplan_service import (
    SpawnEntry,
    SpawnResult,
    SubplanService,
)
from cleveragents.domain.models.core.decision import (
    ContextSnapshot,
    Decision,
    DecisionType,
)
from cleveragents.domain.models.core.plan import (
    ExecutionMode,
    NamespacedName,
    Plan,
    PlanIdentity,
    SubplanConfig,
)

_PARENT_PLAN_ID: str = "01HGZ6FE0AQDYTR4BXVQZ6PN00"
_ROOT_PLAN_ID: str = "01HGZ6FE0AQDYTR4BXVQZ6RF00"
_SPAWN_DEC_ID: str = "01HGZ6FE0AQDYTR4BXVQZ6DA00"


def _make_parent_plan() -> Plan:
    """Create a minimal parent plan for testing."""
    return Plan(
        identity=PlanIdentity(
            plan_id=_PARENT_PLAN_ID,
            root_plan_id=_ROOT_PLAN_ID,
        ),
        namespaced_name=NamespacedName(namespace="local", name="parent-plan"),
        description="Parent plan for invariant propagation test",
        action_name="local/parent-action",
    )


def _make_spawn_decision(
    plan_id: str = _PARENT_PLAN_ID,
) -> Decision:
    """Create a subplan_spawn Decision."""
    return Decision(
        decision_id=_SPAWN_DEC_ID,
        plan_id=plan_id,
        decision_type=DecisionType.SUBPLAN_SPAWN,
        sequence_number=10,
        question="Should we spawn a child plan?",
        chosen_option="local/sub-action",
        context_snapshot=ContextSnapshot(),
    )


def _make_spawn_entry(plan_id: str = _PARENT_PLAN_ID) -> SpawnEntry:
    """Create a single spawn entry."""
    return SpawnEntry(
        decision=_make_spawn_decision(plan_id),
        action_name="local/sub-action",
        description="Child plan for invariant propagation test",
    )


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


@given("a parent plan with invariant_enforced decisions recorded")
def step_parent_with_invariant_decisions(context: Context) -> None:
    """Set up a parent plan with one invariant_enforced decision."""
    context.parent_plan = _make_parent_plan()
    context.decision_service = DecisionService()

    # Record one invariant_enforced decision on the parent plan
    inv_dec = context.decision_service.record_decision(
        plan_id=_PARENT_PLAN_ID,
        decision_type=DecisionType.INVARIANT_ENFORCED,
        question="Is this invariant enforced?",
        chosen_option="invariant enforced: do not modify production data",
    )
    context.parent_invariant_decisions = [inv_dec]
    context.expected_invariant_count = 1

    context.subplan_service = SubplanService(
        decision_service=context.decision_service,
    )
    context.subplan_config = SubplanConfig(
        execution_mode=ExecutionMode.SEQUENTIAL,
    )


@given("a parent plan with {count:d} invariant_enforced decisions recorded")
def step_parent_with_n_invariant_decisions(context: Context, count: int) -> None:
    """Set up a parent plan with N invariant_enforced decisions."""
    context.parent_plan = _make_parent_plan()
    context.decision_service = DecisionService()

    inv_decisions: list[Decision] = []
    for i in range(count):
        inv_dec = context.decision_service.record_decision(
            plan_id=_PARENT_PLAN_ID,
            decision_type=DecisionType.INVARIANT_ENFORCED,
            question=f"Is invariant {i} enforced?",
            chosen_option=f"invariant enforced: constraint {i}",
        )
        inv_decisions.append(inv_dec)

    context.parent_invariant_decisions = inv_decisions
    context.expected_invariant_count = count

    context.subplan_service = SubplanService(
        decision_service=context.decision_service,
    )
    context.subplan_config = SubplanConfig(
        execution_mode=ExecutionMode.SEQUENTIAL,
    )


@given("a parent plan with a non_overridable invariant_enforced decision")
def step_parent_with_non_overridable_invariant(context: Context) -> None:
    """Set up a parent plan with a non_overridable invariant_enforced decision."""
    context.parent_plan = _make_parent_plan()
    context.decision_service = DecisionService()

    # Record a non_overridable invariant decision
    inv_dec = context.decision_service.record_decision(
        plan_id=_PARENT_PLAN_ID,
        decision_type=DecisionType.INVARIANT_ENFORCED,
        question="Is this non-overridable global invariant enforced?",
        chosen_option="non_overridable invariant enforced: never delete user data",
        rationale="Global non-overridable invariant must propagate to all child plans",
    )
    context.parent_invariant_decisions = [inv_dec]
    context.non_overridable_chosen_option = inv_dec.chosen_option
    context.expected_invariant_count = 1

    context.subplan_service = SubplanService(
        decision_service=context.decision_service,
    )
    context.subplan_config = SubplanConfig(
        execution_mode=ExecutionMode.SEQUENTIAL,
    )


@given("a parent plan with no invariant_enforced decisions")
def step_parent_with_no_invariant_decisions(context: Context) -> None:
    """Set up a parent plan with no invariant_enforced decisions."""
    context.parent_plan = _make_parent_plan()
    context.decision_service = DecisionService()
    context.parent_invariant_decisions = []
    context.expected_invariant_count = 0

    context.subplan_service = SubplanService(
        decision_service=context.decision_service,
    )
    context.subplan_config = SubplanConfig(
        execution_mode=ExecutionMode.SEQUENTIAL,
    )


@given("a valid spawn entry for the parent plan")
def step_valid_spawn_entry(context: Context) -> None:
    """Create a single valid spawn entry."""
    context.spawn_entries = [_make_spawn_entry()]


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


@when("I spawn a child plan via SubplanService")
def step_spawn_child_plan(context: Context) -> None:
    """Call SubplanService.spawn() and capture the result."""
    context.spawn_error = None
    try:
        context.spawn_result = context.subplan_service.spawn(
            parent_plan=context.parent_plan,
            config=context.subplan_config,
            spawn_entries=context.spawn_entries,
        )
    except Exception as exc:
        context.spawn_error = exc
        context.spawn_result = None


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


@then(
    "the decision service should have recorded invariant_enforced decisions"
    " for the child plan"
)
def step_child_has_invariant_decisions(context: Context) -> None:
    """Assert that invariant_enforced decisions were recorded for the child plan.

    Bug #9131: spawn() does not propagate invariant_enforced decisions to
    child plans.  This assertion will fail until the bug is fixed.
    """
    assert context.spawn_error is None, (
        f"Spawn raised an unexpected error: {context.spawn_error}"
    )
    result: SpawnResult = context.spawn_result
    assert result is not None, "Spawn result is None"
    assert len(result.child_plans) > 0, "No child plans were created"

    child_plan: Plan = result.child_plans[0]
    child_plan_id: str = child_plan.identity.plan_id

    # Query the decision service for invariant_enforced decisions on the child plan
    child_invariant_decisions: list[Decision] = context.decision_service.list_by_type(
        child_plan_id,
        DecisionType.INVARIANT_ENFORCED,
    )

    assert len(child_invariant_decisions) > 0, (
        f"Child plan {child_plan_id!r} has no invariant_enforced decisions. "
        f"Bug #9131: SubplanService.spawn() does not propagate "
        f"invariant_enforced decisions from parent plan to child plans."
    )


@then("the child plan should have {count:d} invariant_enforced decisions recorded")
def step_child_has_n_invariant_decisions(context: Context, count: int) -> None:
    """Assert that the child plan has exactly N invariant_enforced decisions.

    Bug #9131: spawn() does not propagate invariant_enforced decisions.
    """
    assert context.spawn_error is None, (
        f"Spawn raised an unexpected error: {context.spawn_error}"
    )
    result: SpawnResult = context.spawn_result
    assert result is not None, "Spawn result is None"
    assert len(result.child_plans) > 0, "No child plans were created"

    child_plan: Plan = result.child_plans[0]
    child_plan_id: str = child_plan.identity.plan_id

    child_invariant_decisions: list[Decision] = context.decision_service.list_by_type(
        child_plan_id,
        DecisionType.INVARIANT_ENFORCED,
    )

    assert len(child_invariant_decisions) == count, (
        f"Child plan {child_plan_id!r} has {len(child_invariant_decisions)} "
        f"invariant_enforced decisions, expected {count}. "
        f"Bug #9131: SubplanService.spawn() does not propagate "
        f"invariant_enforced decisions from parent plan to child plans."
    )


@then("the child plan should have the non_overridable invariant decision propagated")
def step_child_has_non_overridable_invariant(context: Context) -> None:
    """Assert that the non_overridable invariant decision was propagated.

    Bug #9131: spawn() does not propagate invariant_enforced decisions.
    """
    assert context.spawn_error is None, (
        f"Spawn raised an unexpected error: {context.spawn_error}"
    )
    result: SpawnResult = context.spawn_result
    assert result is not None, "Spawn result is None"
    assert len(result.child_plans) > 0, "No child plans were created"

    child_plan: Plan = result.child_plans[0]
    child_plan_id: str = child_plan.identity.plan_id

    child_invariant_decisions: list[Decision] = context.decision_service.list_by_type(
        child_plan_id,
        DecisionType.INVARIANT_ENFORCED,
    )

    assert len(child_invariant_decisions) > 0, (
        f"Child plan {child_plan_id!r} has no invariant_enforced decisions. "
        f"Bug #9131: non_overridable invariant was not propagated."
    )

    # Verify the non_overridable invariant text was propagated
    expected_option: str = context.non_overridable_chosen_option
    propagated_options: list[str] = [d.chosen_option for d in child_invariant_decisions]
    assert expected_option in propagated_options, (
        f"Non-overridable invariant chosen_option {expected_option!r} not found "
        f"in child plan decisions: {propagated_options}. "
        f"Bug #9131: non_overridable invariant was not propagated."
    )


@then("the spawn should succeed with no invariant decisions recorded for the child")
def step_spawn_succeeds_no_invariants(context: Context) -> None:
    """Assert that spawn succeeds cleanly when parent has no invariant decisions."""
    assert context.spawn_error is None, (
        f"Spawn raised an unexpected error: {context.spawn_error}"
    )
    result: SpawnResult = context.spawn_result
    assert result is not None, "Spawn result is None"
    assert result.total_spawned == 1, (
        f"Expected 1 spawned child, got {result.total_spawned}"
    )

    # When parent has no invariant decisions, child should also have none
    assert len(result.child_plans) > 0, "No child plans were created"
    child_plan: Plan = result.child_plans[0]
    child_plan_id: str = child_plan.identity.plan_id

    child_invariant_decisions: list[Decision] = context.decision_service.list_by_type(
        child_plan_id,
        DecisionType.INVARIANT_ENFORCED,
    )

    assert len(child_invariant_decisions) == 0, (
        f"Child plan {child_plan_id!r} has {len(child_invariant_decisions)} "
        f"invariant_enforced decisions, expected 0 (parent had none)."
    )
