"""Step definitions for plan_execution_context_coverage_boost.feature."""

from unittest.mock import MagicMock

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

from cleveragents.application.services.plan_execution_context import (
    PlanExecutionContext,
    RuntimeExecuteActor,
    RuntimeExecuteResult,
)
from cleveragents.core.exceptions import PlanError, ValidationError
from cleveragents.domain.models.core.change import ChangeSetStore
from cleveragents.tool.runner import ToolRunner


@given("a pec fresh context")
def step_pec_fresh_context(context: Context) -> None:
    pass


@when("I pec construct PlanExecutionContext with empty plan_id")
def step_pec_construct_empty_plan_id(context: Context) -> None:
    context.pec_error = None
    try:
        PlanExecutionContext(plan_id="")
    except Exception as exc:
        context.pec_error = exc


@then('a pec ValidationError should be raised with "plan_id must not be empty"')
def step_pec_validation_error_empty_plan_id(context: Context) -> None:
    assert isinstance(context.pec_error, ValidationError), (
        f"Expected ValidationError, got {type(context.pec_error)}: {context.pec_error}"
    )
    assert "plan_id must not be empty" in str(context.pec_error)


@given("a pec PlanExecutionContext with all optional fields set")
def step_pec_context_with_all_fields(context: Context) -> None:
    context.pec_store = MagicMock(spec=ChangeSetStore)
    context.pec_ctx = PlanExecutionContext(
        plan_id="01PECPLAN01TESTPECPLAN01",
        decision_root_id="01PECDECI01TESTPECDECI01",
        sandbox_root="/tmp/pec-sandbox",
        automation_profile="trusted",
        project_resources={"repo": "my-repo"},
        resource_bindings={"db": MagicMock()},
        changeset_store=context.pec_store,
        plan_env="docker",
        project_env="kubernetes",
    )


@then("pec the automation_profile property should return the configured value")
def step_pec_automation_profile(context: Context) -> None:
    assert context.pec_ctx.automation_profile == "trusted"


@then("pec the plan_env property should return the configured value")
def step_pec_plan_env(context: Context) -> None:
    assert context.pec_ctx.plan_env == "docker"


@then("pec the project_env property should return the configured value")
def step_pec_project_env(context: Context) -> None:
    assert context.pec_ctx.project_env == "kubernetes"


@then("pec the project_resources property should return the configured dict")
def step_pec_project_resources(context: Context) -> None:
    assert context.pec_ctx.project_resources == {"repo": "my-repo"}


@then("pec the resource_bindings property should return the configured dict")
def step_pec_resource_bindings(context: Context) -> None:
    assert len(context.pec_ctx.resource_bindings) == 1
    assert "db" in context.pec_ctx.resource_bindings


@then("pec the changeset_store property should return the configured store")
def step_pec_changeset_store(context: Context) -> None:
    assert context.pec_ctx.changeset_store is context.pec_store


@then("pec the active_changeset_ids property should return a list")
def step_pec_active_changeset_ids(context: Context) -> None:
    assert isinstance(context.pec_ctx.active_changeset_ids, list)


@given("a pec PlanExecutionContext with a valid plan_id")
def step_pec_context_valid_plan_id(context: Context) -> None:
    store = MagicMock(spec=ChangeSetStore)
    store.get.return_value = None  # unknown changeset → None
    context.pec_ctx = PlanExecutionContext(
        plan_id="01PECPLAN02TESTPECPLAN02",
        changeset_store=store,
    )


@when("I pec call record_change without starting a changeset")
def step_pec_record_change_no_changeset(context: Context) -> None:
    from cleveragents.domain.models.core.change import ChangeEntry, ChangeOperation

    entry = ChangeEntry(
        plan_id="01PECPLAN02TESTPECPLAN02",
        resource_id="res-1",
        tool_name="test-tool",
        operation=ChangeOperation.MODIFY,
        path="/tmp/test",
    )
    context.pec_error = None
    try:
        context.pec_ctx.record_change(entry)
    except Exception as exc:
        context.pec_error = exc


@then("a pec PlanError should be raised about no active changeset")
def step_pec_no_active_changeset(context: Context) -> None:
    assert isinstance(context.pec_error, PlanError), (
        f"Expected PlanError, got {type(context.pec_error)}: {context.pec_error}"
    )
    assert "No active changeset" in str(context.pec_error)


@when("I pec call get_changeset with a nonexistent changeset_id")
def step_pec_get_changeset_nonexistent(context: Context) -> None:
    context.pec_get_result = context.pec_ctx.get_changeset("nonexistent-id")


@then("pec the result should be None")
def step_pec_result_none(context: Context) -> None:
    assert context.pec_get_result is None, (
        f"Expected None, got {context.pec_get_result}"
    )


@when("I pec call summarize on the execution context")
def step_pec_summarize(context: Context) -> None:
    context.pec_summary = context.pec_ctx.summarize()


@then("pec the summary should contain plan_id and decision_root_id")
def step_pec_summary_has_core_fields(context: Context) -> None:
    s = context.pec_summary
    assert s["plan_id"] == "01PECPLAN02TESTPECPLAN02"
    assert "decision_root_id" in s


@then("pec the summary should contain counts for resources and bindings")
def step_pec_summary_has_counts(context: Context) -> None:
    s = context.pec_summary
    assert "project_resource_count" in s
    assert "resource_binding_count" in s
    assert "active_changeset_count" in s
    assert "active_changeset_ids" in s
    assert "changeset_summaries" in s


@given("a pec valid PlanExecutionContext")
def step_pec_valid_context(context: Context) -> None:
    store = MagicMock(spec=ChangeSetStore)
    context.pec_ctx = PlanExecutionContext(
        plan_id="01PECPLAN03TESTPECPLAN03",
        changeset_store=store,
    )


@when("I pec construct RuntimeExecuteActor with None tool_runner")
def step_pec_runtime_actor_no_tool_runner(context: Context) -> None:
    context.pec_error = None
    try:
        RuntimeExecuteActor(
            tool_runner=None,
            execution_context=context.pec_ctx,
        )
    except Exception as exc:
        context.pec_error = exc


@then('a pec ValidationError should be raised with "tool_runner must not be None"')
def step_pec_tool_runner_validation_error(context: Context) -> None:
    assert isinstance(context.pec_error, ValidationError), (
        f"Expected ValidationError, got {type(context.pec_error)}: {context.pec_error}"
    )
    assert "tool_runner must not be None" in str(context.pec_error)


@given("a pec mock ToolRunner")
def step_pec_mock_tool_runner(context: Context) -> None:
    context.pec_tool_runner = MagicMock(spec=ToolRunner)


@when("I pec construct RuntimeExecuteActor with None execution_context")
def step_pec_runtime_actor_no_context(context: Context) -> None:
    context.pec_error = None
    try:
        RuntimeExecuteActor(
            tool_runner=context.pec_tool_runner,
            execution_context=None,
        )
    except Exception as exc:
        context.pec_error = exc


@then(
    'a pec ValidationError should be raised with "execution_context must not be None"'
)
def step_pec_execution_context_validation_error(context: Context) -> None:
    assert isinstance(context.pec_error, ValidationError), (
        f"Expected ValidationError, got {type(context.pec_error)}: {context.pec_error}"
    )
    assert "execution_context must not be None" in str(context.pec_error)


@given("a pec RuntimeExecuteActor with valid tool_runner and execution_context")
def step_pec_runtime_actor_valid(context: Context) -> None:
    store = MagicMock(spec=ChangeSetStore)
    store.start.return_value = "01PECCHG01TESTPECCHG01"
    tool_runner = MagicMock(spec=ToolRunner)
    tool_runner.discover.return_value = []

    ctx = PlanExecutionContext(
        plan_id="01PECPLAN04TESTPECPLAN04",
        changeset_store=store,
    )
    context.pec_store = store
    context.pec_tool_runner = tool_runner
    context.pec_actor = RuntimeExecuteActor(
        tool_runner=tool_runner,
        execution_context=ctx,
    )


@given(
    "a pec RuntimeExecuteActor with valid tool_runner and execution_context "
    "with sandbox_root"
)
def step_pec_runtime_actor_with_sandbox(context: Context) -> None:
    store = MagicMock(spec=ChangeSetStore)
    store.start.return_value = "01PECCHG02TESTPECCHG02"
    tool_runner = MagicMock(spec=ToolRunner)
    tool_runner.discover.return_value = []

    ctx = PlanExecutionContext(
        plan_id="01PECPLAN05TESTPECPLAN05",
        sandbox_root="/tmp/pec-sandbox",
        changeset_store=store,
    )
    context.pec_store = store
    context.pec_tool_runner = tool_runner
    context.pec_actor = RuntimeExecuteActor(
        tool_runner=tool_runner,
        execution_context=ctx,
    )


@given("a pec stream event collector")
def step_pec_stream_collector(context: Context) -> None:
    events: list[tuple[str, dict]] = []
    context.pec_events = events
    context.pec_stream_callback = lambda evt, data: events.append((evt, data))


@then("pec the tool_runner property should return the configured ToolRunner")
def step_pec_tool_runner_property(context: Context) -> None:
    assert context.pec_actor.tool_runner is context.pec_tool_runner


@then(
    "pec the execution_context property should return the configured "
    "PlanExecutionContext"
)
def step_pec_execution_context_property(context: Context) -> None:
    assert context.pec_actor.execution_context is not None
    assert context.pec_actor.execution_context.plan_id == "01PECPLAN04TESTPECPLAN04"


class _FakeDecision:
    def __init__(self, decision_id: str, step_text: str, sequence: int):
        self.decision_id = decision_id
        self.step_text = step_text
        self.sequence = sequence


@when("I pec execute with decisions and stream callback")
def step_pec_execute_with_callback(context: Context) -> None:
    decisions = [
        _FakeDecision("dec-1", "Step 1", 0),
        _FakeDecision("dec-2", "Step 2", 1),
    ]
    context.pec_result = context.pec_actor.execute(
        decisions=decisions,
        stream_callback=context.pec_stream_callback,
    )


@when("I pec execute with decisions and no stream callback")
def step_pec_execute_no_callback(context: Context) -> None:
    decisions = [
        _FakeDecision("dec-1", "Step 1", 0),
    ]
    context.pec_result = context.pec_actor.execute(
        decisions=decisions,
        stream_callback=None,
    )


@then('pec the stream events should include "runtime_execute_started"')
def step_pec_events_started(context: Context) -> None:
    event_names = [e[0] for e in context.pec_events]
    assert "runtime_execute_started" in event_names, (
        f"Expected runtime_execute_started in {event_names}"
    )


@then('pec the stream events should include "runtime_execute_complete"')
def step_pec_events_complete(context: Context) -> None:
    event_names = [e[0] for e in context.pec_events]
    assert "runtime_execute_complete" in event_names, (
        f"Expected runtime_execute_complete in {event_names}"
    )


@then("pec the result should be a RuntimeExecuteResult with a valid changeset_id")
def step_pec_result_valid_runtime_result(context: Context) -> None:
    assert isinstance(context.pec_result, RuntimeExecuteResult), (
        f"Expected RuntimeExecuteResult, got {type(context.pec_result)}"
    )
    assert context.pec_result.changeset_id is not None
    assert len(context.pec_result.changeset_id) > 0


@then("pec the result sandbox_refs should include the sandbox_root")
def step_pec_sandbox_refs_include_root(context: Context) -> None:
    assert "/tmp/pec-sandbox" in context.pec_result.sandbox_refs, (
        f"Expected /tmp/pec-sandbox in sandbox_refs: {context.pec_result.sandbox_refs}"
    )
