"""Step definitions for plan_lifecycle_service_coverage_boost_r3.feature.

Targets remaining uncovered lines in PlanLifecycleService:
- Lines ~1520-1540: complete_strategize event_bus.emit success + exception
- Lines ~1569-1581: fail_strategize event_bus.emit success + exception
- Lines ~1739-1759: complete_execute event_bus.emit success + exception
- Lines ~1774-1792: fail_execute event_bus.emit success + exception
- Lines ~2036-2054: fail_apply event_bus.emit success + exception
- Lines ~2056-2057: fail_execute _cleanup_devcontainers call
- Lines ~2056-2057: fail_apply _cleanup_devcontainers call
"""

from __future__ import annotations

from unittest.mock import patch

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

from cleveragents.application.services.plan_lifecycle_service import (
    PlanLifecycleService,
)
from cleveragents.config.settings import Settings
from cleveragents.domain.models.core.plan import (
    PlanPhase,
    ProcessingState,
    ProjectLink,
)
from cleveragents.infrastructure.events.types import EventType


# -----------------------------------------------------------------
# Background
# -----------------------------------------------------------------


@given("I have a fresh plan lifecycle service for coverage boost r3")
def step_create_fresh_service_r3(context: Context) -> None:
    """Create a clean PlanLifecycleService instance."""
    Settings._instance = None
    settings = Settings()
    context.service = PlanLifecycleService(settings=settings)
    context.error = None


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


def _create_action_r3(context: Context, name: str, **kwargs):
    """Helper to create a basic action with sensible defaults."""
    defaults = {
        "name": name,
        "description": f"Action {name}",
        "definition_of_done": "Tests pass",
        "strategy_actor": "openai/gpt-4",
        "execution_actor": "openai/gpt-4",
    }
    defaults.update(kwargs)
    return context.service.create_action(**defaults)


def _advance_to_state(
    context: Context, target_phase: PlanPhase, target_state: ProcessingState
):
    """Advance the current plan to the target phase and state."""
    pid = context.plan.identity.plan_id
    if (
        target_phase == PlanPhase.STRATEGIZE
        and target_state == ProcessingState.PROCESSING
    ):
        context.service.start_strategize(pid)
        context.plan = context.service.get_plan(pid)
    elif (
        target_phase == PlanPhase.EXECUTE and target_state == ProcessingState.PROCESSING
    ):
        context.service.start_strategize(pid)
        context.service.complete_strategize(pid)
        context.service.execute_plan(pid)
        context.service.start_execute(pid)
        context.plan = context.service.get_plan(pid)
    elif target_phase == PlanPhase.APPLY and target_state == ProcessingState.PROCESSING:
        context.service.start_strategize(pid)
        context.service.complete_strategize(pid)
        context.service.execute_plan(pid)
        context.service.start_execute(pid)
        context.service.complete_execute(pid)
        context.service.apply_plan(pid)
        context.service.start_apply(pid)
        context.plan = context.service.get_plan(pid)


class RecordingEventBus:
    """A simple event bus that records emitted events."""

    def __init__(self):
        self.events = []

    def emit(self, event):
        self.events.append(event)

    def subscribe(self, event_type, handler):
        pass


class FailingEventBus:
    """An event bus that always raises on emit."""

    def emit(self, event):
        raise RuntimeError("Simulated event bus failure")

    def subscribe(self, event_type, handler):
        pass


# =================================================================
# complete_strategize — event_bus.emit success
# =================================================================


@given("a plan lifecycle service with a recording event bus for r3")
def step_service_with_recording_bus_r3(context: Context) -> None:
    """Create a service with a recording event bus."""
    Settings._instance = None
    settings = Settings()
    context.recording_bus = RecordingEventBus()
    context.service = PlanLifecycleService(
        settings=settings,
        event_bus=context.recording_bus,
    )
    context.error = None


@given('an action "{name}" exists for coverage boost r3')
def step_create_action_r3(context: Context, name: str) -> None:
    """Create an action with the given name."""
    context.action = _create_action_r3(context, name)


@given("a plan in strategize phase with processing state for r3")
def step_plan_strategize_processing_r3(context: Context) -> None:
    """Create a plan in Strategize/PROCESSING state."""
    context.plan = context.service.use_action(
        action_name=str(context.action.namespaced_name),
        project_links=[ProjectLink(project_name="proj-r3")],
    )
    _advance_to_state(context, PlanPhase.STRATEGIZE, ProcessingState.PROCESSING)


@when("I complete strategize on the plan with event bus")
def step_complete_strategize_with_bus_r3(context: Context) -> None:
    """Complete strategize — should emit PLAN_STATE_CHANGED event."""
    context.error = None
    try:
        context.plan = context.service.complete_strategize(
            context.plan.identity.plan_id
        )
    except Exception as e:
        context.error = e


@then("the event bus should have recorded a PLAN_STATE_CHANGED event")
def step_verify_plan_state_changed_event_r3(context: Context) -> None:
    """Verify PLAN_STATE_CHANGED was emitted."""
    assert context.error is None, f"Expected no error, got: {context.error}"
    state_events = [
        e
        for e in context.recording_bus.events
        if e.event_type == EventType.PLAN_STATE_CHANGED
    ]
    assert len(state_events) >= 1, (
        f"Expected at least 1 PLAN_STATE_CHANGED event, got {len(state_events)}. "
        f"All events: {[e.event_type for e in context.recording_bus.events]}"
    )


# =================================================================
# complete_strategize — event_bus.emit exception
# =================================================================


@given("a plan lifecycle service with a failing event bus for r3")
def step_service_with_failing_bus_r3(context: Context) -> None:
    """Create a service with an event bus that raises on emit."""
    Settings._instance = None
    settings = Settings()
    context.service = PlanLifecycleService(
        settings=settings,
        event_bus=FailingEventBus(),
    )
    context.error = None


@when("I complete strategize on the plan and event bus emit fails")
def step_complete_strategize_failing_bus_r3(context: Context) -> None:
    """Complete strategize — event bus will raise but plan should still complete."""
    context.error = None
    try:
        context.plan = context.service.complete_strategize(
            context.plan.identity.plan_id
        )
    except Exception as e:
        context.error = e


@then('the plan phase should be "{phase}" and state should be "{state}"')
def step_verify_plan_phase_state_r3(context: Context, phase: str, state: str) -> None:
    """Verify the plan phase and state."""
    assert context.error is None, f"Expected no error, got: {context.error}"
    assert context.plan.phase.value == phase, (
        f"Expected phase '{phase}', got '{context.plan.phase.value}'"
    )
    assert context.plan.processing_state.value == state, (
        f"Expected state '{state}', got '{context.plan.processing_state.value}'"
    )


# =================================================================
# fail_strategize — event_bus.emit success
# =================================================================


@when('I fail strategize on the plan with error "{error}"')
def step_fail_strategize_with_error_r3(context: Context, error: str) -> None:
    """Fail strategize with an error message."""
    context.error = None
    try:
        context.plan = context.service.fail_strategize(
            context.plan.identity.plan_id, error
        )
    except Exception as e:
        context.error = e


@then("the event bus should have recorded a PLAN_ERRORED event")
def step_verify_plan_errored_event_r3(context: Context) -> None:
    """Verify PLAN_ERRORED was emitted."""
    assert context.error is None, f"Expected no error, got: {context.error}"
    error_events = [
        e
        for e in context.recording_bus.events
        if e.event_type == EventType.PLAN_ERRORED
    ]
    assert len(error_events) >= 1, (
        f"Expected at least 1 PLAN_ERRORED event, got {len(error_events)}. "
        f"All events: {[e.event_type for e in context.recording_bus.events]}"
    )


# =================================================================
# fail_strategize — event_bus.emit exception
# =================================================================


@when("I fail strategize on the plan and event bus emit fails")
def step_fail_strategize_failing_bus_r3(context: Context) -> None:
    """Fail strategize — event bus will raise but plan should still be errored."""
    context.error = None
    try:
        context.plan = context.service.fail_strategize(
            context.plan.identity.plan_id, "Test error"
        )
    except Exception as e:
        context.error = e


@then("the plan should be in errored state for r3")
def step_verify_errored_state_r3(context: Context) -> None:
    """Verify the plan is in ERRORED state."""
    assert context.error is None, f"Expected no error, got: {context.error}"
    assert context.plan.processing_state == ProcessingState.ERRORED, (
        f"Expected ERRORED, got {context.plan.processing_state.value}"
    )


# =================================================================
# complete_execute — event_bus.emit success
# =================================================================


@given("a plan in execute phase with processing state for r3")
def step_plan_execute_processing_r3(context: Context) -> None:
    """Create a plan in Execute/PROCESSING state."""
    context.plan = context.service.use_action(
        action_name=str(context.action.namespaced_name),
        project_links=[ProjectLink(project_name="proj-r3")],
    )
    _advance_to_state(context, PlanPhase.EXECUTE, ProcessingState.PROCESSING)


@when("I complete execute on the plan with event bus")
def step_complete_execute_with_bus_r3(context: Context) -> None:
    """Complete execute — should emit PLAN_STATE_CHANGED event."""
    context.error = None
    try:
        context.plan = context.service.complete_execute(context.plan.identity.plan_id)
    except Exception as e:
        context.error = e


# =================================================================
# complete_execute — event_bus.emit exception
# =================================================================


@when("I complete execute on the plan and event bus emit fails")
def step_complete_execute_failing_bus_r3(context: Context) -> None:
    """Complete execute — event bus will raise but plan should still complete."""
    context.error = None
    try:
        context.plan = context.service.complete_execute(context.plan.identity.plan_id)
    except Exception as e:
        context.error = e


# =================================================================
# fail_execute — event_bus.emit success
# =================================================================


@when('I fail execute on the plan with error "{error}"')
def step_fail_execute_with_error_r3(context: Context, error: str) -> None:
    """Fail execute with an error message."""
    context.error = None
    try:
        context.plan = context.service.fail_execute(
            context.plan.identity.plan_id, error
        )
    except Exception as e:
        context.error = e


# =================================================================
# fail_execute — event_bus.emit exception
# =================================================================


@when("I fail execute on the plan and event bus emit fails")
def step_fail_execute_failing_bus_r3(context: Context) -> None:
    """Fail execute — event bus will raise but plan should still be errored."""
    context.error = None
    try:
        context.plan = context.service.fail_execute(
            context.plan.identity.plan_id, "Test error"
        )
    except Exception as e:
        context.error = e


# =================================================================
# fail_apply — event_bus.emit success
# =================================================================


@given("a plan in apply phase with processing state for r3")
def step_plan_apply_processing_r3(context: Context) -> None:
    """Create a plan in Apply/PROCESSING state."""
    context.plan = context.service.use_action(
        action_name=str(context.action.namespaced_name),
        project_links=[ProjectLink(project_name="proj-r3")],
    )
    _advance_to_state(context, PlanPhase.APPLY, ProcessingState.PROCESSING)


@when('I fail apply on the plan with error "{error}"')
def step_fail_apply_with_error_r3(context: Context, error: str) -> None:
    """Fail apply with an error message."""
    context.error = None
    try:
        context.plan = context.service.fail_apply(context.plan.identity.plan_id, error)
    except Exception as e:
        context.error = e


# =================================================================
# fail_apply — event_bus.emit exception
# =================================================================


@when("I fail apply on the plan and event bus emit fails")
def step_fail_apply_failing_bus_r3(context: Context) -> None:
    """Fail apply — event bus will raise but plan should still be errored."""
    context.error = None
    try:
        context.plan = context.service.fail_apply(
            context.plan.identity.plan_id, "Test error"
        )
    except Exception as e:
        context.error = e


# =================================================================
# fail_execute — _cleanup_devcontainers success
# =================================================================


@given("a plan lifecycle service with mocked devcontainer cleanup for r3")
def step_service_with_mocked_cleanup_r3(context: Context) -> None:
    """Create a service where devcontainer cleanup returns stopped containers."""
    Settings._instance = None
    settings = Settings()
    context.service = PlanLifecycleService(settings=settings)
    context.cleanup_called = False
    context.error = None


@when('I fail execute on the plan with error "{error}" and cleanup should be called')
def step_fail_execute_with_cleanup_r3(context: Context, error: str) -> None:
    """Fail execute with mocked cleanup."""
    context.error = None
    context.cleanup_called = False
    try:
        with patch(
            "cleveragents.application.services.cleanup_service.stop_all_active_containers",
            return_value=["container-1", "container-2"],
        ):
            context.plan = context.service.fail_execute(
                context.plan.identity.plan_id, error
            )
            context.cleanup_called = True
    except Exception as e:
        context.error = e


@then("the plan should be in errored state and cleanup should have been called")
def step_verify_errored_with_cleanup_r3(context: Context) -> None:
    """Verify the plan is ERRORED and cleanup was invoked."""
    assert context.error is None, f"Expected no error, got: {context.error}"
    assert context.plan.processing_state == ProcessingState.ERRORED, (
        f"Expected ERRORED, got {context.plan.processing_state.value}"
    )


# =================================================================
# fail_apply — _cleanup_devcontainers success
# =================================================================


@when('I fail apply on the plan with error "{error}" and cleanup should be called')
def step_fail_apply_with_cleanup_r3(context: Context, error: str) -> None:
    """Fail apply with mocked cleanup."""
    context.error = None
    context.cleanup_called = False
    try:
        with patch(
            "cleveragents.application.services.cleanup_service.stop_all_active_containers",
            return_value=["container-1", "container-2"],
        ):
            context.plan = context.service.fail_apply(
                context.plan.identity.plan_id, error
            )
            context.cleanup_called = True
    except Exception as e:
        context.error = e
