"""Robot Framework helper for ACMS pipeline Phase 2 integration tests.

Provides a CLI-style interface for Robot to invoke Phase 2 pipeline
components and verify the results.  Exit code 0 = success, 1 = failure.

Usage:
    python robot/helper_acms_pipeline_phase2.py dedup-basic
    python robot/helper_acms_pipeline_phase2.py depth-resolve
    python robot/helper_acms_pipeline_phase2.py scorer-default
    python robot/helper_acms_pipeline_phase2.py packer-budget
    python robot/helper_acms_pipeline_phase2.py scored-fragment
    python robot/helper_acms_pipeline_phase2.py pipeline-integration
"""

from __future__ import annotations

import sys
from collections.abc import Callable
from pathlib import Path

_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
    sys.path.insert(0, _SRC)

from cleveragents.application.services.acms_phase2 import (  # noqa: E402
    ContentHashDeduplicator,
    GreedyKnapsackPacker,
    MaxDepthResolver,
    WeightedCompositeScorer,
)
from cleveragents.application.services.acms_service import ACMSPipeline  # noqa: E402
from cleveragents.domain.models.core.context_fragment import (  # noqa: E402
    ContextBudget,
    ContextFragment,
    FragmentProvenance,
    ScoredFragment,
)

_DEFAULT_PROV = FragmentProvenance(resource_uri="test://robot-phase2")
_PLAN_ID = "01JQTESTPN00000000000000CC"


def _frag(
    uko_node: str = "test://default",
    content: str = "test content",
    score: float = 0.5,
    tokens: int = 10,
    depth: int = 0,
) -> ContextFragment:
    return ContextFragment(
        uko_node=uko_node,
        content=content,
        relevance_score=score,
        token_count=tokens,
        detail_depth=depth,
        provenance=_DEFAULT_PROV,
    )


def _cmd_dedup_basic() -> int:
    """Deduplicate identical fragments and keep highest scored."""
    dedup = ContentHashDeduplicator()
    fragments = [
        _frag(uko_node="project://a.py", content="hello", score=0.5),
        _frag(uko_node="project://a.py", content="hello", score=0.9),
        _frag(uko_node="project://b.py", content="hello", score=0.7),
    ]
    result = dedup.deduplicate(fragments)
    assert len(result) == 2, f"Expected 2, got {len(result)}"
    # Find the fragment for a.py — should be the higher-scored one
    a_frags = [f for f in result if f.uko_node == "project://a.py"]
    assert len(a_frags) == 1
    assert a_frags[0].relevance_score == 0.9
    print("phase2-dedup-ok")
    return 0


def _cmd_depth_resolve() -> int:
    """Resolve depth conflicts to max depth per node."""
    resolver = MaxDepthResolver()
    fragments = [
        _frag(uko_node="project://a.py", content="brief", depth=2, tokens=10),
        _frag(uko_node="project://a.py", content="full", depth=9, tokens=100),
        _frag(uko_node="project://b.py", content="b", depth=5, tokens=50),
    ]
    result = resolver.resolve(fragments)
    assert len(result) == 2, f"Expected 2, got {len(result)}"
    a_frags = [f for f in result if f.uko_node == "project://a.py"]
    assert a_frags[0].detail_depth == 9
    print("phase2-depth-ok")
    return 0


def _cmd_scorer_default() -> int:
    """Score fragments with default weights."""
    scorer = WeightedCompositeScorer()
    fragments = [
        _frag(uko_node="project://a.py", content="hello", score=0.8),
        _frag(uko_node="project://b.py", content="world", score=0.4),
    ]
    result = scorer.score(fragments)
    assert len(result) == 2
    for f in result:
        assert 0.0 <= f.relevance_score <= 1.0
        assert "_original_relevance" in f.metadata
    print("phase2-scorer-ok")
    return 0


def _cmd_packer_budget() -> int:
    """Pack fragments into budget."""
    packer = GreedyKnapsackPacker()
    fragments = [
        _frag(uko_node="project://a.py", content="alpha", score=0.9, tokens=100),
        _frag(uko_node="project://b.py", content="beta", score=0.7, tokens=100),
        _frag(uko_node="project://c.py", content="gamma", score=0.5, tokens=100),
    ]
    budget = ContextBudget(max_tokens=250, reserved_tokens=0)
    result = packer.pack(fragments, budget)
    assert len(result) == 2, f"Expected 2, got {len(result)}"
    total = sum(f.token_count for f in result)
    assert total <= 250, f"Total {total} exceeds budget 250"
    print("phase2-packer-ok")
    return 0


def _cmd_scored_fragment() -> int:
    """Create and verify ScoredFragment model."""
    frag = _frag(uko_node="project://a.py", content="test", score=0.8)
    scored = ScoredFragment(
        fragment=frag,
        composite_score=0.85,
        score_components={"relevance": 0.9, "hierarchy": 0.8},
    )
    assert scored.composite_score == 0.85
    assert scored.score_components["relevance"] == 0.9
    assert scored.fragment is frag
    print("phase2-scored-fragment-ok")
    return 0


def _cmd_pipeline_integration() -> int:
    """Inject Phase 2 components into ACMSPipeline."""
    pipeline = ACMSPipeline(
        deduplicator=ContentHashDeduplicator(),
        depth_resolver=MaxDepthResolver(),
        scorer=WeightedCompositeScorer(),
        packer=GreedyKnapsackPacker(),
    )
    fragments = [
        _frag(uko_node="project://a.py", content="hello", score=0.9, tokens=50),
        _frag(uko_node="project://a.py", content="hello", score=0.5, tokens=50),
        _frag(uko_node="project://b.py", content="world", score=0.7, tokens=50),
    ]
    budget = ContextBudget(max_tokens=200, reserved_tokens=0)
    payload = pipeline.assemble(
        plan_id=_PLAN_ID,
        fragments=fragments,
        budget=budget,
    )
    assert len(payload.fragments) >= 1
    assert payload.total_tokens <= 200
    print("phase2-pipeline-ok")
    return 0


_COMMANDS: dict[str, Callable[[], int]] = {
    "dedup-basic": _cmd_dedup_basic,
    "depth-resolve": _cmd_depth_resolve,
    "scorer-default": _cmd_scorer_default,
    "packer-budget": _cmd_packer_budget,
    "scored-fragment": _cmd_scored_fragment,
    "pipeline-integration": _cmd_pipeline_integration,
}


if __name__ == "__main__":
    if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS:
        print(f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>", file=sys.stderr)
        sys.exit(1)
    sys.exit(_COMMANDS[sys.argv[1]]())
