"""Step definitions for phase reversion state machine feature."""

from __future__ import annotations

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

from cleveragents.application.services.plan_lifecycle_service import (
    InvalidPhaseTransitionError,
    PlanLifecycleService,
)
from cleveragents.config.settings import Settings
from cleveragents.core.exceptions import PlanError
from cleveragents.domain.models.core.action import ActionArgument
from cleveragents.domain.models.core.plan import (
    AutomationProfileProvenance,
    AutomationProfileRef,
    InvariantSource,
    PlanInvariant,
    PlanPhase,
    ProcessingState,
    ProjectLink,
)


@given("a plan lifecycle service is initialized")
def step_init_service(context: Context) -> None:
    """Initialize a PlanLifecycleService for testing."""
    settings = Settings()
    context.service = PlanLifecycleService(settings=settings)


@given('an action "{action_name}" exists')
def step_create_action(context: Context, action_name: str) -> None:
    """Create a test action."""
    service: PlanLifecycleService = context.service
    service.create_action(
        name=action_name,
        description=f"Test action {action_name}",
        definition_of_done="Tests pass",
        strategy_actor="local/test-strategist",
        execution_actor="local/test-executor",
    )
    context.action_name = action_name


@given('a plan is created from action "{action_name}"')
def step_create_plan(context: Context, action_name: str) -> None:
    """Use an action to create a plan."""
    service: PlanLifecycleService = context.service
    plan = service.use_action(action_name)
    context.plan = plan
    context.plan_id = plan.identity.plan_id


@given('a plan is created from action "{action_name}" with arguments')
def step_create_plan_with_args(context: Context, action_name: str) -> None:
    """Use an action to create a plan with arguments and project links."""
    service: PlanLifecycleService = context.service
    # Re-create the action with an argument definition
    service._actions.pop(str(action_name), None)
    service.create_action(
        name=action_name,
        description=f"Test action {action_name}",
        definition_of_done="Tests pass",
        strategy_actor="local/test-strategist",
        execution_actor="local/test-executor",
        arguments=[
            ActionArgument(name="target_coverage", arg_type="integer"),
        ],
    )
    plan = service.use_action(
        action_name,
        project_links=[ProjectLink(project_name="local/test-project")],
        arguments={"target_coverage": 80},
        invariants=[
            PlanInvariant(text="No regressions", source=InvariantSource.PLAN),
        ],
    )
    context.plan = plan
    context.plan_id = plan.identity.plan_id


@given("the plan is advanced to Apply phase with constrained state")
def step_advance_to_apply_constrained(context: Context) -> None:
    """Advance the plan through Strategize and Execute to Apply/constrained."""
    service: PlanLifecycleService = context.service
    plan_id: str = context.plan_id

    service.start_strategize(plan_id)
    service.complete_strategize(plan_id)
    plan = service.get_plan(plan_id)
    # If auto-progressed past strategize, still need to handle execute
    if plan.phase == PlanPhase.EXECUTE:
        service.start_execute(plan_id)
        service.complete_execute(plan_id)
        plan = service.get_plan(plan_id)
    elif plan.phase == PlanPhase.STRATEGIZE:
        service.execute_plan(plan_id)
        service.start_execute(plan_id)
        service.complete_execute(plan_id)
        plan = service.get_plan(plan_id)

    if plan.phase == PlanPhase.EXECUTE:
        service.apply_plan(plan_id)
    if plan.phase != PlanPhase.APPLY:
        # Force phase for testing
        plan = service.get_plan(plan_id)
        plan.phase = PlanPhase.APPLY
        plan.processing_state = ProcessingState.CONSTRAINED

    plan = service.get_plan(plan_id)
    if plan.processing_state != ProcessingState.CONSTRAINED:
        service.start_apply(plan_id)
        plan = service.get_plan(plan_id)
        plan.processing_state = ProcessingState.CONSTRAINED
        plan.error_message = "Constraints too restrictive"

    context.plan = service.get_plan(plan_id)


@given('the plan has automation profile "{profile_name}" allowing auto-reversion')
def step_set_profile_allowing(context: Context, profile_name: str) -> None:
    """Set a profile that allows auto-reversion."""
    plan = context.service.get_plan(context.plan_id)
    plan.automation_profile = AutomationProfileRef(
        profile_name=profile_name,
        provenance=AutomationProfileProvenance.PLAN,
    )
    context.plan = plan


@given('the plan has automation profile "{profile_name}" blocking auto-reversion')
def step_set_profile_blocking(context: Context, profile_name: str) -> None:
    """Set a profile that blocks auto-reversion."""
    plan = context.service.get_plan(context.plan_id)
    plan.automation_profile = AutomationProfileRef(
        profile_name=profile_name,
        provenance=AutomationProfileProvenance.PLAN,
    )
    context.plan = plan


@given("the plan is advanced to Execute phase")
def step_advance_to_execute(context: Context) -> None:
    """Advance the plan through Strategize to Execute phase."""
    service: PlanLifecycleService = context.service
    plan_id: str = context.plan_id

    # Set manual profile to prevent auto-progress
    plan = service.get_plan(plan_id)
    plan.automation_profile = AutomationProfileRef(
        profile_name="manual",
        provenance=AutomationProfileProvenance.PLAN,
    )

    service.start_strategize(plan_id)
    service.complete_strategize(plan_id)
    service.execute_plan(plan_id)
    context.plan = service.get_plan(plan_id)


@given("the plan has been reverted 3 times")
def step_set_reversion_count(context: Context) -> None:
    """Set the reversion count to the maximum."""
    plan = context.service.get_plan(context.plan_id)
    plan.reversion_count = 3
    # Advance to execute so reversion is structurally valid
    plan.automation_profile = AutomationProfileRef(
        profile_name="manual",
        provenance=AutomationProfileProvenance.PLAN,
    )
    service: PlanLifecycleService = context.service
    plan_id = context.plan_id
    if (
        plan.phase == PlanPhase.STRATEGIZE
        and plan.processing_state == ProcessingState.QUEUED
    ):
        service.start_strategize(plan_id)
        service.complete_strategize(plan_id)
        service.execute_plan(plan_id)
    plan = service.get_plan(plan_id)
    plan.reversion_count = 3
    context.plan = plan


@given("the plan has reached applied terminal state")
def step_set_applied(context: Context) -> None:
    """Move the plan to the applied terminal state."""
    service: PlanLifecycleService = context.service
    plan_id: str = context.plan_id

    plan = service.get_plan(plan_id)
    plan.automation_profile = AutomationProfileRef(
        profile_name="manual",
        provenance=AutomationProfileProvenance.PLAN,
    )

    service.start_strategize(plan_id)
    service.complete_strategize(plan_id)
    service.execute_plan(plan_id)
    service.start_execute(plan_id)
    service.complete_execute(plan_id)
    service.apply_plan(plan_id)
    service.start_apply(plan_id)
    service.complete_apply(plan_id)
    context.plan = service.get_plan(plan_id)


@given("the plan is in Strategize phase")
def step_plan_in_strategize(context: Context) -> None:
    """Verify plan is in Strategize phase."""
    plan = context.service.get_plan(context.plan_id)
    assert plan.phase == PlanPhase.STRATEGIZE


@given("the plan has been cancelled")
def step_cancel_plan(context: Context) -> None:
    """Cancel the plan."""
    service: PlanLifecycleService = context.service
    service.cancel_plan(context.plan_id, reason="test cancellation")
    context.plan = service.get_plan(context.plan_id)


@when('auto-reversion from apply is attempted with reason "{reason}"')
def step_try_auto_revert_apply(context: Context, reason: str) -> None:
    """Try auto-reversion from apply."""
    service: PlanLifecycleService = context.service
    context.plan = service.try_auto_revert_from_apply(context.plan_id, reason)


@when('the plan is manually reverted to Strategize with reason "{reason}"')
def step_manual_revert(context: Context, reason: str) -> None:
    """Manually revert the plan to Strategize."""
    service: PlanLifecycleService = context.service
    context.plan = service.revert_plan(
        context.plan_id, PlanPhase.STRATEGIZE, reason=reason
    )


@when("a reversion is attempted on the maxed-out plan")
def step_attempt_reversion_maxed(context: Context) -> None:
    """Attempt reversion on a plan that has hit the max."""
    service: PlanLifecycleService = context.service
    try:
        context.plan = service.revert_plan(
            context.plan_id, PlanPhase.STRATEGIZE, reason="should fail"
        )
        context.raised_error = None
    except PlanError as e:
        context.raised_error = e


@when('auto-reversion from execute is attempted with reason "{reason}"')
def step_try_auto_revert_execute(context: Context, reason: str) -> None:
    """Try auto-reversion from execute."""
    service: PlanLifecycleService = context.service
    context.plan = service.try_auto_revert_from_execute(context.plan_id, reason)


@when("a reversion is attempted on the terminal plan")
def step_attempt_reversion_terminal(context: Context) -> None:
    """Attempt reversion on a terminal plan."""
    service: PlanLifecycleService = context.service
    try:
        context.plan = service.revert_plan(
            context.plan_id, PlanPhase.STRATEGIZE, reason="should fail"
        )
        context.raised_error = None
    except PlanError as e:
        context.raised_error = e


@when("a reversion to Apply phase is attempted")
def step_attempt_reversion_wrong_phase(context: Context) -> None:
    """Attempt reversion to an invalid target phase."""
    service: PlanLifecycleService = context.service
    try:
        context.plan = service.revert_plan(
            context.plan_id, PlanPhase.APPLY, reason="should fail"
        )
        context.raised_error = None
    except InvalidPhaseTransitionError as e:
        context.raised_error = e


@when("the plan is re-advanced to Execute phase")
def step_re_advance_to_execute(context: Context) -> None:
    """Re-advance the plan to Execute after reversion."""
    service: PlanLifecycleService = context.service
    plan_id: str = context.plan_id

    service.start_strategize(plan_id)
    service.complete_strategize(plan_id)
    service.execute_plan(plan_id)
    context.plan = service.get_plan(plan_id)


@when("a reversion is attempted on the cancelled plan")
def step_attempt_reversion_cancelled(context: Context) -> None:
    """Attempt reversion on a cancelled plan."""
    service: PlanLifecycleService = context.service
    try:
        context.plan = service.revert_plan(
            context.plan_id, PlanPhase.STRATEGIZE, reason="should fail"
        )
        context.raised_error = None
    except PlanError as e:
        context.raised_error = e


@then("the plan should be in Strategize phase")
def step_check_strategize(context: Context) -> None:
    """Verify plan is in Strategize phase."""
    assert context.plan.phase == PlanPhase.STRATEGIZE


@then("the plan processing state should be queued")
def step_check_queued(context: Context) -> None:
    """Verify plan processing state is QUEUED."""
    assert context.plan.processing_state == ProcessingState.QUEUED


@then("the plan reversion count should be {count:d}")
def step_check_reversion_count(context: Context, count: int) -> None:
    """Verify plan reversion count."""
    assert context.plan.reversion_count == count


@then(
    'a reversion decision should be recorded with source "{source}" and target "{target}"'
)
def step_check_reversion_decision(context: Context, source: str, target: str) -> None:
    """Verify a reversion decision was recorded."""
    decisions = context.plan.decisions
    reversion_decisions = [d for d in decisions if d["type"] == "reversion"]
    assert len(reversion_decisions) > 0
    latest = reversion_decisions[-1]
    assert latest["source_phase"] == source
    assert latest["target_phase"] == target
    assert "timestamp" in latest


@then('a reversion decision should be recorded with reason "{reason}"')
def step_check_reversion_reason(context: Context, reason: str) -> None:
    """Verify a reversion decision with specific reason was recorded."""
    decisions = context.plan.decisions
    reversion_decisions = [d for d in decisions if d["type"] == "reversion"]
    assert len(reversion_decisions) > 0
    latest = reversion_decisions[-1]
    assert reason in latest["reason"]


@then("a PlanError should be raised about max reversions")
def step_check_max_reversion_error(context: Context) -> None:
    """Verify a PlanError was raised about max reversions."""
    assert context.raised_error is not None
    assert isinstance(context.raised_error, PlanError)
    assert (
        "maximum reversion count" in str(context.raised_error).lower()
        or "max" in str(context.raised_error).lower()
    )


@then("the plan should remain in Apply phase")
def step_check_still_apply(context: Context) -> None:
    """Verify plan is still in Apply phase."""
    assert context.plan.phase == PlanPhase.APPLY


@then("the plan processing state should be constrained")
def step_check_constrained(context: Context) -> None:
    """Verify plan processing state is CONSTRAINED."""
    assert context.plan.processing_state == ProcessingState.CONSTRAINED


@then("a PlanError should be raised about terminal state")
def step_check_terminal_error(context: Context) -> None:
    """Verify a PlanError was raised about terminal state."""
    assert context.raised_error is not None
    assert isinstance(context.raised_error, PlanError)
    assert "terminal" in str(context.raised_error).lower()


@then("an InvalidPhaseTransitionError should be raised")
def step_check_invalid_transition_error(context: Context) -> None:
    """Verify an InvalidPhaseTransitionError was raised."""
    assert context.raised_error is not None
    assert isinstance(context.raised_error, InvalidPhaseTransitionError)


@then("the plan arguments should be preserved")
def step_check_arguments_preserved(context: Context) -> None:
    """Verify plan arguments are preserved after reversion."""
    assert context.plan.arguments == {"target_coverage": 80}


@then("the plan project links should be preserved")
def step_check_project_links_preserved(context: Context) -> None:
    """Verify plan project links are preserved after reversion."""
    assert len(context.plan.project_links) == 1
    assert context.plan.project_links[0].project_name == "local/test-project"


@then("the plan invariants should be preserved")
def step_check_invariants_preserved(context: Context) -> None:
    """Verify plan invariants are preserved after reversion."""
    assert len(context.plan.invariants) >= 1
    texts = [inv.text for inv in context.plan.invariants]
    assert "No regressions" in texts


@then("there should be {count:d} reversion decisions recorded")
def step_check_decision_count(context: Context, count: int) -> None:
    """Verify the number of reversion decisions."""
    decisions = context.plan.decisions
    reversion_decisions = [d for d in decisions if d["type"] == "reversion"]
    assert len(reversion_decisions) == count


@then("the plan in Strategize phase cannot revert to Apply")
def step_check_no_revert_strat_to_apply(context: Context) -> None:
    """Verify Strategize cannot revert to Apply."""
    plan = context.service.get_plan(context.plan_id)
    plan.phase = PlanPhase.STRATEGIZE
    assert not plan.can_revert_to(PlanPhase.APPLY)


@then("the plan in Strategize phase cannot revert to Action")
def step_check_no_revert_strat_to_action(context: Context) -> None:
    """Verify Strategize cannot revert to Action."""
    plan = context.service.get_plan(context.plan_id)
    plan.phase = PlanPhase.STRATEGIZE
    assert not plan.can_revert_to(PlanPhase.ACTION)


@then("the plan in Execute phase can revert to Strategize")
def step_check_revert_exec_to_strat(context: Context) -> None:
    """Verify Execute can revert to Strategize."""
    plan = context.service.get_plan(context.plan_id)
    plan.phase = PlanPhase.EXECUTE
    assert plan.can_revert_to(PlanPhase.STRATEGIZE)


@then("the plan in Apply phase can revert to Strategize")
def step_check_revert_apply_to_strat(context: Context) -> None:
    """Verify Apply can revert to Strategize."""
    plan = context.service.get_plan(context.plan_id)
    plan.phase = PlanPhase.APPLY
    plan.processing_state = ProcessingState.CONSTRAINED
    assert plan.can_revert_to(PlanPhase.STRATEGIZE)


@then("the plan should remain in Execute phase")
def step_check_still_execute(context: Context) -> None:
    """Verify plan is still in Execute phase."""
    assert context.plan.phase == PlanPhase.EXECUTE
