"""Step definitions for checkpoint_wiring.feature."""

from __future__ import annotations

import tempfile
from pathlib import Path

from behave import given, then, when

from cleveragents.infrastructure.sandbox.checkpoint import (
    CheckpointManager,
    SandboxCheckpoint,
)

# ── CLI factory ──────────────────────────────────────────


@when("I build a PlanExecutor via _get_plan_executor for ckpt_wire")
def step_build_executor(context: object) -> None:
    from cleveragents.cli.commands.plan import _get_plan_executor

    executor = _get_plan_executor()
    context.ckpt_executor = executor


@then("the executor checkpoint_manager should not be None for ckpt_wire")
def step_executor_has_mgr(context: object) -> None:
    executor = context.ckpt_executor
    assert executor._checkpoint_manager is not None, (
        "PlanExecutor._checkpoint_manager is None -- not wired"
    )


# ── DI container ─────────────────────────────────────────


@when("I get CheckpointManager from the DI container for ckpt_wire")
def step_container_mgr(context: object) -> None:
    from cleveragents.application.container import get_container

    container = get_container()
    context.ckpt_container_mgr = container.checkpoint_manager()


@then("the container checkpoint_manager should not be None for ckpt_wire")
def step_container_has_mgr(context: object) -> None:
    mgr = context.ckpt_container_mgr
    assert mgr is not None
    assert isinstance(mgr, CheckpointManager)


@then("the executor checkpoint_manager should be the container singleton for ckpt_wire")
def step_executor_matches_container(context: object) -> None:
    executor = context.ckpt_executor
    container_mgr = context.ckpt_container_mgr
    assert executor._checkpoint_manager is container_mgr, (
        "PlanExecutor did not reuse container CheckpointManager singleton"
    )


# ── _try_create_checkpoint with real manager ─────────────


@given("a PlanExecutor with a real CheckpointManager for ckpt_wire")
def step_executor_with_mgr(context: object) -> None:
    from cleveragents.application.services.plan_executor import PlanExecutor
    from cleveragents.application.services.plan_lifecycle_service import (
        PlanLifecycleService,
    )
    from cleveragents.config.settings import Settings

    lifecycle = PlanLifecycleService(settings=Settings())
    mgr = CheckpointManager()
    executor = PlanExecutor(
        lifecycle_service=lifecycle,
        checkpoint_manager=mgr,
    )
    context.ckpt_executor = executor
    context.ckpt_lifecycle = lifecycle
    context.ckpt_manager = mgr
    context.ckpt_plan_id = "plan-ckpt-test-01"


@given("a sandbox exists for the test plan for ckpt_wire")
def step_sandbox_exists(context: object) -> None:
    from cleveragents.infrastructure.sandbox.factory import SandboxFactory
    from cleveragents.infrastructure.sandbox.manager import SandboxManager

    plan_id = context.ckpt_plan_id
    tmpdir = tempfile.mkdtemp(prefix="ckpt-wire-")
    source_file = Path(tmpdir, "test.txt")
    initial_content = "checkpoint test"
    source_file.write_text(initial_content)

    factory = SandboxFactory()
    mgr = SandboxManager(factory=factory, cleanup_on_exit=False)
    sandbox = mgr.get_or_create_sandbox(
        plan_id=plan_id,
        resource_id="res-ckpt-test",
        original_path=tmpdir,
        sandbox_strategy="copy_on_write",
    )
    context.ckpt_sandbox = sandbox

    sandbox_path = Path(sandbox.context.sandbox_path)
    sandbox_path.mkdir(parents=True, exist_ok=True)
    sandbox_file = sandbox_path / "test.txt"
    if not sandbox_file.exists():
        sandbox_file.write_text(initial_content)
    context.ckpt_sandbox_file = sandbox_file
    context.ckpt_original_content = sandbox_file.read_text()

    # Patch the executor's sandbox resolver to return our sandbox
    executor = context.ckpt_executor
    executor._resolve_sandbox_for_checkpoint = lambda pid: sandbox


@given("a plan exists in the lifecycle service for ckpt_wire")
def step_plan_exists(context: object) -> None:
    from cleveragents.domain.models.core.plan import ProjectLink

    lifecycle = context.ckpt_lifecycle
    lifecycle.create_action(
        name="local/ckpt-wire-action",
        description="Checkpoint wiring test",
        definition_of_done="Checkpoint created",
        strategy_actor="local/stub",
        execution_actor="local/stub",
    )
    plan = lifecycle.use_action(
        action_name="local/ckpt-wire-action",
        project_links=[ProjectLink(project_name="test")],
    )
    context.ckpt_plan_id = plan.identity.plan_id

    # Re-patch resolver with correct plan_id
    executor = context.ckpt_executor
    sandbox = getattr(context, "ckpt_sandbox", None)
    if sandbox is not None:
        executor._resolve_sandbox_for_checkpoint = lambda pid: sandbox


@when("I call _try_create_checkpoint for ckpt_wire")
def step_try_checkpoint(context: object) -> None:
    executor = context.ckpt_executor
    plan_id = context.ckpt_plan_id
    result = executor._try_create_checkpoint(plan_id, "test_phase")
    context.ckpt_result = result


@then("a SandboxCheckpoint should be returned for ckpt_wire")
def step_checkpoint_returned(context: object) -> None:
    result = context.ckpt_result
    assert result is not None, "Expected SandboxCheckpoint, got None"
    assert isinstance(result, SandboxCheckpoint)


@then("None should be returned for ckpt_wire")
def step_none_returned(context: object) -> None:
    result = context.ckpt_result
    assert result is None


@then("the plan last_checkpoint_id should not be None for ckpt_wire")
def step_plan_has_checkpoint(context: object) -> None:
    lifecycle = context.ckpt_lifecycle
    plan_id = context.ckpt_plan_id
    plan = lifecycle.get_plan(plan_id)
    assert plan.last_checkpoint_id is not None, (
        f"plan.last_checkpoint_id is None for plan {plan_id}"
    )


@when("I mutate the sandbox file for ckpt_wire")
def step_mutate_sandbox(context: object) -> None:
    file_path = context.ckpt_sandbox_file
    mutated_content = "checkpoint test mutated"
    file_path.write_text(mutated_content)
    context.ckpt_mutated_content = mutated_content


@when("I rollback to the last checkpoint via the executor for ckpt_wire")
def step_rollback_to_checkpoint(context: object) -> None:
    executor = context.ckpt_executor
    plan_id = context.ckpt_plan_id
    success = executor._try_rollback_to_last_checkpoint(plan_id)
    context.ckpt_rollback_success = success


@then("rollback should succeed for ckpt_wire")
def step_verify_rollback_success(context: object) -> None:
    assert context.ckpt_rollback_success is True, "Rollback did not succeed"


@then("the sandbox file should match the original contents for ckpt_wire")
def step_verify_sandbox_restored(context: object) -> None:
    file_path = context.ckpt_sandbox_file
    assert file_path.read_text() == context.ckpt_original_content, (
        "Sandbox contents were not restored from checkpoint"
    )


# ── PlanExecutor with no manager ─────────────────────────


@given("a PlanExecutor with no CheckpointManager for ckpt_wire")
def step_executor_no_mgr(context: object) -> None:
    from cleveragents.application.services.plan_executor import PlanExecutor
    from cleveragents.application.services.plan_lifecycle_service import (
        PlanLifecycleService,
    )
    from cleveragents.config.settings import Settings

    executor = PlanExecutor(
        lifecycle_service=PlanLifecycleService(settings=Settings()),
    )
    context.ckpt_executor = executor
    context.ckpt_plan_id = "plan-no-mgr"


# ── CheckpointManager no-arg construction ────────────────


@when("I instantiate CheckpointManager with no args for ckpt_wire")
def step_instantiate_mgr(context: object) -> None:
    context.ckpt_new_mgr = CheckpointManager()


@then("CheckpointManager should be created successfully for ckpt_wire")
def step_mgr_created(context: object) -> None:
    mgr = context.ckpt_new_mgr
    assert mgr is not None
    assert isinstance(mgr, CheckpointManager)


# ── Default capability behaviour ─────────────────────────


@when("I create default capabilities for a writable resource for ckpt_wire")
def step_create_resource_capabilities(context: object) -> None:
    from cleveragents.domain.models.core.resource import ResourceCapabilities

    # Explicit flags to exercise the model_validator that derives
    # checkpointable from writable AND sandboxable.
    context.ckpt_resource_caps = ResourceCapabilities(
        writable=True,
        sandboxable=True,
    )


@then("the resource capability should be checkpointable for ckpt_wire")
def step_resource_checkpointable(context: object) -> None:
    capabilities = context.ckpt_resource_caps
    assert capabilities.checkpointable is True


@when("I create default capabilities for a writable tool for ckpt_wire")
def step_create_tool_capabilities(context: object) -> None:
    from cleveragents.domain.models.core.tool import ToolCapability

    context.ckpt_tool_cap = ToolCapability(writes=True)


@then("the tool capability should be checkpointable for ckpt_wire")
def step_tool_checkpointable(context: object) -> None:
    capability = context.ckpt_tool_cap
    assert capability.checkpointable is True


@when("I create capabilities for a non-writable resource for ckpt_wire")
def step_create_non_writable_resource(context: object) -> None:
    from cleveragents.domain.models.core.resource import ResourceCapabilities

    context.ckpt_resource_caps = ResourceCapabilities(writable=False)


@then("the resource capability should not be checkpointable for ckpt_wire")
def step_resource_not_checkpointable(context: object) -> None:
    capabilities = context.ckpt_resource_caps
    assert capabilities.checkpointable is False, (
        f"Expected checkpointable=False for non-writable resource, "
        f"got {capabilities.checkpointable}"
    )


@then(
    "creating a non-writable resource with checkpointable True "
    "should raise ValueError for ckpt_wire"
)
def step_non_writable_checkpointable_raises(context: object) -> None:
    from cleveragents.domain.models.core.resource import ResourceCapabilities

    try:
        ResourceCapabilities(writable=False, checkpointable=True)
        raise AssertionError("Expected ValueError but no exception was raised")
    except ValueError:
        pass  # Expected
