"""Step definitions for subplan model hierarchy, defaults, and guardrails."""

from __future__ import annotations

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

from cleveragents.domain.models.core.plan import (
    ExecutionMode,
    NamespacedName,
    Plan,
    PlanIdentity,
    PlanPhase,
    ProcessingState,
    SubplanConfig,
    SubplanFailureHandler,
    SubplanMergeStrategy,
    SubplanStatus,
    can_transition,
)

_R = "01ARZ3NDEKTSV4RRFFQ69G5FAV"  # root ULID
_P = "01ARZ3NDEKTSV4RRFFQ69G5FAW"  # parent ULID
_C = "01ARZ3NDEKTSV4RRFFQ69G5FAX"  # child ULID
_G = "01ARZ3NDEKTSV4RRFFQ69G5FAY"  # grandchild ULID
_S = "01KH72MDGQMPRW3R5WPZVB8KC1"  # status ULID


def _plan(
    pid: str = _R,
    par: str | None = None,
    root: str | None = None,
    att: int = 1,
    ph: PlanPhase = PlanPhase.STRATEGIZE,
    ps: ProcessingState = ProcessingState.QUEUED,
    sc: SubplanConfig | None = None,
    ss: list[SubplanStatus] | None = None,
) -> Plan:
    """Build a Plan with sensible defaults for subplan testing."""
    return Plan(
        identity=PlanIdentity(
            plan_id=pid,
            parent_plan_id=par,
            root_plan_id=root,
            attempt=att,
        ),
        namespaced_name=NamespacedName(
            server=None,
            namespace="local",
            name="subplan-test",
        ),
        description="Subplan test plan",
        action_name="local/subplan-action",
        phase=ph,
        processing_state=ps,
        subplan_config=sc,
        subplan_statuses=ss or [],
    )


def _st(err: str | None = None, att: int = 1) -> SubplanStatus:
    """Build a SubplanStatus."""
    return SubplanStatus(
        subplan_id=_S,
        action_name="local/sub-task",
        status=ProcessingState.ERRORED if err else ProcessingState.QUEUED,
        error=err,
        attempt_number=att,
        target_resources=["src/main.py"],
    )


# -- Given: plan creation --
@given("I create a subplan-test root plan")
def step_g01(ctx: Context) -> None:
    """Create root plan."""
    ctx.subplan_plan = _plan()


@given("I create a subplan-test plan with root_plan_id equal to plan_id")
def step_g02(ctx: Context) -> None:
    """Create root plan with explicit root_plan_id."""
    ctx.subplan_plan = _plan(root=_R)


@given("I create a subplan-test child plan with a parent")
def step_g03(ctx: Context) -> None:
    """Create child plan."""
    ctx.subplan_plan = _plan(pid=_C, par=_P, root=_R)


@given("I create a subplan-test three-level hierarchy")
def step_g05(ctx: Context) -> None:
    """Create root/parent/grandchild."""
    ctx.subplan_root = _plan(pid=_R, root=_R)
    ctx.subplan_parent = _plan(pid=_P, par=_R, root=_R)
    ctx.subplan_grandchild = _plan(pid=_G, par=_P, root=_R)


@given("I create a subplan-test default plan")
def step_g06(ctx: Context) -> None:
    """Create default plan."""
    ctx.subplan_plan = _plan()


@given("I create a subplan-test plan with sequential subplan config")
def step_g07(ctx: Context) -> None:
    """Plan with sequential config."""
    ctx.subplan_plan = _plan(
        sc=SubplanConfig(
            execution_mode=ExecutionMode.SEQUENTIAL,
            merge_strategy=SubplanMergeStrategy.GIT_THREE_WAY,
            fail_fast=False,
        )
    )


@given("I create a subplan-test plan with parallel subplan config")
def step_g08(ctx: Context) -> None:
    """Plan with parallel config."""
    ctx.subplan_plan = _plan(
        sc=SubplanConfig(
            execution_mode=ExecutionMode.PARALLEL,
            max_parallel=10,
            retry_failed=True,
            max_retries=3,
        )
    )


@given("I create a subplan-test default subplan config")
def step_g09(ctx: Context) -> None:
    """Standalone SubplanConfig with defaults."""
    ctx.subplan_config = SubplanConfig()


@given("I create a subplan-test plan with one subplan status")
def step_g10(ctx: Context) -> None:
    """Plan with one status."""
    ctx.subplan_plan = _plan(ss=[_st()])


@given("I create a subplan-test fail-fast config with errored status")
def step_g13(ctx: Context) -> None:
    """Fail-fast config + errored status."""
    ctx.subplan_handler_config = SubplanConfig(
        execution_mode=ExecutionMode.PARALLEL, fail_fast=True
    )
    ctx.subplan_handler_status = _st("TimeoutError: timed out")


@given("I create a subplan-test parallel-no-fail-fast config with errored status")
def step_g14(ctx: Context) -> None:
    """Parallel (no fail_fast) + errored status."""
    ctx.subplan_handler_config = SubplanConfig(
        execution_mode=ExecutionMode.PARALLEL, fail_fast=False
    )
    ctx.subplan_handler_status = _st("TimeoutError: timed out")


@given("I create a subplan-test config with retriable timeout error")
def step_g15(ctx: Context) -> None:
    """Config + retriable TimeoutError."""
    ctx.subplan_handler_config = SubplanConfig(retry_failed=True, max_retries=2)
    ctx.subplan_handler_status = _st("TimeoutError: op timed out", 1)


@given("I create a subplan-test config with non-retriable configuration error")
def step_g16(ctx: Context) -> None:
    """Config + non-retriable ConfigurationError."""
    ctx.subplan_handler_config = SubplanConfig(retry_failed=True, max_retries=2)
    ctx.subplan_handler_status = _st("ConfigurationError: bad", 1)


@given("I create a subplan-test config with retries disabled and retriable error")
def step_g17(ctx: Context) -> None:
    """Config (retries off) + retriable error."""
    ctx.subplan_handler_config = SubplanConfig(retry_failed=False)
    ctx.subplan_handler_status = _st("TimeoutError: timed out", 1)


@given("I create a subplan-test config with max retries exceeded")
def step_g18(ctx: Context) -> None:
    """Config + attempt > max_retries."""
    ctx.subplan_handler_config = SubplanConfig(retry_failed=True, max_retries=2)
    ctx.subplan_handler_status = _st("TimeoutError: timed out", 3)


@given("I build a subplan-test plan with the child identity")
def step_g19(ctx: Context) -> None:
    """Alias plan for CLI dict assertion."""
    ctx.subplan_cli_plan = ctx.subplan_plan


@given("I build a subplan-test plan with the root identity")
def step_g20(ctx: Context) -> None:
    """Alias plan for CLI dict assertion."""
    ctx.subplan_cli_plan = ctx.subplan_plan


# -- When: validation errors --
@when("I try to create a subplan-test identity with invalid parent ULID")
def step_w01(ctx: Context) -> None:
    """Invalid parent_plan_id."""
    ctx.error = None
    try:
        PlanIdentity(plan_id=_R, parent_plan_id="not-a-valid-ulid")
    except ValidationError as e:
        ctx.error = e


@when("I try to create a subplan-test config with max_parallel {v:d}")
def step_w02(ctx: Context, v: int) -> None:
    """Invalid max_parallel."""
    ctx.error = None
    try:
        SubplanConfig(max_parallel=v)
    except ValidationError as e:
        ctx.error = e


@when("I try to create a subplan-test config with max_retries {v:d}")
def step_w03(ctx: Context, v: int) -> None:
    """Invalid max_retries."""
    ctx.error = None
    try:
        SubplanConfig(max_retries=v)
    except ValidationError as e:
        ctx.error = e


# -- Then: plan identity --
@then("the subplan-test plan should not be a subplan")
def step_t01(ctx: Context) -> None:
    """Not a subplan."""
    assert not ctx.subplan_plan.is_subplan


@then("the subplan-test plan should be a subplan")
def step_t02(ctx: Context) -> None:
    """Is a subplan."""
    assert ctx.subplan_plan.is_subplan


@then("the subplan-test plan should be a root plan")
def step_t03(ctx: Context) -> None:
    """Is root."""
    assert ctx.subplan_plan.is_root_plan


@then("the subplan-test plan should not be a root plan")
def step_t04(ctx: Context) -> None:
    """Not root."""
    assert not ctx.subplan_plan.is_root_plan


@then("the subplan-test plan depth should be {e:d}")
def step_t05(ctx: Context, e: int) -> None:
    """Depth check."""
    assert ctx.subplan_plan.depth == e


@then("the subplan-test plan attempt should be {e:d}")
def step_t06(ctx: Context, e: int) -> None:
    """Attempt check."""
    assert ctx.subplan_plan.identity.attempt == e


@then('the subplan-test plan phase should be "{e}"')
def step_t07(ctx: Context, e: str) -> None:
    """Phase check."""
    assert ctx.subplan_plan.phase.value == e


@then('the subplan-test plan processing state should be "{e}"')
def step_t08(ctx: Context, e: str) -> None:
    """Processing state check."""
    assert ctx.subplan_plan.processing_state.value == e


@then("the subplan-test plan should not be terminal")
def step_t09(ctx: Context) -> None:
    """Not terminal."""
    assert not ctx.subplan_plan.is_terminal


@then("the subplan-test plan should not be errored")
def step_t10(ctx: Context) -> None:
    """Not errored."""
    assert not ctx.subplan_plan.is_errored


@then("the subplan-test plan automation profile should be none")
def step_t11(ctx: Context) -> None:
    """Automation profile defaults to None."""
    assert ctx.subplan_plan.automation_profile is None


@then("the subplan-test plan subplan_config should be none")
def step_t12(ctx: Context) -> None:
    """No subplan config."""
    assert ctx.subplan_plan.subplan_config is None


@then("the subplan-test plan should have {c:d} subplan statuses")
def step_t13(ctx: Context, c: int) -> None:
    """Status count check."""
    assert len(ctx.subplan_plan.subplan_statuses) == c


# -- Then: subplan config on plan --
@then('the subplan-test plan subplan_config execution_mode should be "{e}"')
def step_t14(ctx: Context, e: str) -> None:
    """Config execution_mode."""
    assert ctx.subplan_plan.subplan_config is not None
    assert ctx.subplan_plan.subplan_config.execution_mode.value == e


@then('the subplan-test plan subplan_config merge_strategy should be "{e}"')
def step_t15(ctx: Context, e: str) -> None:
    """Config merge_strategy."""
    assert ctx.subplan_plan.subplan_config is not None
    assert ctx.subplan_plan.subplan_config.merge_strategy.value == e


@then("the subplan-test plan subplan_config fail_fast should be {e}")
def step_t16(ctx: Context, e: str) -> None:
    """Config fail_fast."""
    assert ctx.subplan_plan.subplan_config is not None
    assert ctx.subplan_plan.subplan_config.fail_fast is (e.lower() == "true")


@then("the subplan-test plan subplan_config max_parallel should be {e:d}")
def step_t17(ctx: Context, e: int) -> None:
    """Config max_parallel."""
    assert ctx.subplan_plan.subplan_config is not None
    assert ctx.subplan_plan.subplan_config.max_parallel == e


@then("the subplan-test plan subplan_config retry_failed should be {e}")
def step_t18(ctx: Context, e: str) -> None:
    """Config retry_failed."""
    assert ctx.subplan_plan.subplan_config is not None
    assert ctx.subplan_plan.subplan_config.retry_failed is (e.lower() == "true")


@then("the subplan-test plan subplan_config max_retries should be {e:d}")
def step_t19(ctx: Context, e: int) -> None:
    """Config max_retries."""
    assert ctx.subplan_plan.subplan_config is not None
    assert ctx.subplan_plan.subplan_config.max_retries == e


# -- Then: standalone SubplanConfig defaults --
@then('the subplan-test subplan config execution_mode should be "{e}"')
def step_t20(ctx: Context, e: str) -> None:
    """Standalone config execution_mode."""
    assert ctx.subplan_config.execution_mode.value == e


@then('the subplan-test subplan config merge_strategy should be "{e}"')
def step_t21(ctx: Context, e: str) -> None:
    """Standalone config merge_strategy."""
    assert ctx.subplan_config.merge_strategy.value == e


@then("the subplan-test subplan config max_parallel should be {e:d}")
def step_t22(ctx: Context, e: int) -> None:
    """Standalone config max_parallel."""
    assert ctx.subplan_config.max_parallel == e


@then("the subplan-test subplan config fail_fast should be {e}")
def step_t23(ctx: Context, e: str) -> None:
    """Standalone config fail_fast."""
    assert ctx.subplan_config.fail_fast is (e.lower() == "true")


@then("the subplan-test subplan config retry_failed should be {e}")
def step_t24(ctx: Context, e: str) -> None:
    """Standalone config retry_failed."""
    assert ctx.subplan_config.retry_failed is (e.lower() == "true")


@then("the subplan-test subplan config max_retries should be {e:d}")
def step_t25(ctx: Context, e: int) -> None:
    """Standalone config max_retries."""
    assert ctx.subplan_config.max_retries == e


@then("the subplan-test subplan config timeout should be none")
def step_t26(ctx: Context) -> None:
    """Standalone config timeout is None."""
    assert ctx.subplan_config.timeout_per_subplan_seconds is None


# -- Then: has_subplans --
@then("the subplan-test plan should have subplans")
def step_t27(ctx: Context) -> None:
    """Has subplans."""
    assert ctx.subplan_plan.has_subplans


@then("the subplan-test plan should not have subplans")
def step_t28(ctx: Context) -> None:
    """No subplans."""
    assert not ctx.subplan_plan.has_subplans


# -- Then: hierarchy --
@then("the subplan-test grandchild root_plan_id should match the root plan_id")
def step_t29(ctx: Context) -> None:
    """Grandchild root matches root."""
    assert (
        ctx.subplan_grandchild.identity.root_plan_id
        == ctx.subplan_root.identity.plan_id
    )


# -- Then: transitions --
@then('subplan-test transition from "{f}" to "{t}" should be valid')
def step_t30(ctx: Context, f: str, t: str) -> None:
    """Valid transition."""
    assert can_transition(PlanPhase(f), PlanPhase(t))


@then('subplan-test transition from "{f}" to "{t}" should be invalid')
def step_t31(ctx: Context, f: str, t: str) -> None:
    """Invalid transition."""
    assert not can_transition(PlanPhase(f), PlanPhase(t))


# -- Then: failure handler --
@then("the subplan-test failure handler should stop others")
def step_t34(ctx: Context) -> None:
    """Handler stops others."""
    h = SubplanFailureHandler()
    assert h.should_stop_others(ctx.subplan_handler_config, ctx.subplan_handler_status)


@then("the subplan-test failure handler should not stop others")
def step_t35(ctx: Context) -> None:
    """Handler does not stop others."""
    h = SubplanFailureHandler()
    assert not h.should_stop_others(
        ctx.subplan_handler_config, ctx.subplan_handler_status
    )


@then("the subplan-test failure handler should retry")
def step_t36(ctx: Context) -> None:
    """Handler retries."""
    h = SubplanFailureHandler()
    assert h.should_retry(ctx.subplan_handler_config, ctx.subplan_handler_status)


@then("the subplan-test failure handler should not retry")
def step_t37(ctx: Context) -> None:
    """Handler does not retry."""
    h = SubplanFailureHandler()
    assert not h.should_retry(ctx.subplan_handler_config, ctx.subplan_handler_status)


# -- Then: CLI dict --
@then("the subplan-test cli dict should contain parent_plan_id")
def step_t38(ctx: Context) -> None:
    """CLI dict has parent_plan_id."""
    assert "parent_plan_id" in ctx.subplan_cli_plan.as_cli_dict()


@then("the subplan-test cli dict should contain subplan_count of {e:d}")
def step_t39(ctx: Context, e: int) -> None:
    """CLI dict subplan_count."""
    assert ctx.subplan_plan.as_cli_dict().get("subplan_count") == e


@then("the subplan-test cli dict should not contain parent_plan_id")
def step_t40(ctx: Context) -> None:
    """CLI dict no parent_plan_id."""
    assert "parent_plan_id" not in ctx.subplan_cli_plan.as_cli_dict()
