"""Step definitions for plan_model_coverage_boost.feature.

Targets uncovered lines in src/cleveragents/domain/models/core/plan.py:
  - Line 792:  can_transition_to_next_phase returns True for ACTION phase
  - Lines 867, 870: get_project_scope resolves by alias / name
  - Lines 893, 895: can_revert_to returns False for terminal / max-reversion
  - Lines 970-980: as_cli_dict multi-project scope alias, read_only, changeset
  - Lines 985-991: as_cli_dict cross_project_dependencies
  - Lines 995, 997: as_cli_dict last_completed_step / last_checkpoint_id
"""

from __future__ import annotations

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

from cleveragents.domain.models.core.multi_project import (
    ChangeSetSummary,
    CrossProjectDependency,
    MultiProjectMetadata,
    ProjectScope,
)
from cleveragents.domain.models.core.plan import (
    NamespacedName,
    Plan,
    PlanIdentity,
    PlanPhase,
    ProcessingState,
)

# ---------------------------------------------------------------------------
# Valid ULIDs for test fixtures (Crockford base32, 26 chars)
# ---------------------------------------------------------------------------
_ULID_A = "01HGZ6FE0AQDYTR4BXVQZ6EA01"
_ULID_CHECKPOINT = "01HGZ6FE0AQDYTR4BXVQZ6EB01"


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_plan(
    phase: PlanPhase = PlanPhase.STRATEGIZE,
    processing_state: ProcessingState = ProcessingState.QUEUED,
    multi_project_metadata: MultiProjectMetadata | None = None,
    last_completed_step: int = -1,
    last_checkpoint_id: str | None = None,
    reversion_count: int = 0,
) -> Plan:
    """Build a minimal Plan for coverage-boost testing."""
    return Plan(
        identity=PlanIdentity(plan_id=_ULID_A),
        namespaced_name=NamespacedName(namespace="local", name="cov-boost"),
        action_name="local/cov-boost-action",
        description="Plan for coverage boost testing",
        phase=phase,
        processing_state=processing_state,
        multi_project_metadata=multi_project_metadata,
        last_completed_step=last_completed_step,
        last_checkpoint_id=last_checkpoint_id,
        reversion_count=reversion_count,
    )


# ===================================================================
# can_transition_to_next_phase — ACTION phase (line 792)
# ===================================================================


@given("a coverage-boost plan in the ACTION phase")
def step_create_action_plan(context: Context) -> None:
    context.plan = _make_plan(
        phase=PlanPhase.ACTION,
        processing_state=ProcessingState.QUEUED,
    )


@when("I check whether the coverage-boost plan can transition to the next phase")
def step_check_transition(context: Context) -> None:
    context.transition_result = context.plan.can_transition_to_next_phase


@then("the coverage-boost transition check should return True")
def step_verify_transition_true(context: Context) -> None:
    assert context.transition_result is True, (
        f"Expected True, got {context.transition_result}"
    )


# ===================================================================
# get_project_scope — alias / name lookup (lines 867, 870)
# ===================================================================


def _multi_project_with_aliased_scopes() -> MultiProjectMetadata:
    """Build MultiProjectMetadata with two scopes, one aliased."""
    return MultiProjectMetadata(
        project_scopes=[
            ProjectScope(
                project_name="local/api-service",
                alias="api",
                read_only=False,
            ),
            ProjectScope(
                project_name="local/web-app",
                alias=None,
                read_only=True,
            ),
        ],
    )


@given("a coverage-boost plan with multi-project metadata containing aliased scopes")
def step_create_plan_with_aliased_scopes(context: Context) -> None:
    context.plan = _make_plan(
        multi_project_metadata=_multi_project_with_aliased_scopes(),
    )


@when('I look up the coverage-boost project scope by alias "{alias}"')
def step_lookup_scope_by_alias(context: Context, alias: str) -> None:
    context.resolved_scope = context.plan.get_project_scope(alias)


@when('I look up the coverage-boost project scope by name "{name}"')
def step_lookup_scope_by_name(context: Context, name: str) -> None:
    context.resolved_scope = context.plan.get_project_scope(name)


@then('the coverage-boost resolved scope project name should be "{expected}"')
def step_verify_scope_name(context: Context, expected: str) -> None:
    assert context.resolved_scope is not None, "Expected a scope, got None"
    assert context.resolved_scope.project_name == expected, (
        f"Expected project_name={expected!r}, "
        f"got {context.resolved_scope.project_name!r}"
    )


@then("the coverage-boost resolved scope should be None")
def step_verify_scope_none(context: Context) -> None:
    assert context.resolved_scope is None, (
        f"Expected None, got {context.resolved_scope}"
    )


@given("a coverage-boost plan without multi-project metadata")
def step_create_plan_no_mp(context: Context) -> None:
    context.plan = _make_plan(multi_project_metadata=None)


# ===================================================================
# can_revert_to — terminal states / max reversions (lines 893, 895)
# ===================================================================


@given("a coverage-boost plan in APPLY phase with APPLIED state")
def step_create_applied_plan(context: Context) -> None:
    context.plan = _make_plan(
        phase=PlanPhase.APPLY,
        processing_state=ProcessingState.APPLIED,
    )


@given("a coverage-boost plan in APPLY phase with CANCELLED state")
def step_create_cancelled_plan(context: Context) -> None:
    context.plan = _make_plan(
        phase=PlanPhase.APPLY,
        processing_state=ProcessingState.CANCELLED,
    )


@given("a coverage-boost plan that has used all allowed reversions")
def step_create_max_reversion_plan(context: Context) -> None:
    context.plan = _make_plan(
        phase=PlanPhase.EXECUTE,
        processing_state=ProcessingState.ERRORED,
        reversion_count=Plan.MAX_REVERSIONS,
    )


@given("a coverage-boost plan in EXECUTE phase with ERRORED state and zero reversions")
def step_create_execute_errored_plan(context: Context) -> None:
    context.plan = _make_plan(
        phase=PlanPhase.EXECUTE,
        processing_state=ProcessingState.ERRORED,
        reversion_count=0,
    )


@when("I check whether the coverage-boost plan can revert to STRATEGIZE")
def step_check_revert(context: Context) -> None:
    context.revert_result = context.plan.can_revert_to(PlanPhase.STRATEGIZE)


@then("the coverage-boost reversion check should return False")
def step_verify_revert_false(context: Context) -> None:
    assert context.revert_result is False, (
        f"Expected False, got {context.revert_result}"
    )


@then("the coverage-boost reversion check should return True")
def step_verify_revert_true(context: Context) -> None:
    assert context.revert_result is True, f"Expected True, got {context.revert_result}"


# ===================================================================
# as_cli_dict — multi-project scope alias, read_only (lines 970, 972)
# ===================================================================


@given(
    "a coverage-boost plan with multi-project scopes having alias and read-only flags"
)
def step_create_plan_mp_alias_readonly(context: Context) -> None:
    mp = MultiProjectMetadata(
        project_scopes=[
            ProjectScope(
                project_name="local/api-service",
                alias="api",
                read_only=True,
            ),
        ],
    )
    context.plan = _make_plan(multi_project_metadata=mp)


@when("I generate the coverage-boost CLI dict")
def step_generate_cli_dict(context: Context) -> None:
    context.cli_dict = context.plan.as_cli_dict()


@then('the coverage-boost CLI dict multi_project scope should contain alias "{alias}"')
def step_verify_mp_alias(context: Context, alias: str) -> None:
    mp = context.cli_dict.get("multi_project")
    assert mp is not None, "multi_project key missing from CLI dict"
    scopes = mp.get("project_scopes", [])
    assert len(scopes) > 0, "No project_scopes in multi_project"
    assert scopes[0].get("alias") == alias, (
        f"Expected alias={alias!r}, got {scopes[0].get('alias')!r}"
    )


@then("the coverage-boost CLI dict multi_project scope should contain read_only True")
def step_verify_mp_read_only(context: Context) -> None:
    mp = context.cli_dict["multi_project"]
    scopes = mp["project_scopes"]
    assert scopes[0].get("read_only") is True, (
        f"Expected read_only=True, got {scopes[0].get('read_only')}"
    )


# ===================================================================
# as_cli_dict — changeset summary (lines 974-980)
# ===================================================================


@given("a coverage-boost plan with a multi-project scope that has a changeset summary")
def step_create_plan_mp_changeset(context: Context) -> None:
    mp = MultiProjectMetadata(
        project_scopes=[
            ProjectScope(
                project_name="local/api-service",
                changeset_summary=ChangeSetSummary(
                    project_name="local/api-service",
                    files_changed=3,
                    files_added=1,
                    files_deleted=0,
                    total_lines_changed=42,
                    validation_passed=True,
                ),
            ),
        ],
    )
    context.plan = _make_plan(multi_project_metadata=mp)


@then(
    "the coverage-boost CLI dict multi_project scope should contain changeset details"
)
def step_verify_mp_changeset(context: Context) -> None:
    mp = context.cli_dict["multi_project"]
    scope = mp["project_scopes"][0]
    cs = scope.get("changeset")
    assert cs is not None, "changeset key missing from scope"
    assert cs["files_changed"] == 3
    assert cs["files_added"] == 1
    assert cs["files_deleted"] == 0
    assert cs["total_lines_changed"] == 42
    assert cs["validation_passed"] is True


# ===================================================================
# as_cli_dict — cross_project_dependencies (lines 985-991)
# ===================================================================


@given("a coverage-boost plan with cross-project dependencies in metadata")
def step_create_plan_mp_deps(context: Context) -> None:
    mp = MultiProjectMetadata(
        project_scopes=[
            ProjectScope(project_name="local/api-service"),
            ProjectScope(project_name="local/web-app"),
        ],
        cross_project_dependencies=[
            CrossProjectDependency(
                source_project="local/web-app",
                target_project="local/api-service",
                dependency_type="api-consumer",
            ),
        ],
    )
    context.plan = _make_plan(multi_project_metadata=mp)


@then(
    "the coverage-boost CLI dict multi_project should contain cross_project_dependencies"
)
def step_verify_mp_deps(context: Context) -> None:
    mp = context.cli_dict["multi_project"]
    deps = mp.get("cross_project_dependencies")
    assert deps is not None, "cross_project_dependencies key missing"
    assert len(deps) == 1
    assert deps[0]["source"] == "local/web-app"
    assert deps[0]["target"] == "local/api-service"
    assert deps[0]["type"] == "api-consumer"


# ===================================================================
# as_cli_dict — last_completed_step / last_checkpoint_id (lines 995, 997)
# ===================================================================


@given("a coverage-boost plan with last_completed_step 2 and a checkpoint ID")
def step_create_plan_resume_metadata(context: Context) -> None:
    context.plan = _make_plan(
        last_completed_step=2,
        last_checkpoint_id=_ULID_CHECKPOINT,
    )


@then("the coverage-boost CLI dict should contain last_completed_step 2")
def step_verify_last_step(context: Context) -> None:
    assert context.cli_dict.get("last_completed_step") == 2, (
        f"Expected last_completed_step=2, got {context.cli_dict.get('last_completed_step')}"
    )


@then("the coverage-boost CLI dict should contain last_checkpoint_id")
def step_verify_checkpoint(context: Context) -> None:
    assert context.cli_dict.get("last_checkpoint_id") == _ULID_CHECKPOINT, (
        f"Expected last_checkpoint_id={_ULID_CHECKPOINT}, "
        f"got {context.cli_dict.get('last_checkpoint_id')}"
    )
