"""Unit tests for the pure status-derivation helpers inside `engine.engine`.

`_derive_final_status` and `_build_revisit_context` are dispatched on
hot paths, but their logic is pure — they take dicts in and return a
status / payload out. Exercise them without touching the engine class.
"""

from __future__ import annotations

from attractor.engine import OutcomeStatus, RunStatus
from attractor.engine.engine import (
    _build_revisit_context,  # pyright: ignore[reportPrivateUsage]
    _derive_final_status,  # pyright: ignore[reportPrivateUsage]
    _EntryEdge,  # pyright: ignore[reportPrivateUsage]
)
from attractor.workflow import Node, NodeKind, Span, ValidGraph


def _node(
    node_id: str,
    *,
    goal_gate: bool = False,
    kind: NodeKind = NodeKind.AGENT,
) -> Node:
    attrs: dict[str, str] = {}
    if goal_gate:
        attrs["goal_gate"] = "true"
    return Node(
        id=node_id,
        kind=kind,
        label=None,
        class_=None,
        attributes=attrs,
        span=Span(line=0, column=0),
    )


# [unit->REQ-EXEC-GOAL-GATES]
def test_no_goal_gates_means_completed() -> None:
    """A graph with no `goal_gate=true` nodes is satisfied trivially."""
    g = ValidGraph(name="t", attributes={}, nodes=[_node("a")], edges=[])
    assert _derive_final_status(g, {"a": OutcomeStatus.SUCCESS}) == RunStatus.COMPLETED


# [unit->REQ-EXEC-GOAL-GATES]
def test_goal_gate_success_at_latest_visit_completes() -> None:
    """Latest outcome is SUCCESS → COMPLETED."""
    g = ValidGraph(name="t", attributes={}, nodes=[_node("verify", goal_gate=True)], edges=[])
    assert _derive_final_status(g, {"verify": OutcomeStatus.SUCCESS}) == RunStatus.COMPLETED


# [unit->REQ-EXEC-GOAL-GATES]
def test_goal_gate_failure_at_latest_visit_incomplete() -> None:
    """Latest is FAILURE → INCOMPLETE per SPEC §6.2."""
    g = ValidGraph(name="t", attributes={}, nodes=[_node("verify", goal_gate=True)], edges=[])
    assert _derive_final_status(g, {"verify": OutcomeStatus.FAILURE}) == RunStatus.INCOMPLETE


# [unit->REQ-EXEC-GOAL-GATES]
def test_goal_gate_unvisited_means_incomplete() -> None:
    """Never-reached goal gate counts as unsatisfied."""
    g = ValidGraph(name="t", attributes={}, nodes=[_node("v", goal_gate=True)], edges=[])
    assert _derive_final_status(g, {}) == RunStatus.INCOMPLETE


# [unit->REQ-EXEC-GOAL-GATES]
def test_any_failing_goal_gate_blocks_completion() -> None:
    """Multiple gates, ANY failing → INCOMPLETE."""
    g = ValidGraph(
        name="t",
        attributes={},
        nodes=[_node("a", goal_gate=True), _node("b", goal_gate=True)],
        edges=[],
    )
    assert _derive_final_status(
        g, {"a": OutcomeStatus.SUCCESS, "b": OutcomeStatus.FAILURE}
    ) == RunStatus.INCOMPLETE


# [unit->REQ-EXEC-OUTCOME-STATUSES]
def test_goal_gate_partial_success_is_satisfied() -> None:
    """PARTIAL_SUCCESS satisfies goal_gate=true per upstream §3.4."""
    g = ValidGraph(
        name="t", attributes={},
        nodes=[_node("verify", goal_gate=True)], edges=[],
    )
    assert (
        _derive_final_status(g, {"verify": OutcomeStatus.PARTIAL_SUCCESS})
        == RunStatus.COMPLETED
    )


# [unit->REQ-EXEC-OUTCOME-NO-OP]
def test_goal_gate_no_op_is_satisfied() -> None:
    """NO_OP is a valid completed terminal state for goal gates."""
    g = ValidGraph(
        name="t", attributes={},
        nodes=[_node("verify", goal_gate=True)], edges=[],
    )
    assert _derive_final_status(g, {"verify": OutcomeStatus.NO_OP}) == RunStatus.COMPLETED


# [unit->REQ-EXEC-REVISIT-CONTEXT]
def test_revisit_context_none_on_first_visit() -> None:
    """No prior context on visit 1."""
    g = ValidGraph(name="t", attributes={}, nodes=[_node("a")], edges=[])
    n = _node("a")
    assert _build_revisit_context(workflow=g, node=n, visit=1, latest_outcomes={}) is None


# [unit->REQ-EXEC-REVISIT-CONTEXT]
def test_revisit_context_carries_visit_n_and_prior_outcome() -> None:
    """Visit 2 returns a dict with `visit_n` and `prior_outcome`."""
    g = ValidGraph(name="t", attributes={}, nodes=[_node("a")], edges=[])
    n = _node("a")
    ctx = _build_revisit_context(
        workflow=g, node=n, visit=2, latest_outcomes={"a": OutcomeStatus.FAILURE}
    )
    assert ctx is not None
    assert ctx["visit_n"] == 2
    assert ctx["prior_outcome"] == "FAILURE"


# [unit->REQ-EXEC-REVISIT-CONTEXT]
def test_revisit_context_includes_captured_output_key() -> None:
    """Even when empty, the key is present so the agent can pattern-match."""
    g = ValidGraph(name="t", attributes={}, nodes=[_node("a")], edges=[])
    n = _node("a")
    ctx = _build_revisit_context(
        workflow=g, node=n, visit=3, latest_outcomes={"a": OutcomeStatus.SUCCESS}
    )
    assert ctx is not None
    assert "captured_output" in ctx
    assert ctx["prior_outcome"] == "SUCCESS"


def _entry_from(
    predecessor_node: str,
    predecessor_kind: NodeKind,
    edge_label: str,
    *,
    captured_output: str = "",
    reason: str | None = None,
    predecessor_branches: tuple[dict[str, str | bool], ...] = (),
) -> _EntryEdge:
    return _EntryEdge(
        predecessor_node=predecessor_node,
        predecessor_kind=predecessor_kind,
        edge_label=edge_label,
        predecessor_captured_output=captured_output,
        predecessor_reason=reason,
        predecessor_branches=predecessor_branches,
    )


# [unit->REQ-EXEC-REVISIT-CONTEXT]
def test_revisit_context_failure_edge_surfaces_predecessor_payload() -> None:
    """Visit 1 via a tool/agent FAILURE edge MUST carry predecessor context.

    SPEC §6.3: the §6.6 fix-up pattern requires the receiving agent to
    see the predecessor's captured_output on its first visit.
    """
    g = ValidGraph(name="t", attributes={}, nodes=[_node("fixup")], edges=[])
    fixup = _node("fixup")
    ctx = _build_revisit_context(
        workflow=g,
        node=fixup,
        visit=1,
        latest_outcomes={"verify": OutcomeStatus.FAILURE},
        entry_edge=_entry_from(
            "verify",
            NodeKind.TOOL,
            "FAILURE",
            captured_output="cargo test: 3 failures",
        ),
    )
    assert ctx is not None
    assert ctx["predecessor_node"] == "verify"
    assert ctx["entry_edge_label"] == "FAILURE"
    assert ctx["predecessor_outcome"] == "FAILURE"
    assert ctx["captured_output"] == "cargo test: 3 failures"
    # Visit 1 → no prior-self fields.
    assert "visit_n" not in ctx
    assert "prior_outcome" not in ctx


# [unit->REQ-EXEC-OUTCOME-NO-OP]
def test_revisit_context_no_op_edge_surfaces_predecessor_payload() -> None:
    """NO_OP report nodes receive the predecessor's no-op reason/evidence."""
    g = ValidGraph(name="t", attributes={}, nodes=[_node("report")], edges=[])
    report = _node("report")
    ctx = _build_revisit_context(
        workflow=g,
        node=report,
        visit=1,
        latest_outcomes={"plan": OutcomeStatus.NO_OP},
        entry_edge=_entry_from(
            "plan",
            NodeKind.AGENT,
            "NO_OP",
            captured_output="already fixed on main",
        ),
    )
    assert ctx is not None
    assert ctx["predecessor_node"] == "plan"
    assert ctx["entry_edge_label"] == "NO_OP"
    assert ctx["predecessor_outcome"] == "NO_OP"
    assert ctx["captured_output"] == "already fixed on main"


# [unit->REQ-EXEC-REVISIT-CONTEXT]
def test_revisit_context_failure_edge_overrides_self_captured_output() -> None:
    """When both visit≥2 and a FAILURE entry coexist, predecessor's output wins.

    The single `captured_output` slot carries the most useful signal:
    on a labeled FAILURE entry that's the predecessor's, not the agent's
    own prior visit (which would be empty under the v0.1 placeholder).
    """
    g = ValidGraph(name="t", attributes={}, nodes=[_node("fixup")], edges=[])
    fixup = _node("fixup")
    ctx = _build_revisit_context(
        workflow=g,
        node=fixup,
        visit=2,
        latest_outcomes={"fixup": OutcomeStatus.SUCCESS},
        entry_edge=_entry_from(
            "verify",
            NodeKind.AGENT,
            "FAILURE",
            captured_output="agent FAIL output",
        ),
    )
    assert ctx is not None
    assert ctx["visit_n"] == 2
    assert ctx["prior_outcome"] == "SUCCESS"
    assert ctx["captured_output"] == "agent FAIL output"
    assert ctx["predecessor_outcome"] == "FAILURE"


# [unit->REQ-EXEC-REVISIT-CONTEXT]
def test_revisit_context_human_gate_edge_emits_predecessor_fields() -> None:
    """Human-gate choice edges carry `predecessor_node` + `entry_edge_label`."""
    g = ValidGraph(name="t", attributes={}, nodes=[_node("rework")], edges=[])
    rework = _node("rework")
    ctx = _build_revisit_context(
        workflow=g,
        node=rework,
        visit=1,
        latest_outcomes={},
        entry_edge=_entry_from("review", NodeKind.HUMAN, "revise"),
    )
    assert ctx is not None
    assert ctx["predecessor_node"] == "review"
    assert ctx["entry_edge_label"] == "revise"
    # Human-gate predecessors carry no FAILURE-style outcome / captured_output.
    assert "predecessor_outcome" not in ctx
    assert "captured_output" not in ctx
    assert "predecessor_reason" not in ctx


# [unit->REQ-EXEC-REVISIT-CONTEXT]
# [unit->REQ-HUMAN-GATE]
def test_revisit_context_human_gate_reason_lands_in_payload() -> None:
    """If the host attached `reason` on respond(), it appears as `predecessor_reason`."""
    g = ValidGraph(name="t", attributes={}, nodes=[_node("rework")], edges=[])
    rework = _node("rework")
    ctx = _build_revisit_context(
        workflow=g,
        node=rework,
        visit=1,
        latest_outcomes={},
        entry_edge=_entry_from(
            "review",
            NodeKind.HUMAN,
            "revise",
            reason="missing security review",
        ),
    )
    assert ctx is not None
    assert ctx["predecessor_reason"] == "missing security review"


# [unit->REQ-EXEC-REVISIT-CONTEXT]
# [unit->REQ-EXEC-PARALLEL-WORKTREE]
def test_revisit_context_join_entry_surfaces_predecessor_branches() -> None:
    """A join successor receives branch refs and outputs for merge/fixup work."""
    g = ValidGraph(name="t", attributes={}, nodes=[_node("merge")], edges=[])
    merge = _node("merge")
    ctx = _build_revisit_context(
        workflow=g,
        node=merge,
        visit=1,
        latest_outcomes={"join": OutcomeStatus.SUCCESS},
        entry_edge=_entry_from(
            "join",
            NodeKind.JOIN,
            "SUCCESS",
            captured_output="[left] ok\n[right] ok",
            predecessor_branches=(
                {
                    "name": "left",
                    "ref": "refs/heads/attractor/run/r/branch/left",
                    "success": True,
                    "captured_output": "ok",
                },
                {
                    "name": "right",
                    "ref": "refs/heads/attractor/run/r/branch/right",
                    "success": True,
                    "captured_output": "ok",
                },
            ),
        ),
    )
    assert ctx is not None
    assert ctx["predecessor_node"] == "join"
    assert ctx["entry_edge_label"] == "SUCCESS"
    assert ctx["captured_output"] == "[left] ok\n[right] ok"
    assert ctx["predecessor_branches"] == [
        {
            "name": "left",
            "ref": "refs/heads/attractor/run/r/branch/left",
            "success": True,
            "captured_output": "ok",
        },
        {
            "name": "right",
            "ref": "refs/heads/attractor/run/r/branch/right",
            "success": True,
            "captured_output": "ok",
        },
    ]
    assert "predecessor_outcome" not in ctx


# [unit->REQ-EXEC-REVISIT-CONTEXT]
def test_revisit_context_unlabeled_edge_emits_no_predecessor_fields() -> None:
    """Unlabeled SUCCESS-default edges produce no predecessor context (SPEC §6.3)."""
    g = ValidGraph(name="t", attributes={}, nodes=[_node("a")], edges=[])
    a = _node("a")
    ctx = _build_revisit_context(
        workflow=g,
        node=a,
        visit=1,
        latest_outcomes={},
        entry_edge=_entry_from("start", NodeKind.START, ""),
    )
    assert ctx is None


# [unit->REQ-EXEC-REVISIT-CONTEXT]
def test_revisit_context_tool_success_edge_emits_no_predecessor_fields() -> None:
    """SPEC §6.3 enumerates tool/agent FAILURE/NO_OP and human-gate edges.

    A labeled `SUCCESS` edge from a tool/agent predecessor does NOT
    qualify — the receiving agent gets no predecessor context unless
    it's also a revisit (visit ≥ 2).
    """
    g = ValidGraph(name="t", attributes={}, nodes=[_node("a")], edges=[])
    a = _node("a")
    ctx = _build_revisit_context(
        workflow=g,
        node=a,
        visit=1,
        latest_outcomes={},
        entry_edge=_entry_from("tool_pred", NodeKind.TOOL, "SUCCESS"),
    )
    assert ctx is None
