"""Additional step definitions for cross_plan_correction.feature coverage.

Covers validation edge cases, append-mode flows, protocol isinstance
checks, and model validator error paths that the primary step file
does not exercise.
"""

from __future__ import annotations

from behave import then, when
from pydantic import ValidationError as PydanticValidationError

from cleveragents.application.services.cross_plan_correction_service import (
    ChildPlanCanceller,
    ChildPlanLookup,
    SandboxRollbacker,
)
from cleveragents.core.exceptions import ValidationError
from cleveragents.domain.models.core.correction import (
    CorrectionImpact,
    CorrectionMode,
    CorrectionResult,
)

# -------------------------------------------------------------------
# Validation — empty / whitespace correction_id for execute_cascade
# -------------------------------------------------------------------


@when("I try to execute a cascade with empty correction_id")
def step_execute_cascade_empty_cid(context: object) -> None:
    try:
        context.service.execute_cascade("", [])  # type: ignore[attr-defined]
        context.error = None  # type: ignore[attr-defined]
    except ValidationError as exc:
        context.error = exc  # type: ignore[attr-defined]


@when("I try to execute a cascade with whitespace-only correction_id")
def step_execute_cascade_whitespace_cid(context: object) -> None:
    try:
        context.service.execute_cascade("   ", [])  # type: ignore[attr-defined]
        context.error = None  # type: ignore[attr-defined]
    except ValidationError as exc:
        context.error = exc  # type: ignore[attr-defined]


# -------------------------------------------------------------------
# Validation — empty / whitespace correction_id for
# execute_correction_with_cascade
# -------------------------------------------------------------------


@when("I try to execute a correction with cascade with empty correction_id")
def step_execute_correction_cascade_empty_cid(context: object) -> None:
    impact = CorrectionImpact(
        affected_decisions=[],
        affected_child_plans=[],
        risk_level="low",
        rollback_tier="full",
    )
    try:
        context.service.execute_correction_with_cascade(  # type: ignore[attr-defined]
            "", impact, CorrectionMode.REVERT
        )
        context.error = None  # type: ignore[attr-defined]
    except ValidationError as exc:
        context.error = exc  # type: ignore[attr-defined]


@when("I try to execute a correction with cascade with whitespace-only correction_id")
def step_execute_correction_cascade_whitespace_cid(context: object) -> None:
    impact = CorrectionImpact(
        affected_decisions=[],
        affected_child_plans=[],
        risk_level="low",
        rollback_tier="full",
    )
    try:
        context.service.execute_correction_with_cascade(  # type: ignore[attr-defined]
            "   ", impact, CorrectionMode.REVERT
        )
        context.error = None  # type: ignore[attr-defined]
    except ValidationError as exc:
        context.error = exc  # type: ignore[attr-defined]


# -------------------------------------------------------------------
# Append-mode steps
# -------------------------------------------------------------------


@when(
    'I execute a correction with cascade for correction "{cid}" '
    "with no child plans in append mode"
)
def step_execute_correction_cascade_no_children_append(
    context: object, cid: str
) -> None:
    impact = CorrectionImpact(
        affected_decisions=["D1", "D2"],
        affected_child_plans=[],
        risk_level="low",
        rollback_tier="full",
        artifacts_to_archive=["D1.artifact", "D2.artifact"],
    )
    context.correction_with_cascade_result = (  # type: ignore[attr-defined]
        context.service.execute_correction_with_cascade(  # type: ignore[attr-defined]
            cid, impact, CorrectionMode.APPEND
        )
    )


@when(
    'I execute a correction with cascade for correction "{cid}" '
    'with child plan "{plan_id}" in append mode'
)
def step_execute_correction_cascade_with_child_append(
    context: object, cid: str, plan_id: str
) -> None:
    impact = CorrectionImpact(
        affected_decisions=["D1"],
        affected_child_plans=[plan_id],
        risk_level="low",
        rollback_tier="full",
        artifacts_to_archive=["D1.artifact"],
    )
    context.correction_with_cascade_result = (  # type: ignore[attr-defined]
        context.service.execute_correction_with_cascade(  # type: ignore[attr-defined]
            cid, impact, CorrectionMode.APPEND
        )
    )


# -------------------------------------------------------------------
# Result assertion steps
# -------------------------------------------------------------------


@then("the correction result reverted decisions should be empty")
def step_correction_result_reverted_empty(context: object) -> None:
    result = context.correction_with_cascade_result  # type: ignore[attr-defined]
    assert isinstance(result, CorrectionResult), (
        f"Expected CorrectionResult, got {type(result)}"
    )
    assert len(result.reverted_decisions) == 0, (
        f"Expected empty reverted_decisions, got {result.reverted_decisions}"
    )


@then("the correction result archived artifacts should be empty")
def step_correction_result_archived_empty(context: object) -> None:
    result = context.correction_with_cascade_result  # type: ignore[attr-defined]
    assert isinstance(result, CorrectionResult), (
        f"Expected CorrectionResult, got {type(result)}"
    )
    assert len(result.archived_artifacts) == 0, (
        f"Expected empty archived_artifacts, got {result.archived_artifacts}"
    )


# -------------------------------------------------------------------
# Model validation steps
# -------------------------------------------------------------------


@when('I try to create a CorrectionImpact with rollback_tier "{tier}"')
def step_create_bad_rollback_tier(context: object, tier: str) -> None:
    try:
        CorrectionImpact(rollback_tier=tier)
        context.error = None  # type: ignore[attr-defined]
    except PydanticValidationError as exc:
        context.error = exc  # type: ignore[attr-defined]


@when('I try to create a CorrectionImpact with risk_level "{level}"')
def step_create_bad_risk_level(context: object, level: str) -> None:
    try:
        CorrectionImpact(risk_level=level)
        context.error = None  # type: ignore[attr-defined]
    except PydanticValidationError as exc:
        context.error = exc  # type: ignore[attr-defined]


# -------------------------------------------------------------------
# Protocol isinstance checks
# -------------------------------------------------------------------


@then("the plan lookup should satisfy the ChildPlanLookup protocol")
def step_lookup_protocol(context: object) -> None:
    assert isinstance(context.lookup, ChildPlanLookup), (  # type: ignore[attr-defined]
        "Mock does not satisfy ChildPlanLookup protocol"
    )


@then("the plan canceller should satisfy the ChildPlanCanceller protocol")
def step_canceller_protocol(context: object) -> None:
    assert isinstance(context.canceller, ChildPlanCanceller), (  # type: ignore[attr-defined]
        "Mock does not satisfy ChildPlanCanceller protocol"
    )


@then("the sandbox rollbacker should satisfy the SandboxRollbacker protocol")
def step_rollbacker_protocol(context: object) -> None:
    assert isinstance(context.rollbacker, SandboxRollbacker), (  # type: ignore[attr-defined]
        "Mock does not satisfy SandboxRollbacker protocol"
    )
