"""Step definitions for features/acms_service_coverage_r3.feature.

Targets uncovered lines in src/cleveragents/application/services/acms_service.py:
- Lines 34-35: TYPE_CHECKING imports (Settings, UnitOfWork) — validated via DI
- Line 112:    ContextStrategy.can_handle protocol default body (Ellipsis)
- Line 120:    ContextStrategy.assemble protocol default body (Ellipsis)
- Line 124:    ContextStrategy.explain protocol default body (Ellipsis)
- Line 275:    StrategySelector.select protocol default body (Ellipsis)
- Line 292:    BudgetAllocator.allocate protocol default body (Ellipsis)
- Line 309:    StrategyExecutor.execute protocol default body (Ellipsis)
- Line 396:    SkeletonCompressor.compress protocol default body (Ellipsis)
- Line 577:    DefaultBudgetPacker.pack returns fragments unchanged
"""

from __future__ import annotations

from unittest.mock import MagicMock

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

from cleveragents.application.services.acms_service import (
    ACMSPipeline,
    BudgetAllocator,
    ContextStrategy,
    DefaultBudgetPacker,
    SkeletonCompressor,
    StrategyCapabilities,
    StrategyExecutor,
    StrategySelector,
)
from cleveragents.domain.models.core.context_fragment import (
    ContextBudget,
    ContextFragment,
    FragmentProvenance,
)

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

_DEFAULT_PROVENANCE = FragmentProvenance(resource_uri="test://coverage-r3")


def _make_frag(
    uko_node: str = "test://r3",
    content: str = "r3 content",
    token_count: int = 10,
    relevance_score: float = 0.5,
) -> ContextFragment:
    return ContextFragment(
        uko_node=uko_node,
        content=content,
        token_count=token_count,
        relevance_score=relevance_score,
        provenance=_DEFAULT_PROVENANCE,
    )


# ---------------------------------------------------------------------------
# Bare Protocol subclasses — inherit default method bodies (the `...` stubs)
# without overriding them, so calling the methods executes the Ellipsis lines.
# ---------------------------------------------------------------------------


class _BareContextStrategy(ContextStrategy):
    """Inherits ContextStrategy Protocol defaults for can_handle/assemble/explain."""

    @property
    def name(self) -> str:
        return "bare_test"

    @property
    def capabilities(self) -> StrategyCapabilities:
        return StrategyCapabilities()


class _BareStrategySelector(StrategySelector):
    """Inherits StrategySelector Protocol default for select."""


class _BareBudgetAllocator(BudgetAllocator):
    """Inherits BudgetAllocator Protocol default for allocate."""


class _BareStrategyExecutor(StrategyExecutor):
    """Inherits StrategyExecutor Protocol default for execute."""


class _BareSkeletonCompressor(SkeletonCompressor):
    """Inherits SkeletonCompressor Protocol default for compress."""


# ---------------------------------------------------------------------------
# Lines 34-35: TYPE_CHECKING imports — DI validation
# ---------------------------------------------------------------------------


@given("ascov3 I have mock Settings and UnitOfWork objects")
def step_given_mock_settings_uow(context: Context) -> None:
    context.ascov3_mock_settings = MagicMock(name="MockSettings")
    context.ascov3_mock_uow = MagicMock(name="MockUnitOfWork")


@when("ascov3 I create a pipeline with settings and unit_of_work injected")
def step_when_create_pipeline_with_di(context: Context) -> None:
    context.ascov3_error = None
    try:
        context.ascov3_pipeline = ACMSPipeline(
            settings=context.ascov3_mock_settings,
            unit_of_work=context.ascov3_mock_uow,
        )
    except Exception as exc:
        context.ascov3_error = exc


@then("ascov3 the pipeline should store the injected dependencies")
def step_then_pipeline_stores_deps(context: Context) -> None:
    assert context.ascov3_error is None, (
        f"Pipeline creation failed: {context.ascov3_error}"
    )
    pipeline = context.ascov3_pipeline
    assert pipeline._settings is context.ascov3_mock_settings, (
        "Pipeline did not store the injected settings"
    )
    assert pipeline._unit_of_work is context.ascov3_mock_uow, (
        "Pipeline did not store the injected unit_of_work"
    )


# ---------------------------------------------------------------------------
# Line 112: ContextStrategy.can_handle default body
# ---------------------------------------------------------------------------


@given("ascov3 I have a bare ContextStrategy subclass")
def step_given_bare_context_strategy(context: Context) -> None:
    context.ascov3_bare_strategy = _BareContextStrategy()


@when("ascov3 I call can_handle on the bare strategy with an empty request")
def step_when_call_can_handle(context: Context) -> None:
    context.ascov3_error = None
    try:
        context.ascov3_can_handle_result = context.ascov3_bare_strategy.can_handle({})
    except Exception as exc:
        context.ascov3_error = exc


@then("ascov3 the can_handle result should be None")
def step_then_can_handle_none(context: Context) -> None:
    assert context.ascov3_error is None, f"can_handle raised: {context.ascov3_error}"
    assert context.ascov3_can_handle_result is None, (
        f"Expected None, got {context.ascov3_can_handle_result!r}"
    )


# ---------------------------------------------------------------------------
# Line 120: ContextStrategy.assemble default body
# ---------------------------------------------------------------------------


@when("ascov3 I call assemble on the bare strategy with empty fragments and a budget")
def step_when_call_assemble(context: Context) -> None:
    context.ascov3_error = None
    try:
        budget = ContextBudget(max_tokens=100, reserved_tokens=0)
        context.ascov3_assemble_result = context.ascov3_bare_strategy.assemble(
            [], budget
        )
    except Exception as exc:
        context.ascov3_error = exc


@then("ascov3 the assemble result should be None")
def step_then_assemble_none(context: Context) -> None:
    assert context.ascov3_error is None, f"assemble raised: {context.ascov3_error}"
    assert context.ascov3_assemble_result is None, (
        f"Expected None, got {context.ascov3_assemble_result!r}"
    )


# ---------------------------------------------------------------------------
# Line 124: ContextStrategy.explain default body
# ---------------------------------------------------------------------------


@when("ascov3 I call explain on the bare strategy")
def step_when_call_explain(context: Context) -> None:
    context.ascov3_error = None
    try:
        context.ascov3_explain_result = context.ascov3_bare_strategy.explain()
    except Exception as exc:
        context.ascov3_error = exc


@then("ascov3 the explain result should be None")
def step_then_explain_none(context: Context) -> None:
    assert context.ascov3_error is None, f"explain raised: {context.ascov3_error}"
    assert context.ascov3_explain_result is None, (
        f"Expected None, got {context.ascov3_explain_result!r}"
    )


# ---------------------------------------------------------------------------
# Line 275: StrategySelector.select default body
# ---------------------------------------------------------------------------


@given("ascov3 I have a bare StrategySelector subclass")
def step_given_bare_selector(context: Context) -> None:
    context.ascov3_bare_selector = _BareStrategySelector()


@when("ascov3 I call select on the bare selector with empty strategies")
def step_when_call_select(context: Context) -> None:
    context.ascov3_error = None
    try:
        context.ascov3_select_result = context.ascov3_bare_selector.select([], {})
    except Exception as exc:
        context.ascov3_error = exc


@then("ascov3 the select result should be None")
def step_then_select_none(context: Context) -> None:
    assert context.ascov3_error is None, f"select raised: {context.ascov3_error}"
    assert context.ascov3_select_result is None, (
        f"Expected None, got {context.ascov3_select_result!r}"
    )


# ---------------------------------------------------------------------------
# Line 292: BudgetAllocator.allocate default body
# ---------------------------------------------------------------------------


@given("ascov3 I have a bare BudgetAllocator subclass")
def step_given_bare_allocator(context: Context) -> None:
    context.ascov3_bare_allocator = _BareBudgetAllocator()


@when("ascov3 I call allocate on the bare allocator with empty candidates")
def step_when_call_allocate(context: Context) -> None:
    context.ascov3_error = None
    try:
        context.ascov3_allocate_result = context.ascov3_bare_allocator.allocate([], 100)
    except Exception as exc:
        context.ascov3_error = exc


@then("ascov3 the allocate result should be None")
def step_then_allocate_none(context: Context) -> None:
    assert context.ascov3_error is None, f"allocate raised: {context.ascov3_error}"
    assert context.ascov3_allocate_result is None, (
        f"Expected None, got {context.ascov3_allocate_result!r}"
    )


# ---------------------------------------------------------------------------
# Line 309: StrategyExecutor.execute default body
# ---------------------------------------------------------------------------


@given("ascov3 I have a bare StrategyExecutor subclass")
def step_given_bare_executor(context: Context) -> None:
    context.ascov3_bare_executor = _BareStrategyExecutor()


@when("ascov3 I call execute on the bare executor with empty allocations")
def step_when_call_execute(context: Context) -> None:
    context.ascov3_error = None
    try:
        budget = ContextBudget(max_tokens=100, reserved_tokens=0)
        context.ascov3_execute_result = context.ascov3_bare_executor.execute(
            [], [], budget
        )
    except Exception as exc:
        context.ascov3_error = exc


@then("ascov3 the execute result should be None")
def step_then_execute_none(context: Context) -> None:
    assert context.ascov3_error is None, f"execute raised: {context.ascov3_error}"
    assert context.ascov3_execute_result is None, (
        f"Expected None, got {context.ascov3_execute_result!r}"
    )


# ---------------------------------------------------------------------------
# Line 396: SkeletonCompressor.compress default body
# ---------------------------------------------------------------------------


@given("ascov3 I have a bare SkeletonCompressor subclass")
def step_given_bare_compressor(context: Context) -> None:
    context.ascov3_bare_compressor = _BareSkeletonCompressor()


@when("ascov3 I call compress on the bare compressor with empty fragments")
def step_when_call_compress(context: Context) -> None:
    context.ascov3_error = None
    try:
        context.ascov3_compress_result = context.ascov3_bare_compressor.compress(
            (), 100
        )
    except Exception as exc:
        context.ascov3_error = exc


@then("ascov3 the compress result should be None")
def step_then_compress_none(context: Context) -> None:
    assert context.ascov3_error is None, f"compress raised: {context.ascov3_error}"
    assert context.ascov3_compress_result is None, (
        f"Expected None, got {context.ascov3_compress_result!r}"
    )


# ---------------------------------------------------------------------------
# Line 577: DefaultBudgetPacker.pack
# ---------------------------------------------------------------------------


@given("ascov3 I have a DefaultBudgetPacker instance")
def step_given_default_packer(context: Context) -> None:
    context.ascov3_packer = DefaultBudgetPacker()


@given("ascov3 I have a list of test fragments")
def step_given_test_fragments(context: Context) -> None:
    context.ascov3_fragments = [
        _make_frag(uko_node="test://r3-a", content="alpha", token_count=10),
        _make_frag(uko_node="test://r3-b", content="beta", token_count=20),
    ]


@when("ascov3 I call pack on the packer with those fragments and a budget")
def step_when_call_pack_with_fragments(context: Context) -> None:
    context.ascov3_error = None
    try:
        budget = ContextBudget(max_tokens=200, reserved_tokens=0)
        context.ascov3_pack_result = context.ascov3_packer.pack(
            context.ascov3_fragments, budget
        )
    except Exception as exc:
        context.ascov3_error = exc


@when("ascov3 I call pack on the packer with empty fragments and a budget")
def step_when_call_pack_empty(context: Context) -> None:
    context.ascov3_error = None
    try:
        budget = ContextBudget(max_tokens=200, reserved_tokens=0)
        context.ascov3_pack_result = context.ascov3_packer.pack([], budget)
    except Exception as exc:
        context.ascov3_error = exc


@then("ascov3 the pack result should be the same fragments unchanged")
def step_then_pack_result_unchanged(context: Context) -> None:
    assert context.ascov3_error is None, f"pack raised: {context.ascov3_error}"
    result = list(context.ascov3_pack_result)
    expected = context.ascov3_fragments
    assert len(result) == len(expected), (
        f"Expected {len(expected)} fragments, got {len(result)}"
    )
    for i, (r, e) in enumerate(zip(result, expected, strict=True)):
        assert r.fragment_id == e.fragment_id, (
            f"Fragment {i}: expected id {e.fragment_id}, got {r.fragment_id}"
        )


@then("ascov3 the pack result should be an empty sequence")
def step_then_pack_result_empty(context: Context) -> None:
    assert context.ascov3_error is None, f"pack raised: {context.ascov3_error}"
    result = list(context.ascov3_pack_result)
    assert result == [], f"Expected empty list, got {result}"
