"""Step definitions for correction_service_coverage_boost.feature.

These steps target specific uncovered lines in correction_service.py:
- Lines 81-92: _emit_correction_applied try block (event_bus.emit call
  with DomainEvent construction including mode.value branch)
- Lines 96-101: _emit_correction_applied except block (event_bus.emit
  raises an exception, caught and logged as warning)

All step patterns are prefixed with 'csboost' to avoid collisions with
existing step definitions in other feature files.
"""

from behave import given, then, when

from cleveragents.application.services.correction_service import CorrectionService
from cleveragents.domain.models.core.correction import (
    CorrectionMode,
    CorrectionStatus,
)
from cleveragents.infrastructure.events.models import DomainEvent
from cleveragents.infrastructure.events.types import EventType

# ---------------------------------------------------------------------------
# Mock event buses
# ---------------------------------------------------------------------------


class _WorkingEventBus:
    """An event bus that records all emitted events."""

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

    def emit(self, event: DomainEvent) -> None:
        self.events.append(event)

    def subscribe(self, event_type, handler) -> None:
        pass


class _FailingEventBus:
    """An event bus whose emit always raises RuntimeError."""

    def __init__(self):
        self.emit_called = False

    def emit(self, event: DomainEvent) -> None:
        self.emit_called = True
        raise RuntimeError("event bus is broken")

    def subscribe(self, event_type, handler) -> None:
        pass


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


@given("the csboost correction service module is imported")
def step_csboost_module_imported(context):
    """Ensure the CorrectionService is importable."""
    assert CorrectionService is not None
    assert CorrectionMode is not None
    assert CorrectionStatus is not None


# ---------------------------------------------------------------------------
# Service creation
# ---------------------------------------------------------------------------


@given("a csboost correction service with a working event bus")
def step_csboost_service_with_working_bus(context):
    """Create a CorrectionService wired to a WorkingEventBus."""
    context.csboost_bus = _WorkingEventBus()
    context.csboost_svc = CorrectionService(event_bus=context.csboost_bus)


@given("a csboost correction service with a failing event bus")
def step_csboost_service_with_failing_bus(context):
    """Create a CorrectionService wired to a FailingEventBus."""
    context.csboost_bus = _FailingEventBus()
    context.csboost_svc = CorrectionService(event_bus=context.csboost_bus)


# ---------------------------------------------------------------------------
# Correction request creation
# ---------------------------------------------------------------------------


@given(
    'a csboost REVERT correction request for plan "{plan_id}" targeting decision "{decision_id}"'
)
def step_csboost_create_revert_request(context, plan_id, decision_id):
    """Create a REVERT correction request."""
    context.csboost_req = context.csboost_svc.request_correction(
        plan_id=plan_id,
        target_decision_id=decision_id,
        mode=CorrectionMode.REVERT,
    )
    context.csboost_cid = context.csboost_req.correction_id


@given(
    'a csboost APPEND correction request for plan "{plan_id}" targeting decision "{decision_id}"'
)
def step_csboost_create_append_request(context, plan_id, decision_id):
    """Create an APPEND correction request."""
    context.csboost_req = context.csboost_svc.request_correction(
        plan_id=plan_id,
        target_decision_id=decision_id,
        mode=CorrectionMode.APPEND,
    )
    context.csboost_cid = context.csboost_req.correction_id


@given('the csboost correction request has guidance "{guidance}"')
def step_csboost_set_guidance(context, guidance):
    """Re-create the correction request with the desired guidance text."""
    old = context.csboost_req
    # Remove the old entry
    del context.csboost_svc._corrections[old.correction_id]
    del context.csboost_svc._attempts[old.correction_id]
    # Create a new one with guidance
    context.csboost_req = context.csboost_svc.request_correction(
        plan_id=old.plan_id,
        target_decision_id=old.target_decision_id,
        mode=old.mode,
        guidance=guidance,
    )
    context.csboost_cid = context.csboost_req.correction_id


@given("the csboost correction has already been executed once")
def step_csboost_execute_first_time(context):
    """Execute the correction once so it moves out of executable state."""
    tree = {context.csboost_req.target_decision_id: []}
    context.csboost_first_result = context.csboost_svc.execute_revert(
        context.csboost_cid, decision_tree=tree
    )
    # Record how many events the bus has after the first execution
    if hasattr(context.csboost_bus, "events"):
        context.csboost_events_after_first = len(context.csboost_bus.events)


# ---------------------------------------------------------------------------
# Execution actions
# ---------------------------------------------------------------------------


@when("I csboost execute the revert correction with a simple decision tree")
def step_csboost_execute_revert(context):
    """Execute the revert correction with a minimal tree."""
    tree = {context.csboost_req.target_decision_id: []}
    context.csboost_result = context.csboost_svc.execute_revert(
        context.csboost_cid, decision_tree=tree
    )
    context.csboost_error = None


@when("I csboost execute the append correction")
def step_csboost_execute_append(context):
    """Execute the append correction."""
    context.csboost_result = context.csboost_svc.execute_append(context.csboost_cid)
    context.csboost_error = None


@when("I csboost attempt to execute the revert correction again")
def step_csboost_execute_revert_again(context):
    """Attempt a second execution which should fail validation."""
    tree = {context.csboost_req.target_decision_id: []}
    try:
        context.csboost_result = context.csboost_svc.execute_revert(
            context.csboost_cid, decision_tree=tree
        )
        context.csboost_error = None
    except Exception as exc:
        context.csboost_result = None
        context.csboost_error = exc


@when("I csboost execute the correction via the dispatch method with a simple tree")
def step_csboost_execute_via_dispatch(context):
    """Execute the correction using execute_correction dispatch."""
    tree = {context.csboost_req.target_decision_id: []}
    context.csboost_result = context.csboost_svc.execute_correction(
        context.csboost_cid, decision_tree=tree
    )
    context.csboost_error = None


# ---------------------------------------------------------------------------
# Assertions: event bus received events
# ---------------------------------------------------------------------------


@then("the csboost event bus should have received exactly {count:d} event")
def step_csboost_bus_received_n(context, count):
    """Assert the working event bus received exactly N events."""
    assert hasattr(context.csboost_bus, "events"), "Expected a _WorkingEventBus"
    actual = len(context.csboost_bus.events)
    assert actual == count, f"Expected {count} event(s), got {actual}"


@then("the csboost emitted event should have type CORRECTION_APPLIED")
def step_csboost_event_type(context):
    """Assert the last emitted event has type CORRECTION_APPLIED."""
    event = context.csboost_bus.events[-1]
    assert event.event_type == EventType.CORRECTION_APPLIED, (
        f"Expected CORRECTION_APPLIED, got {event.event_type}"
    )


@then("the csboost emitted event details should contain the correction id")
def step_csboost_event_has_correction_id(context):
    """Assert the event details include the correction_id."""
    event = context.csboost_bus.events[-1]
    assert event.details["correction_id"] == context.csboost_cid


@then('the csboost emitted event details should contain mode "{mode}"')
def step_csboost_event_has_mode(context, mode):
    """Assert the event details include the expected mode string."""
    event = context.csboost_bus.events[-1]
    assert event.details["mode"] == mode, (
        f"Expected mode '{mode}', got '{event.details['mode']}'"
    )


@then('the csboost emitted event plan_id should be "{plan_id}"')
def step_csboost_event_plan_id(context, plan_id):
    """Assert the event plan_id matches."""
    event = context.csboost_bus.events[-1]
    assert event.plan_id == plan_id, (
        f"Expected plan_id '{plan_id}', got '{event.plan_id}'"
    )


@then('the csboost emitted event details should contain guidance "{guidance}"')
def step_csboost_event_has_guidance(context, guidance):
    """Assert the event details include the guidance text."""
    event = context.csboost_bus.events[-1]
    assert event.details["guidance"] == guidance, (
        f"Expected guidance '{guidance}', got '{event.details.get('guidance')}'"
    )


@then(
    'the csboost emitted event details should contain target_decision_id "{decision_id}"'
)
def step_csboost_event_has_target(context, decision_id):
    """Assert the event details include target_decision_id."""
    event = context.csboost_bus.events[-1]
    assert event.details["target_decision_id"] == decision_id, (
        f"Expected target_decision_id '{decision_id}', "
        f"got '{event.details.get('target_decision_id')}'"
    )


# ---------------------------------------------------------------------------
# Assertions: result status
# ---------------------------------------------------------------------------


@then("the csboost correction result status should be APPLIED")
def step_csboost_result_applied(context):
    """Assert the correction result status is APPLIED."""
    assert context.csboost_result is not None, "No result captured"
    assert context.csboost_result.status == CorrectionStatus.APPLIED, (
        f"Expected APPLIED, got {context.csboost_result.status}"
    )


@then("the csboost correction result status should be FAILED")
def step_csboost_result_failed(context):
    """Assert the correction ended in FAILED status or raised a ValidationError."""
    if context.csboost_result is not None:
        assert context.csboost_result.status == CorrectionStatus.FAILED
    else:
        assert context.csboost_error is not None


# ---------------------------------------------------------------------------
# Assertions: failing bus
# ---------------------------------------------------------------------------


@then("the csboost failing event bus emit should have been called")
def step_csboost_failing_bus_emit_called(context):
    """Assert that the failing event bus had its emit method invoked."""
    assert hasattr(context.csboost_bus, "emit_called"), "Expected a _FailingEventBus"
    assert context.csboost_bus.emit_called is True, "emit() was never called"


@then("csboost no exception should have propagated to the caller")
def step_csboost_no_exception(context):
    """Assert that execute_revert/execute_append did not raise."""
    assert context.csboost_error is None, (
        f"Expected no exception, got {context.csboost_error!r}"
    )


# ---------------------------------------------------------------------------
# Assertions: no new events after second attempt
# ---------------------------------------------------------------------------


@then(
    "the csboost event bus should have received exactly {count:d} new events since the second attempt"
)
def step_csboost_no_new_events(context, count):
    """Assert no additional events were emitted during the failed second attempt."""
    current = len(context.csboost_bus.events)
    events_before = getattr(context, "csboost_events_after_first", 0)
    new_events = current - events_before
    assert new_events == count, (
        f"Expected {count} new event(s) since second attempt, got {new_events}"
    )
