"""Step definitions for Plan Resume feature tests."""

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

from cleveragents.application.services.plan_lifecycle_service import (
    PlanLifecycleService,
)
from cleveragents.application.services.plan_resume_service import (
    PlanResumeService,
)
from cleveragents.config.settings import Settings
from cleveragents.core.exceptions import PlanError, ValidationError
from cleveragents.domain.models.core.plan import (
    PlanPhase,
    ProcessingState,
    ProjectLink,
)
from cleveragents.domain.models.core.resume import ResumeMetadata

# --- Background ---


@given("I have a plan lifecycle service for resume tests")
def step_create_resume_lifecycle_service(context: Context) -> None:
    """Create lifecycle service for resume tests."""
    settings = Settings()
    context.lifecycle_service = PlanLifecycleService(settings=settings)
    context.error = None
    context.plan = None
    context.plan_id = None
    context.eligibility = None
    context.summary = None


@given("I have a plan resume service")
def step_create_resume_service(context: Context) -> None:
    """Create plan resume service."""
    context.resume_service = PlanResumeService(
        lifecycle_service=context.lifecycle_service,
    )


# --- Helper to create plans in specific states ---


def _create_action(context: Context) -> str:
    """Create and return an action name for testing."""
    action_name = f"local/resume-test-{ULID()!s}"[:30].lower()
    context.lifecycle_service.create_action(
        name=action_name,
        description="Resume test action",
        definition_of_done="Step 1\nStep 2\nStep 3",
        strategy_actor="local/stub-strategy",
        execution_actor="local/stub-execute",
    )
    return action_name


def _create_plan_in_state(
    context: Context,
    phase: PlanPhase,
    state: ProcessingState,
) -> str:
    """Create a plan and move it to the given phase/state."""
    action_name = _create_action(context)
    plan = context.lifecycle_service.use_action(
        action_name=action_name,
        project_links=[ProjectLink(project_name="local/test-proj")],
    )
    plan_id = plan.identity.plan_id

    if phase == PlanPhase.STRATEGIZE and state == ProcessingState.QUEUED:
        pass
    elif phase == PlanPhase.STRATEGIZE and state == ProcessingState.PROCESSING:
        context.lifecycle_service.start_strategize(plan_id)
    elif phase == PlanPhase.EXECUTE and state == ProcessingState.PROCESSING:
        context.lifecycle_service.start_strategize(plan_id)
        p = context.lifecycle_service.get_plan(plan_id)
        p.decision_root_id = str(ULID())
        context.lifecycle_service._commit_plan(p)
        context.lifecycle_service.complete_strategize(plan_id)
        context.lifecycle_service.execute_plan(plan_id)
        context.lifecycle_service.start_execute(plan_id)
    elif phase == PlanPhase.EXECUTE and state == ProcessingState.ERRORED:
        context.lifecycle_service.start_strategize(plan_id)
        p = context.lifecycle_service.get_plan(plan_id)
        p.decision_root_id = str(ULID())
        context.lifecycle_service._commit_plan(p)
        context.lifecycle_service.complete_strategize(plan_id)
        context.lifecycle_service.execute_plan(plan_id)
        context.lifecycle_service.start_execute(plan_id)
        context.lifecycle_service.fail_execute(plan_id, "Test error")
    elif phase == PlanPhase.APPLY and state == ProcessingState.APPLIED:
        context.lifecycle_service.start_strategize(plan_id)
        p = context.lifecycle_service.get_plan(plan_id)
        p.decision_root_id = str(ULID())
        context.lifecycle_service._commit_plan(p)
        context.lifecycle_service.complete_strategize(plan_id)
        context.lifecycle_service.execute_plan(plan_id)
        context.lifecycle_service.start_execute(plan_id)
        context.lifecycle_service.complete_execute(plan_id)
        context.lifecycle_service.apply_plan(plan_id)
        context.lifecycle_service.start_apply(plan_id)
        context.lifecycle_service.complete_apply(plan_id)
    elif phase == PlanPhase.APPLY and state == ProcessingState.CONSTRAINED:
        context.lifecycle_service.start_strategize(plan_id)
        p = context.lifecycle_service.get_plan(plan_id)
        p.decision_root_id = str(ULID())
        context.lifecycle_service._commit_plan(p)
        context.lifecycle_service.complete_strategize(plan_id)
        context.lifecycle_service.execute_plan(plan_id)
        context.lifecycle_service.start_execute(plan_id)
        context.lifecycle_service.complete_execute(plan_id)
        context.lifecycle_service.apply_plan(plan_id)
        context.lifecycle_service.start_apply(plan_id)
        p2 = context.lifecycle_service.get_plan(plan_id)
        p2.processing_state = ProcessingState.PROCESSING
        context.lifecycle_service._commit_plan(p2)
        context.lifecycle_service.constrain_apply(plan_id, "constraints violated")

    context.plan_id = plan_id
    context.plan = context.lifecycle_service.get_plan(plan_id)
    return plan_id


# --- Given steps ---


@given("I have a resume-test plan in execute phase with errored state")
def step_resume_plan_execute_errored(context: Context) -> None:
    """Create a plan in execute/errored state for resume tests."""
    _create_plan_in_state(context, PlanPhase.EXECUTE, ProcessingState.ERRORED)


@given("I have a resume-test plan in execute phase with processing state")
def step_resume_plan_execute_processing(context: Context) -> None:
    """Create a plan in execute/processing state for resume tests."""
    _create_plan_in_state(context, PlanPhase.EXECUTE, ProcessingState.PROCESSING)


@given("I have a resume-test plan in strategize phase with queued state")
def step_resume_plan_strategize_queued(context: Context) -> None:
    """Create a plan in strategize/queued state for resume tests."""
    _create_plan_in_state(context, PlanPhase.STRATEGIZE, ProcessingState.QUEUED)


@given("I have a resume-test plan in applied terminal state")
def step_resume_plan_applied(context: Context) -> None:
    """Create a plan in applied terminal state for resume tests."""
    _create_plan_in_state(context, PlanPhase.APPLY, ProcessingState.APPLIED)


@given("I have a resume-test plan in cancelled terminal state")
def step_resume_plan_cancelled(context: Context) -> None:
    """Create a plan and cancel it for resume tests."""
    _create_plan_in_state(context, PlanPhase.EXECUTE, ProcessingState.PROCESSING)
    context.lifecycle_service.cancel_plan(context.plan_id, "test cancel")
    context.plan = context.lifecycle_service.get_plan(context.plan_id)


@given("I have a resume-test plan in constrained terminal state")
def step_resume_plan_constrained(context: Context) -> None:
    """Create a plan in constrained terminal state for resume tests."""
    _create_plan_in_state(context, PlanPhase.APPLY, ProcessingState.CONSTRAINED)


@given("I have a resume-test plan in action phase")
def step_resume_plan_action_phase(context: Context) -> None:
    """Create a plan and set it to action phase for resume tests."""
    _create_plan_in_state(context, PlanPhase.STRATEGIZE, ProcessingState.QUEUED)
    plan = context.lifecycle_service.get_plan(context.plan_id)
    plan.phase = PlanPhase.ACTION
    context.lifecycle_service._commit_plan(plan)
    context.plan = plan


@given('the resume-test plan has checkpoint at step {index:d} with decision "{dec_id}"')
def step_resume_plan_has_checkpoint(context: Context, index: int, dec_id: str) -> None:
    """Add a checkpoint to the plan at given step."""
    context.resume_service.set_total_steps(context.plan_id, 5)
    context.resume_service.record_step_checkpoint(
        plan_id=context.plan_id,
        step_index=index,
        decision_id=dec_id,
        step_text=f"Step {index}",
    )


@given("I have resume metadata with last completed step {step:d}")
def step_resume_metadata_with_step(context: Context, step: int) -> None:
    """Create resume metadata with given step."""
    context.resume_metadata = ResumeMetadata(
        last_completed_step=step,
        total_steps=5,
    )


@given("I have fresh resume metadata")
def step_fresh_resume_metadata(context: Context) -> None:
    """Create fresh resume metadata with no progress."""
    context.resume_metadata = ResumeMetadata()


@given("I have resume metadata with {total:d} total steps and last step {last:d}")
def step_resume_metadata_with_totals(context: Context, total: int, last: int) -> None:
    """Create resume metadata with total and last step."""
    context.resume_metadata = ResumeMetadata(
        last_completed_step=last,
        total_steps=total,
    )


# --- When steps ---


@when("I validate resume eligibility")
def step_validate_resume_eligibility(context: Context) -> None:
    """Validate resume eligibility for the current plan."""
    context.eligibility = context.resume_service.validate_eligibility(
        context.plan_id,
    )


@when('I record a resume step checkpoint at index {index:d} with decision "{dec_id}"')
def step_record_resume_checkpoint(context: Context, index: int, dec_id: str) -> None:
    """Record a step checkpoint."""
    context.resume_service.set_total_steps(context.plan_id, 5)
    context.resume_service.record_step_checkpoint(
        plan_id=context.plan_id,
        step_index=index,
        decision_id=dec_id,
        step_text=f"Step {index}",
    )
    context.plan = context.lifecycle_service.get_plan(context.plan_id)


@when("I resume the plan with dry-run")
def step_resume_dry_run(context: Context) -> None:
    """Resume the plan in dry-run mode."""
    context.summary = context.resume_service.resume_plan(context.plan_id, dry_run=True)


@when("I resume the plan without dry-run")
def step_resume_live(context: Context) -> None:
    """Resume the plan in live mode."""
    context.summary = context.resume_service.resume_plan(context.plan_id, dry_run=False)
    context.plan = context.lifecycle_service.get_plan(context.plan_id)


@when("I record a graceful shutdown for resume")
def step_record_resume_shutdown(context: Context) -> None:
    """Record a graceful shutdown."""
    context.resume_service.record_shutdown(context.plan_id)


@when("I try to resume a plan with empty ID")
def step_resume_empty_id(context: Context) -> None:
    """Try to resume a plan with empty ID."""
    try:
        context.resume_service.resume_plan("")
    except ValidationError as e:
        context.error = e


@when("I try to validate eligibility with empty plan ID")
def step_validate_eligibility_empty_id(context: Context) -> None:
    """Try to validate eligibility with empty plan ID."""
    try:
        context.resume_service.validate_eligibility("")
    except ValidationError as e:
        context.error = e


@when("I try to resume the terminal plan for resume test")
def step_resume_terminal_plan(context: Context) -> None:
    """Try to resume a terminal plan."""
    try:
        context.resume_service.resume_plan(context.plan_id)
    except PlanError as e:
        context.error = e


@when("I set the resume total steps to {total:d}")
def step_set_resume_total_steps(context: Context, total: int) -> None:
    """Set total steps for the plan."""
    context.resume_service.set_total_steps(context.plan_id, total)


@when("I try to set resume total steps with empty plan ID")
def step_set_resume_total_steps_empty(context: Context) -> None:
    """Try to set total steps with empty plan ID."""
    try:
        context.resume_service.set_total_steps("", 5)
    except ValidationError as e:
        context.error = e


# --- Then steps ---


@then("the plan should be eligible for resume")
def step_assert_eligible(context: Context) -> None:
    """Assert the plan is eligible for resume."""
    assert context.eligibility is not None
    assert context.eligibility.eligible is True, (
        f"Expected eligible=True, got: {context.eligibility.reason}"
    )


@then("the plan should not be eligible for resume")
def step_assert_not_eligible(context: Context) -> None:
    """Assert the plan is not eligible for resume."""
    assert context.eligibility is not None
    assert context.eligibility.eligible is False


@then('the ineligible reason should be "{reason}"')
def step_assert_ineligible_reason(context: Context, reason: str) -> None:
    """Assert the ineligible reason code."""
    assert context.eligibility is not None
    assert context.eligibility.ineligible_reason is not None
    assert context.eligibility.ineligible_reason.value == reason


@then("the resume-test plan should have last completed step {step:d}")
def step_assert_resume_last_step(context: Context, step: int) -> None:
    """Assert the plan's last completed step."""
    plan = context.lifecycle_service.get_plan(context.plan_id)
    assert plan.last_completed_step == step


@then("the resume-test plan should have a checkpoint ID set")
def step_assert_resume_checkpoint_id(context: Context) -> None:
    """Assert the plan has a checkpoint ID."""
    plan = context.lifecycle_service.get_plan(context.plan_id)
    assert plan.last_checkpoint_id is not None


@then("the resume metadata should have {count:d} checkpoints")
def step_assert_resume_checkpoint_count(context: Context, count: int) -> None:
    """Assert the resume metadata checkpoint count."""
    metadata = context.resume_service.get_resume_metadata(context.plan_id)
    assert len(metadata.checkpoints) == count


@then("the resume metadata next step should be {step:d}")
def step_assert_resume_next_step(context: Context, step: int) -> None:
    """Assert the resume metadata next step index."""
    metadata = context.resume_service.get_resume_metadata(context.plan_id)
    assert metadata.next_step_index == step


@then("I should get a resume summary")
def step_assert_resume_summary_exists(context: Context) -> None:
    """Assert a resume summary was returned."""
    assert context.summary is not None


@then("the resume summary next step should be {step:d}")
def step_assert_resume_summary_next_step(context: Context, step: int) -> None:
    """Assert the resume summary next step."""
    assert context.summary is not None
    assert context.summary.next_step_index == step


@then("the resume-test plan processing state should still be errored")
def step_assert_resume_still_errored(context: Context) -> None:
    """Assert the plan is still in errored state."""
    plan = context.lifecycle_service.get_plan(context.plan_id)
    assert plan.processing_state == ProcessingState.ERRORED


@then('the resume summary decision ID should be "{dec_id}"')
def step_assert_resume_summary_decision(context: Context, dec_id: str) -> None:
    """Assert the resume summary decision ID."""
    assert context.summary is not None
    assert context.summary.decision_id == dec_id


@then("the resume-test plan processing state should be processing")
def step_assert_resume_processing(context: Context) -> None:
    """Assert the plan is in processing state."""
    plan = context.lifecycle_service.get_plan(context.plan_id)
    assert plan.processing_state == ProcessingState.PROCESSING


@then("the resume-test plan error message should be cleared")
def step_assert_resume_error_cleared(context: Context) -> None:
    """Assert the plan error message is None."""
    plan = context.lifecycle_service.get_plan(context.plan_id)
    assert plan.error_message is None


@then("the resume metadata should be marked as interrupted")
def step_assert_resume_interrupted(context: Context) -> None:
    """Assert the resume metadata is marked interrupted."""
    metadata = context.resume_service.get_resume_metadata(context.plan_id)
    assert metadata.interrupted is True
    assert metadata.interrupted_at is not None


@then("a resume validation error should be raised")
def step_assert_resume_validation_error(context: Context) -> None:
    """Assert a ValidationError was raised."""
    assert context.error is not None
    assert isinstance(context.error, ValidationError)


@then("a plan error should be raised for resume")
def step_assert_resume_plan_error(context: Context) -> None:
    """Assert a PlanError was raised."""
    assert context.error is not None
    assert isinstance(context.error, PlanError)


@then("the resume metadata total steps should be {total:d}")
def step_assert_resume_total_steps(context: Context, total: int) -> None:
    """Assert the resume metadata total steps."""
    metadata = context.resume_service.get_resume_metadata(context.plan_id)
    assert metadata.total_steps == total


@then("the resume metadata has_progress should be true")
def step_assert_resume_has_progress_true(context: Context) -> None:
    """Assert has_progress is True."""
    assert context.resume_metadata.has_progress is True


@then("the resume metadata has_progress should be false")
def step_assert_resume_has_progress_false(context: Context) -> None:
    """Assert has_progress is False."""
    assert context.resume_metadata.has_progress is False


@then("the resume metadata is_complete should be true")
def step_assert_resume_is_complete_true(context: Context) -> None:
    """Assert is_complete is True."""
    assert context.resume_metadata.is_complete is True


@then("the resume metadata is_complete should be false")
def step_assert_resume_is_complete_false(context: Context) -> None:
    """Assert is_complete is False."""
    assert context.resume_metadata.is_complete is False
