"""Step definitions for ``features/context_strategies.feature``.

Covers the first batch of built-in context strategies:

* **SimpleKeywordStrategy** — keyword matching / word-density fallback
* **SemanticEmbeddingStrategy** — Jaccard word-overlap similarity
* **BreadthDepthNavigatorStrategy** — UKO hierarchy navigation

Also covers pipeline registration of all three strategies.
"""

from __future__ import annotations

from typing import Any

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

from cleveragents.application.services.acms_service import ACMSPipeline
from cleveragents.application.services.context_strategies import (
    BreadthDepthNavigatorStrategy,
    SemanticEmbeddingStrategy,
    SimpleKeywordStrategy,
)
from cleveragents.domain.models.core.context_fragment import (
    ContextBudget,
    ContextFragment,
    FragmentProvenance,
)

__all__: list[str] = []


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

_DEFAULT_PROVENANCE = FragmentProvenance(resource_uri="test://default")


def _make_fragment(
    uko_node: str,
    content: str,
    score: float,
    tokens: int,
    depth: int,
) -> ContextFragment:
    """Build a ``ContextFragment`` for test purposes."""
    return ContextFragment(
        uko_node=uko_node,
        content=content,
        relevance_score=score,
        token_count=tokens,
        detail_depth=depth,
        provenance=FragmentProvenance(resource_uri=uko_node),
    )


# ---------------------------------------------------------------------------
# Given steps
# ---------------------------------------------------------------------------


@given('a SimpleKeywordStrategy with query "{query}"')
def step_simple_keyword_with_query(context: Context, query: str) -> None:
    strategy = SimpleKeywordStrategy()
    strategy.set_query(query)
    context.strategy = strategy


@given("a SimpleKeywordStrategy without query")
def step_simple_keyword_no_query(context: Context) -> None:
    context.strategy = SimpleKeywordStrategy()


@given('a SemanticEmbeddingStrategy with query "{query}"')
def step_semantic_embedding_with_query(context: Context, query: str) -> None:
    strategy = SemanticEmbeddingStrategy()
    strategy.set_query(query)
    context.strategy = strategy


@given("a SemanticEmbeddingStrategy without query")
def step_semantic_embedding_no_query(context: Context) -> None:
    context.strategy = SemanticEmbeddingStrategy()


@given('a BreadthDepthNavigatorStrategy with focus "{focus}"')
def step_breadth_depth_with_focus(context: Context, focus: str) -> None:
    strategy = BreadthDepthNavigatorStrategy()
    strategy.set_focus([focus])
    context.strategy = strategy


@given("a BreadthDepthNavigatorStrategy without focus")
def step_breadth_depth_no_focus(context: Context) -> None:
    context.strategy = BreadthDepthNavigatorStrategy()


@given("the following strategy fragments:")
def step_strategy_fragments_table(context: Context) -> None:
    context.strategy_fragments = []
    for row in context.table:
        frag = _make_fragment(
            uko_node=row["uko_node"],
            content=row["content"],
            score=float(row["score"]),
            tokens=int(row["tokens"]),
            depth=int(row["depth"]),
        )
        context.strategy_fragments.append(frag)


@given("an empty strategy fragment list")
def step_empty_strategy_fragments(context: Context) -> None:
    context.strategy_fragments = []


@given(
    "a strategy budget with max_tokens {max_tokens:d} and reserved_tokens {reserved:d}"
)
def step_strategy_budget(context: Context, max_tokens: int, reserved: int) -> None:
    context.strategy_budget = ContextBudget(
        max_tokens=max_tokens, reserved_tokens=reserved
    )


@given("an ACMS pipeline for strategy tests")
def step_pipeline_for_strategies(context: Context) -> None:
    context.pipeline = ACMSPipeline()


# ---------------------------------------------------------------------------
# When steps
# ---------------------------------------------------------------------------


@when("I assemble with the SimpleKeywordStrategy")
def step_assemble_simple_keyword(context: Context) -> None:
    context.strategy_result = list(
        context.strategy.assemble(context.strategy_fragments, context.strategy_budget)
    )


@when("I assemble with the SemanticEmbeddingStrategy")
def step_assemble_semantic_embedding(context: Context) -> None:
    context.strategy_result = list(
        context.strategy.assemble(context.strategy_fragments, context.strategy_budget)
    )


@when("I assemble with the BreadthDepthNavigatorStrategy")
def step_assemble_breadth_depth(context: Context) -> None:
    context.strategy_result = list(
        context.strategy.assemble(context.strategy_fragments, context.strategy_budget)
    )


@when('I check can_handle on SimpleKeywordStrategy with query "{query}"')
def step_can_handle_simple_keyword(context: Context, query: str) -> None:
    request: dict[str, Any] = {"query": query}
    context.confidence = context.strategy.can_handle(request)


@when('I check can_handle on SemanticEmbeddingStrategy with query "{query}"')
def step_can_handle_semantic_with_query(context: Context, query: str) -> None:
    request: dict[str, Any] = {"query": query}
    context.confidence = context.strategy.can_handle(request)


@when("I check can_handle on SemanticEmbeddingStrategy without query")
def step_can_handle_semantic_no_query(context: Context) -> None:
    request: dict[str, Any] = {}
    context.confidence = context.strategy.can_handle(request)


@when("I check can_handle on BreadthDepthNavigator with focus")
def step_can_handle_breadth_depth_with_focus(context: Context) -> None:
    request: dict[str, Any] = {"focus": ["project://app/io.py"]}
    context.confidence = context.strategy.can_handle(request)


@when("I check can_handle on BreadthDepthNavigator with string focus")
def step_can_handle_breadth_depth_string_focus(context: Context) -> None:
    request: dict[str, Any] = {"focus": "project://app/io.py"}
    context.confidence = context.strategy.can_handle(request)


@when("I check can_handle on BreadthDepthNavigator without focus")
def step_can_handle_breadth_depth_no_focus(context: Context) -> None:
    request: dict[str, Any] = {}
    context.confidence = context.strategy.can_handle(request)


@when("I register SimpleKeywordStrategy with the pipeline")
def step_register_simple_keyword(context: Context) -> None:
    context.pipeline.register_strategy("simple-keyword", SimpleKeywordStrategy())


@when("I register all batch 1 strategies with the pipeline")
def step_register_all_batch1(context: Context) -> None:
    context.pipeline.register_strategy("simple-keyword", SimpleKeywordStrategy())
    context.pipeline.register_strategy(
        "semantic-embedding", SemanticEmbeddingStrategy()
    )
    context.pipeline.register_strategy(
        "breadth-depth-navigator", BreadthDepthNavigatorStrategy()
    )


# ---------------------------------------------------------------------------
# Then steps
# ---------------------------------------------------------------------------


@then('the first result fragment should have uko_node "{uko_node}"')
def step_first_result_uko_node(context: Context, uko_node: str) -> None:
    assert len(context.strategy_result) > 0, "Expected at least one result fragment"
    actual = context.strategy_result[0].uko_node
    assert actual == uko_node, (
        f"Expected first fragment uko_node '{uko_node}', got '{actual}'"
    )


@then("{count:d} fragments should be returned by strategy")
def step_fragment_count(context: Context, count: int) -> None:
    actual = len(context.strategy_result)
    assert actual == count, f"Expected {count} fragments, got {actual}"


@then("the strategy confidence should be {expected:g}")
def step_strategy_confidence(context: Context, expected: float) -> None:
    actual = context.confidence
    assert abs(actual - expected) < 1e-6, (
        f"Expected strategy confidence {expected}, got {actual}"
    )


@then("the SimpleKeywordStrategy should not support semantic search")
def step_simple_keyword_no_semantic(context: Context) -> None:
    caps = context.strategy.capabilities
    assert not caps.supports_semantic_search, (
        "SimpleKeywordStrategy should NOT support semantic search"
    )


@then('the SimpleKeywordStrategy name should be "{name}"')
def step_simple_keyword_name(context: Context, name: str) -> None:
    actual = context.strategy.name
    assert actual == name, f"Expected name '{name}', got '{actual}'"


@then("the SemanticEmbeddingStrategy should support semantic search")
def step_semantic_supports_search(context: Context) -> None:
    caps = context.strategy.capabilities
    assert caps.supports_semantic_search, (
        "SemanticEmbeddingStrategy should support semantic search"
    )


@then('the SemanticEmbeddingStrategy name should be "{name}"')
def step_semantic_name(context: Context, name: str) -> None:
    actual = context.strategy.name
    assert actual == name, f"Expected name '{name}', got '{actual}'"


@then("the BreadthDepthNavigatorStrategy should support graph navigation")
def step_breadth_depth_supports_graph(context: Context) -> None:
    caps = context.strategy.capabilities
    assert caps.supports_graph_navigation, (
        "BreadthDepthNavigatorStrategy should support graph navigation"
    )


@then('the BreadthDepthNavigatorStrategy name should be "{name}"')
def step_breadth_depth_name(context: Context, name: str) -> None:
    actual = context.strategy.name
    assert actual == name, f"Expected name '{name}', got '{actual}'"


@then('the SimpleKeywordStrategy explain should contain "{text}"')
def step_simple_keyword_explain(context: Context, text: str) -> None:
    explanation = context.strategy.explain()
    assert text.lower() in explanation.lower(), (
        f"Expected explain to contain '{text}', got: {explanation}"
    )


@then('the SemanticEmbeddingStrategy explain should contain "{text}"')
def step_semantic_explain(context: Context, text: str) -> None:
    explanation = context.strategy.explain()
    assert text.lower() in explanation.lower(), (
        f"Expected explain to contain '{text}', got: {explanation}"
    )


@then('the BreadthDepthNavigatorStrategy explain should contain "{text}"')
def step_breadth_depth_explain(context: Context, text: str) -> None:
    explanation = context.strategy.explain()
    assert text.lower() in explanation.lower(), (
        f"Expected explain to contain '{text}', got: {explanation}"
    )


@then('the pipeline should have strategy "{name}"')
def step_pipeline_has_strategy(context: Context, name: str) -> None:
    strategies = context.pipeline.BUILTIN_STRATEGIES
    registered = context.pipeline._strategies
    has_builtin = name in strategies
    has_registered = name in registered
    assert has_builtin or has_registered, (
        f"Pipeline does not have strategy '{name}'. "
        f"Builtins: {list(strategies.keys())}, Registered: {list(registered.keys())}"
    )
