"""Helper script for Robot Framework plan correct tree wiring tests.

Verifies that ``correct_decision()`` passes ``decision_tree`` and
``influence_edges`` to ``CorrectionService.analyze_impact()`` and
``CorrectionService.execute_correction()`` when invoked via the CLI.

Fixes: https://git.cleverthis.com/cleveragents/cleveragents-core/issues/606
"""

from __future__ import annotations

import sys
from collections.abc import Callable
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import MagicMock, patch

# Ensure src is importable when run from workspace root
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))

from typer.testing import CliRunner

from cleveragents.cli.commands.plan import app as plan_app

runner = CliRunner()

_PATCH_CONTAINER = "cleveragents.application.container.get_container"
_PATCH_CORRECTION_SVC = (
    "cleveragents.application.services.correction_service.CorrectionService"
)

_PLAN_ID = "01ROBOT_PLAN_ID_000000000001"
_ROOT_ID = "DEC-ROOT-R01"
_CHILD_A = "DEC-CHILD-RA"
_CHILD_B = "DEC-CHILD-RB"
_LEAF_ID = "DEC-LEAF-R01"


def _make_decision(decision_id: str, parent_decision_id: str | None) -> SimpleNamespace:
    """Create a minimal decision-like namespace."""
    return SimpleNamespace(
        decision_id=decision_id,
        parent_decision_id=parent_decision_id,
    )


def _build_container(
    decisions: list[SimpleNamespace],
    influence_edges: dict[str, list[str]],
) -> MagicMock:
    """Build a mock DI container returning a mock DecisionService."""
    mock_decision_svc = MagicMock()
    mock_decision_svc.list_decisions.return_value = decisions
    mock_decision_svc.get_influence_edges.return_value = influence_edges
    mock_container = MagicMock()
    mock_container.decision_service.return_value = mock_decision_svc
    return mock_container


def _build_correction_svc(
    *,
    analyze_affected: list[str] | None = None,
    execute_affected: list[str] | None = None,
) -> MagicMock:
    """Build a mock CorrectionService."""
    svc = MagicMock()
    svc.request_correction.return_value = SimpleNamespace(
        correction_id="CORR-ROBOT-01",
        mode=SimpleNamespace(value="revert"),
        target_decision_id=_ROOT_ID,
        guidance="Fix this",
    )
    if analyze_affected is not None:
        svc.analyze_impact.return_value = SimpleNamespace(
            affected_decisions=analyze_affected,
            affected_files=[f"{d}.py" for d in analyze_affected],
            estimated_cost=float(len(analyze_affected)) * 1.5,
            risk_level="low",
        )
    if execute_affected is not None:
        svc.execute_correction.return_value = SimpleNamespace(
            correction_id="CORR-ROBOT-01",
            status=SimpleNamespace(value="applied"),
            reverted_decisions=execute_affected,
            new_decisions=[],
        )
    return svc


# ---------------------------------------------------------------------------
# Test functions
# ---------------------------------------------------------------------------


def _test_dry_run_tree() -> None:
    """Verify analyze_impact receives decision_tree and influence_edges."""
    decisions = [
        _make_decision(_ROOT_ID, None),
        _make_decision(_CHILD_A, _ROOT_ID),
        _make_decision(_CHILD_B, _ROOT_ID),
    ]
    influence_edges: dict[str, list[str]] = {_ROOT_ID: [_CHILD_A]}
    container = _build_container(decisions, influence_edges)
    correction_svc = _build_correction_svc(
        analyze_affected=[_ROOT_ID, _CHILD_A, _CHILD_B],
    )

    container.correction_service.return_value = correction_svc

    args = [
        "correct",
        _ROOT_ID,
        "--mode",
        "revert",
        "--guidance",
        "Fix this",
        "--plan",
        _PLAN_ID,
        "--dry-run",
    ]
    with patch(_PATCH_CONTAINER, return_value=container):
        result = runner.invoke(plan_app, args)

    assert result.exit_code == 0, f"Exit {result.exit_code}: {result.output}"
    correction_svc.analyze_impact.assert_called_once()
    kw = correction_svc.analyze_impact.call_args.kwargs
    assert "decision_tree" in kw, f"Missing decision_tree: {kw}"
    assert "influence_edges" in kw, f"Missing influence_edges: {kw}"
    assert _ROOT_ID in kw["decision_tree"]
    print("plan-correct-tree-wiring-dry-run-ok")


def _test_execute_tree() -> None:
    """Verify execute_correction receives decision_tree and influence_edges."""
    decisions = [
        _make_decision(_ROOT_ID, None),
        _make_decision(_CHILD_A, _ROOT_ID),
        _make_decision(_CHILD_B, _ROOT_ID),
    ]
    influence_edges: dict[str, list[str]] = {_ROOT_ID: [_CHILD_A]}
    container = _build_container(decisions, influence_edges)
    correction_svc = _build_correction_svc(
        execute_affected=[_ROOT_ID, _CHILD_A, _CHILD_B],
    )

    container.correction_service.return_value = correction_svc

    args = [
        "correct",
        _ROOT_ID,
        "--mode",
        "revert",
        "--guidance",
        "Fix this",
        "--plan",
        _PLAN_ID,
        "--yes",
    ]
    with patch(_PATCH_CONTAINER, return_value=container):
        result = runner.invoke(plan_app, args)

    assert result.exit_code == 0, f"Exit {result.exit_code}: {result.output}"
    correction_svc.execute_correction.assert_called_once()
    kw = correction_svc.execute_correction.call_args.kwargs
    assert "decision_tree" in kw, f"Missing decision_tree: {kw}"
    assert "influence_edges" in kw, f"Missing influence_edges: {kw}"
    print("plan-correct-tree-wiring-execute-ok")


def _test_leaf_empty() -> None:
    """Verify a leaf decision produces empty tree/edges."""
    decisions = [_make_decision(_LEAF_ID, None)]
    container = _build_container(decisions, {})
    correction_svc = _build_correction_svc(
        analyze_affected=[_LEAF_ID],
    )

    container.correction_service.return_value = correction_svc

    args = [
        "correct",
        _LEAF_ID,
        "--mode",
        "revert",
        "--guidance",
        "Fix this",
        "--plan",
        _PLAN_ID,
        "--dry-run",
    ]
    with patch(_PATCH_CONTAINER, return_value=container):
        result = runner.invoke(plan_app, args)

    assert result.exit_code == 0, f"Exit {result.exit_code}: {result.output}"
    kw = correction_svc.analyze_impact.call_args.kwargs
    assert kw["decision_tree"] == {}, f"Expected empty tree: {kw}"
    assert kw["influence_edges"] == {}, f"Expected empty edges: {kw}"
    print("plan-correct-tree-wiring-leaf-ok")


# ---------------------------------------------------------------------------
# Dispatch
# ---------------------------------------------------------------------------

_TESTS: dict[str, Callable[[], None]] = {
    "dry_run_tree": _test_dry_run_tree,
    "execute_tree": _test_execute_tree,
    "leaf_empty": _test_leaf_empty,
}

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <{'|'.join(_TESTS)}>")
        sys.exit(1)
    test_name = sys.argv[1]
    if test_name not in _TESTS:
        print(f"Unknown test: {test_name}. Available: {list(_TESTS)}")
        sys.exit(1)
    _TESTS[test_name]()
