"""Step definitions for strategy_coordinator_coverage.feature.

These steps target specific uncovered lines in strategy_coordinator.py:
- Lines 147, 152, 157, 162: Property accessors (config, selector, allocator, executor)
- Line 192: fragments=None default in coordinate()
- Line 198: backends enrichment in coordinate()
- Line 200: plan_context enrichment in coordinate()
- Lines 291-294: Zero-confidence excess redistribution in _apply_max_caps
- Lines 328, 331: _extract_strategies_used with strategy_source metadata
"""

from collections.abc import Sequence
from dataclasses import dataclass

from behave import given, then, when

from cleveragents.application.services.acms_pipeline import (
    CircuitBreaker,
    ConfidenceWeightedSelector,
    ParallelStrategyExecutor,
    ProportionalBudgetAllocator,
)
from cleveragents.application.services.acms_service import (
    StrategyCapabilities,
)
from cleveragents.application.services.strategy_coordinator import (
    CoordinationResult,
    CoordinatorConfig,
    StrategyCoordinator,
)
from cleveragents.domain.models.core.context_fragment import (
    ContextBudget,
    ContextFragment,
    FragmentProvenance,
)

# ---------------------------------------------------------------------------
# Helper: minimal ContextStrategy implementation
# ---------------------------------------------------------------------------

_DEFAULT_PROVENANCE = FragmentProvenance(
    resource_uri="test://coverage",
    location="line 1",
    resource_type="test",
)

_DEFAULT_CAPABILITIES = StrategyCapabilities()


@dataclass(frozen=True)
class _StubStrategy:
    """Minimal ContextStrategy implementation for testing."""

    _name: str
    _confidence: float = 0.8
    _capabilities: StrategyCapabilities = _DEFAULT_CAPABILITIES
    _strategy_source: str = ""

    @property
    def name(self) -> str:
        return self._name

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

    def can_handle(self, request: dict) -> float:
        return self._confidence

    def assemble(
        self,
        fragments: Sequence[ContextFragment],
        budget: ContextBudget,
    ) -> Sequence[ContextFragment]:
        return [
            ContextFragment(
                uko_node="uko://test/stub",
                content="stub fragment",
                token_count=10,
                source_type="test",
                provenance=_DEFAULT_PROVENANCE,
                strategy_source=self._strategy_source,
            )
        ]

    def explain(self) -> str:
        return f"Stub strategy: {self._name}"


# ---------------------------------------------------------------------------
# Helper: request-capturing selector
# ---------------------------------------------------------------------------


class _CapturingSelector:
    """A selector that captures the enriched request for inspection."""

    def __init__(self) -> None:
        self.captured_request: dict | None = None
        self._preference_boost = 1.5

    def select(self, strategies, request):
        self.captured_request = dict(request)
        # Return all strategies with confidence 0.8
        return [(s, 0.8) for s in strategies]


# ---------------------------------------------------------------------------
# Helper: passthrough executor
# ---------------------------------------------------------------------------


class _PassthroughExecutor:
    """An executor that directly calls each strategy's assemble method."""

    def __init__(self) -> None:
        self._circuit_breaker = CircuitBreaker()

    @property
    def circuit_breaker(self) -> CircuitBreaker:
        return self._circuit_breaker

    def execute(self, allocations, fragments, budget):
        result = []
        for strategy, _confidence, allocated_tokens in allocations:
            scoped_budget = ContextBudget(
                max_tokens=max(allocated_tokens, 2),
                reserved_tokens=0,
            )
            result.extend(strategy.assemble(fragments, scoped_budget))
        return result


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


@given("the strategy coordinator module is imported")
def step_coordinator_module_imported(context):
    """Ensure the module is importable."""
    assert StrategyCoordinator is not None
    assert CoordinatorConfig is not None
    assert CoordinationResult is not None


# ---------------------------------------------------------------------------
# Property accessors (lines 147, 152, 157, 162)
# ---------------------------------------------------------------------------


@given("a strategy coordinator with default configuration")
def step_create_default_coordinator(context):
    """Create a StrategyCoordinator with default configuration."""
    context.coordinator = StrategyCoordinator()


@when("I access the config property")
def step_access_config_property(context):
    """Access the config property."""
    context.accessed_config = context.coordinator.config


@then("the config property returns a CoordinatorConfig instance")
def step_verify_config_type(context):
    """Verify config is a CoordinatorConfig."""
    assert isinstance(context.accessed_config, CoordinatorConfig)


@when("I access the selector property")
def step_access_selector_property(context):
    """Access the selector property."""
    context.accessed_selector = context.coordinator.selector


@then("the selector property returns a ConfidenceWeightedSelector instance")
def step_verify_selector_type(context):
    """Verify selector is a ConfidenceWeightedSelector."""
    assert isinstance(context.accessed_selector, ConfidenceWeightedSelector)


@when("I access the allocator property")
def step_access_allocator_property(context):
    """Access the allocator property."""
    context.accessed_allocator = context.coordinator.allocator


@then("the allocator property returns a ProportionalBudgetAllocator instance")
def step_verify_allocator_type(context):
    """Verify allocator is a ProportionalBudgetAllocator."""
    assert isinstance(context.accessed_allocator, ProportionalBudgetAllocator)


@when("I access the executor property")
def step_access_executor_property(context):
    """Access the executor property."""
    context.accessed_executor = context.coordinator.executor


@then("the executor property returns a ParallelStrategyExecutor instance")
def step_verify_executor_type(context):
    """Verify executor is a ParallelStrategyExecutor."""
    assert isinstance(context.accessed_executor, ParallelStrategyExecutor)


# ---------------------------------------------------------------------------
# coordinate() with fragments=None default (line 192)
# ---------------------------------------------------------------------------


@given("a strategy coordinator with mock pipeline components")
def step_create_coordinator_with_mocks(context):
    """Create a StrategyCoordinator with mock selector, allocator, executor."""
    context.mock_executor = _PassthroughExecutor()
    context.coordinator = StrategyCoordinator(
        selector=ConfidenceWeightedSelector(),
        allocator=ProportionalBudgetAllocator(min_useful_budget=1),
        executor=context.mock_executor,
    )


@given("a mock strategy that returns fragments without strategy_source")
def step_create_mock_strategy_no_source(context):
    """Create a stub strategy that returns fragments without strategy_source."""
    context.test_strategy = _StubStrategy(
        _name="test_strategy",
        _confidence=0.9,
        _strategy_source="",
    )


@when("I call coordinate without passing fragments")
def step_call_coordinate_no_fragments(context):
    """Call coordinate() without fragments to hit line 192 (fragments=None default)."""
    budget = ContextBudget(max_tokens=4096, reserved_tokens=0)
    context.coord_result = context.coordinator.coordinate(
        request={"preferred_strategies": []},
        strategies=[context.test_strategy],
        budget=budget,
        # fragments parameter intentionally omitted → hits line 192
    )


@then("the coordination should succeed")
def step_verify_coordination_success(context):
    """Verify the coordination completed without errors."""
    assert isinstance(context.coord_result, CoordinationResult)


@then("the result should contain fragments from the mock strategy")
def step_verify_fragments_returned(context):
    """Verify fragments were returned."""
    assert len(context.coord_result.fragments) > 0


# ---------------------------------------------------------------------------
# coordinate() with backends (line 198)
# ---------------------------------------------------------------------------


@given("a strategy coordinator with a request-capturing selector")
def step_create_coordinator_with_capturing_selector(context):
    """Create a coordinator whose selector captures the enriched request."""
    context.capturing_selector = _CapturingSelector()
    context.mock_executor = _PassthroughExecutor()
    context.coordinator = StrategyCoordinator(
        selector=context.capturing_selector,
        allocator=ProportionalBudgetAllocator(min_useful_budget=1),
        executor=context.mock_executor,
    )


@when("I call coordinate with backends parameter")
def step_call_coordinate_with_backends(context):
    """Call coordinate() with a backends dict to hit line 198."""
    budget = ContextBudget(max_tokens=4096, reserved_tokens=0)
    context.coord_result = context.coordinator.coordinate(
        request={"preferred_strategies": []},
        strategies=[context.test_strategy],
        budget=budget,
        backends={"llm": "openai", "embedding": "local"},
    )


@then("the enriched request should contain a backends key")
def step_verify_backends_enrichment(context):
    """Verify the selector received a request with 'backends' key."""
    captured = context.capturing_selector.captured_request
    assert captured is not None, "Selector should have captured the request"
    assert "backends" in captured, (
        f"Expected 'backends' in request, got keys: {list(captured.keys())}"
    )
    assert captured["backends"]["llm"] == "openai"


# ---------------------------------------------------------------------------
# coordinate() with plan_context (line 200)
# ---------------------------------------------------------------------------


@when("I call coordinate with plan_context parameter")
def step_call_coordinate_with_plan_context(context):
    """Call coordinate() with a plan_context dict to hit line 200."""
    budget = ContextBudget(max_tokens=4096, reserved_tokens=0)
    context.coord_result = context.coordinator.coordinate(
        request={"preferred_strategies": []},
        strategies=[context.test_strategy],
        budget=budget,
        plan_context={"goal": "refactor", "phase": "analysis"},
    )


@then("the enriched request should contain a plan_context key")
def step_verify_plan_context_enrichment(context):
    """Verify the selector received a request with 'plan_context' key."""
    captured = context.capturing_selector.captured_request
    assert captured is not None, "Selector should have captured the request"
    assert "plan_context" in captured, (
        f"Expected 'plan_context' in request, got keys: {list(captured.keys())}"
    )
    assert captured["plan_context"]["goal"] == "refactor"


# ---------------------------------------------------------------------------
# _apply_max_caps: zero-confidence redistribution (lines 291-294)
# ---------------------------------------------------------------------------


@given("a strategy coordinator with per_strategy_max_cap of {cap:d}")
def step_create_coordinator_with_cap(context, cap):
    """Create a coordinator with a per-strategy max cap."""
    config = CoordinatorConfig(per_strategy_max_cap=cap)
    context.coordinator = StrategyCoordinator(config=config)


@given(
    "allocations with one capped strategy and two zero-confidence uncapped strategies"
)
def step_create_zero_confidence_allocations(context):
    """Build allocations where:

    - Strategy A: 800 tokens (exceeds cap of 500) → generates 300 excess
    - Strategy B: confidence=0.0, 200 tokens (below cap)
    - Strategy C: confidence=0.0, 100 tokens (below cap)

    Since total_conf of uncapped strategies is 0, the else branch (lines 291-294)
    fires and divides excess equally.
    """
    context.test_allocations = [
        (_StubStrategy(_name="strategy_a", _confidence=0.9), 0.9, 800),
        (_StubStrategy(_name="strategy_b", _confidence=0.0), 0.0, 200),
        (_StubStrategy(_name="strategy_c", _confidence=0.0), 0.0, 100),
    ]


@when("I apply max caps to the allocations")
def step_apply_max_caps(context):
    """Call _apply_max_caps to exercise the zero-confidence redistribution."""
    context.capped_result = context.coordinator._apply_max_caps(
        context.test_allocations
    )


@then("the excess tokens should be equally redistributed among uncapped strategies")
def step_verify_equal_redistribution(context):
    """Verify the excess (300) is split equally among the 2 uncapped strategies.

    excess = 800 - 500 = 300
    share = 300 // 2 = 150
    Strategy A: capped at 500
    Strategy B: min(200 + 150, 500) = 350
    Strategy C: min(100 + 150, 500) = 250
    """
    result = context.capped_result
    assert len(result) == 3

    # Strategy A should be capped at 500
    assert result[0][2] == 500, f"Expected strategy_a at 500, got {result[0][2]}"

    # Strategy B: 200 + 150 = 350
    assert result[1][2] == 350, f"Expected strategy_b at 350, got {result[1][2]}"

    # Strategy C: 100 + 150 = 250
    assert result[2][2] == 250, f"Expected strategy_c at 250, got {result[2][2]}"


# ---------------------------------------------------------------------------
# _extract_strategies_used with strategy_source (lines 328, 331)
# ---------------------------------------------------------------------------


@given("a mock strategy that returns fragments with strategy_source set")
def step_create_mock_strategy_with_source(context):
    """Create a stub strategy that returns fragments with strategy_source."""
    context.test_strategy = _StubStrategy(
        _name="sourced_strategy",
        _confidence=0.9,
        _strategy_source="relevance",
    )


@then("the strategies_used should be derived from fragment strategy_source")
def step_verify_strategies_used_from_source(context):
    """Verify strategies_used comes from fragment strategy_source, not allocations."""
    result = context.coord_result
    assert "relevance" in result.strategies_used, (
        f"Expected 'relevance' in strategies_used, got {result.strategies_used}"
    )
