"""Step definitions for Definition-of-Done gating in the Apply phase.

Tests that ``PlanLifecycleService.apply_plan`` evaluates DoD criteria
before transitioning to the Apply phase and blocks the transition when
any required criteria fail.
"""

from __future__ import annotations

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

from cleveragents.application.services.plan_lifecycle_service import (
    DoDGatingError,
    PlanLifecycleService,
)
from cleveragents.config.settings import Settings
from cleveragents.domain.models.core.plan import (
    PlanPhase,
    ProjectLink,
)

# ---------------------------------------------------------------------------
# Background / setup
# ---------------------------------------------------------------------------


@given("I have a plan lifecycle service for DoD gating")
def step_create_dod_lifecycle_service(context: Context) -> None:
    """Create a fresh PlanLifecycleService for DoD gating tests."""
    settings = Settings()
    context.dod_service = PlanLifecycleService(settings=settings)
    context.dod_plan = None
    context.dod_error = None


# ---------------------------------------------------------------------------
# Plan creation helpers
# ---------------------------------------------------------------------------


def _create_plan_in_execute_complete(
    context: Context,
    override_dod: str | None = None,
    override_arguments: dict[str, str] | None = None,
) -> None:
    """Create an action + plan and drive it to Execute/COMPLETE state.

    The action is always created with a valid DoD text.  After plan
    creation, the plan's ``definition_of_done`` and ``arguments`` fields
    are overridden directly so tests can exercise arbitrary DoD text and
    argument combinations without needing to declare them on the action.
    """
    service: PlanLifecycleService = context.dod_service

    action = service.create_action(
        name="local/dod-test-action",
        description="DoD gating test action",
        definition_of_done="placeholder definition of done",
        strategy_actor="openai/gpt-4",
        execution_actor="openai/gpt-4",
    )

    plan = service.use_action(
        action_name=str(action.namespaced_name),
        project_links=[ProjectLink(project_name="test-project")],
    )

    # Override DoD and arguments directly on the plan object so tests
    # can exercise arbitrary combinations without action-level validation.
    if override_dod is not None:
        plan.definition_of_done = override_dod
    if override_arguments is not None:
        plan.arguments = override_arguments

    # Drive to Execute/COMPLETE
    service.start_strategize(plan.identity.plan_id)
    service.complete_strategize(plan.identity.plan_id)
    service.execute_plan(plan.identity.plan_id)
    service.start_execute(plan.identity.plan_id)
    service.complete_execute(plan.identity.plan_id)

    context.dod_plan = service.get_plan(plan.identity.plan_id)


@given("I have a plan with no definition of done in execute complete state")
def step_plan_no_dod(context: Context) -> None:
    """Create a plan with definition_of_done cleared to empty string."""
    _create_plan_in_execute_complete(context, override_dod="")


@given("I have a plan with empty definition of done in execute complete state")
def step_plan_empty_dod(context: Context) -> None:
    """Create a plan with definition_of_done set to whitespace only."""
    _create_plan_in_execute_complete(context, override_dod="   ")


@given(
    'I have a plan with definition of done "{dod_text}" and argument "{arg_key}" equal to "{arg_value}"'
)
def step_plan_with_dod_and_arg(
    context: Context, dod_text: str, arg_key: str, arg_value: str
) -> None:
    """Create a plan with a specific DoD text and a single argument."""
    _create_plan_in_execute_complete(
        context,
        override_dod=dod_text,
        override_arguments={arg_key: arg_value},
    )


@given('I have a plan with definition of done "{dod_text}" and no arguments')
def step_plan_with_dod_no_args(context: Context, dod_text: str) -> None:
    """Create a plan with a specific DoD text and no arguments."""
    # Replace literal \n with actual newlines for multi-line DoD
    actual_dod = dod_text.replace("\\n", "\n")
    _create_plan_in_execute_complete(
        context,
        override_dod=actual_dod,
        override_arguments={},
    )


# ---------------------------------------------------------------------------
# Apply actions
# ---------------------------------------------------------------------------


@when("I apply the plan with DoD gating")
def step_apply_plan_dod(context: Context) -> None:
    """Apply the plan (expecting success)."""
    service: PlanLifecycleService = context.dod_service
    plan_id = context.dod_plan.identity.plan_id
    context.dod_error = None
    context.dod_plan = service.apply_plan(plan_id)


@when("I try to apply the plan with DoD gating")
def step_try_apply_plan_dod(context: Context) -> None:
    """Attempt to apply the plan, capturing DoDGatingError if raised."""
    service: PlanLifecycleService = context.dod_service
    plan_id = context.dod_plan.identity.plan_id
    context.dod_error = None
    try:
        context.dod_plan = service.apply_plan(plan_id)
    except DoDGatingError as exc:
        context.dod_error = exc
        # Refresh plan state from service after the blocked attempt
        context.dod_plan = service.get_plan(plan_id)


@when("I start the apply phase")
def step_start_apply_dod(context: Context) -> None:
    """Start the Apply phase processing."""
    service: PlanLifecycleService = context.dod_service
    plan_id = context.dod_plan.identity.plan_id
    context.dod_plan = service.start_apply(plan_id)


@when("I complete the apply phase")
def step_complete_apply_dod(context: Context) -> None:
    """Complete the Apply phase."""
    service: PlanLifecycleService = context.dod_service
    plan_id = context.dod_plan.identity.plan_id
    context.dod_plan = service.complete_apply(plan_id)


# ---------------------------------------------------------------------------
# Assertions
# ---------------------------------------------------------------------------


@then("the plan should transition to apply phase")
def step_assert_plan_in_apply(context: Context) -> None:
    """Assert the plan is now in the Apply phase."""
    plan = context.dod_plan
    assert plan.phase == PlanPhase.APPLY, (
        f"Expected plan phase 'apply', got '{plan.phase.value}'"
    )


@then("the plan should remain in execute phase")
def step_assert_plan_in_execute(context: Context) -> None:
    """Assert the plan is still in the Execute phase."""
    plan = context.dod_plan
    assert plan.phase == PlanPhase.EXECUTE, (
        f"Expected plan phase 'execute', got '{plan.phase.value}'"
    )


@then('the dod plan phase should be "{expected_phase}"')
def step_assert_dod_plan_phase(context: Context, expected_phase: str) -> None:
    """Assert the DoD plan phase matches the expected value."""
    plan = context.dod_plan
    assert plan.phase.value == expected_phase, (
        f"Expected plan phase '{expected_phase}', got '{plan.phase.value}'"
    )


@then('the dod plan processing state should be "{expected_state}"')
def step_assert_dod_plan_processing_state(
    context: Context, expected_state: str
) -> None:
    """Assert the DoD plan processing state matches the expected value."""
    plan = context.dod_plan
    assert plan.processing_state.value == expected_state, (
        f"Expected processing state '{expected_state}', "
        f"got '{plan.processing_state.value}'"
    )


@then("the plan validation summary should be empty")
def step_assert_validation_summary_empty(context: Context) -> None:
    """Assert the plan's validation_summary is None (no DoD evaluated)."""
    plan = context.dod_plan
    assert plan.validation_summary is None, (
        f"Expected validation_summary to be None, got {plan.validation_summary!r}"
    )


@then("the plan validation summary should record dod_evaluated as true")
def step_assert_dod_evaluated_true(context: Context) -> None:
    """Assert validation_summary has dod_evaluated=True."""
    plan = context.dod_plan
    assert plan.validation_summary is not None, (
        "Expected validation_summary to be set, but it is None"
    )
    assert plan.validation_summary.get("dod_evaluated") is True, (
        f"Expected dod_evaluated=True, got {plan.validation_summary!r}"
    )


@then("the plan validation summary should record dod_all_passed as true")
def step_assert_dod_all_passed_true(context: Context) -> None:
    """Assert validation_summary has dod_all_passed=True."""
    plan = context.dod_plan
    assert plan.validation_summary is not None, (
        "Expected validation_summary to be set, but it is None"
    )
    assert plan.validation_summary.get("dod_all_passed") is True, (
        f"Expected dod_all_passed=True, got {plan.validation_summary!r}"
    )


@then("the plan validation summary should record dod_all_passed as false")
def step_assert_dod_all_passed_false(context: Context) -> None:
    """Assert validation_summary has dod_all_passed=False."""
    plan = context.dod_plan
    assert plan.validation_summary is not None, (
        "Expected validation_summary to be set, but it is None"
    )
    assert plan.validation_summary.get("dod_all_passed") is False, (
        f"Expected dod_all_passed=False, got {plan.validation_summary!r}"
    )


@then("a DoD gating error should be raised")
def step_assert_dod_gating_error(context: Context) -> None:
    """Assert a DoDGatingError was raised."""
    assert context.dod_error is not None, (
        "Expected a DoDGatingError to be raised, but no error was captured"
    )
    assert isinstance(context.dod_error, DoDGatingError), (
        f"Expected DoDGatingError, got {type(context.dod_error).__name__}"
    )


@then(
    "the DoD gating error should report {failed:d} failed criteria out of {total:d} total"
)
def step_assert_dod_error_counts(context: Context, failed: int, total: int) -> None:
    """Assert the DoDGatingError has the expected failure counts."""
    error = context.dod_error
    assert isinstance(error, DoDGatingError), (
        f"Expected DoDGatingError, got {type(error).__name__}"
    )
    assert error.failed_count == failed, (
        f"Expected failed_count={failed}, got {error.failed_count}"
    )
    assert error.total_count == total, (
        f"Expected total_count={total}, got {error.total_count}"
    )


@then('the plan validation summary should contain key "{key}" with value true')
def step_assert_validation_summary_key_true(context: Context, key: str) -> None:
    """Assert validation_summary contains a key with value True."""
    plan = context.dod_plan
    assert plan.validation_summary is not None, (
        "Expected validation_summary to be set, but it is None"
    )
    assert key in plan.validation_summary, (
        f"Expected key '{key}' in validation_summary, "
        f"got keys: {list(plan.validation_summary.keys())}"
    )
    assert plan.validation_summary[key] is True, (
        f"Expected validation_summary['{key}']=True, "
        f"got {plan.validation_summary[key]!r}"
    )


@then('the plan validation summary should contain key "{key}"')
def step_assert_validation_summary_has_key(context: Context, key: str) -> None:
    """Assert validation_summary contains a specific key."""
    plan = context.dod_plan
    assert plan.validation_summary is not None, (
        "Expected validation_summary to be set, but it is None"
    )
    assert key in plan.validation_summary, (
        f"Expected key '{key}' in validation_summary, "
        f"got keys: {list(plan.validation_summary.keys())}"
    )
