"""End-to-end integration test: tool-only workflow → COMPLETED.

Drives `Engine.run` against a tempdir-backed git repo with a tool-only
workflow (no agent nodes, no `ANTHROPIC_API_KEY` required). Asserts:

- worktree directory created during the run,
- tool ran (build artifact present on the worktree branch),
- journal + worktree commits written,
- run completes `COMPLETED`,
- worktree dir REMOVED after completion (§6.7),
- worktree branch ref RETAINED for `git diff`.
"""

from __future__ import annotations

import subprocess
from pathlib import Path

import pygit2
import pytest

from attractor.engine import (
    Checkpointed,
    Engine,
    EngineEvent,
    NodeStarted,
    RunCompleted,
    RunStatus,
)
from attractor.workflow import parse, validate


@pytest.mark.asyncio
# [int->REQ-DOD-WORKFLOW-RUN]
# [int->REQ-EXEC-NODE-LIFECYCLE]
# [int->REQ-EXEC-RUN-ISOLATION]
# [int->REQ-CHECKPOINT-DUAL-COMMIT]
async def test_tool_only_run_completes_end_to_end(
    seeded_repo: Path, tool_only_workflow: str
) -> None:
    engine = Engine(seeded_repo)
    graph = validate(parse(tool_only_workflow))

    events: list[EngineEvent] = []
    status = await engine.run(graph, inputs=(), events=events.append)

    assert status == RunStatus.COMPLETED

    # We saw NodeStarted for build + verify (start/exit are special-cased
    # but DO emit NodeStarted because the engine treats them as nodes).
    node_started = [e for e in events if isinstance(e, NodeStarted)]
    visited_ids = [e.node_id for e in node_started]
    assert "build" in visited_ids
    assert "verify" in visited_ids

    # RunCompleted fires exactly once with the COMPLETED status.
    completes = [e for e in events if isinstance(e, RunCompleted)]
    assert len(completes) == 1
    assert completes[0].status == "completed"

    # Each non-trivial node produced a Checkpointed event.
    checkpoints = [e for e in events if isinstance(e, Checkpointed)]
    assert len(checkpoints) >= 2  # build + verify (start emits one too)

    # Worktree branch ref exists.
    repo = pygit2.Repository(str(seeded_repo))
    handles = engine.list()
    # After COMPLETED the worktree directory is removed, so list() may
    # find no live worktree. The state branch should still exist:
    state_branches = [
        ref for ref in repo.references if ref.startswith("refs/heads/attractor/run/")
    ]
    assert any(b.endswith("/state") for b in state_branches)
    assert any(b.endswith("/worktree") for b in state_branches)

    # Worktree branch has at least one commit (build's `build.log`).
    worktree_branches = [b for b in state_branches if b.endswith("/worktree")]
    assert worktree_branches
    wt_ref = repo.lookup_reference(worktree_branches[0])
    wt_log = subprocess.run(
        ["git", "-C", str(seeded_repo), "log", "--oneline", wt_ref.name],
        capture_output=True,
        text=True,
        check=True,
    )
    # `seed: initial commit` + at least one node checkpoint.
    log_lines = wt_log.stdout.strip().splitlines()
    assert len(log_lines) >= 2

    # And: handles should be empty (worktree dir removed for COMPLETED).
    assert handles == []


@pytest.mark.asyncio
# [int->REQ-EXEC-GOAL-GATES]
async def test_goal_gate_failure_ends_incomplete(
    seeded_repo: Path, goal_gate_unmet_workflow: str
) -> None:
    """A failing `goal_gate` node with no FAILURE edge ends `incomplete`
    and leaves the worktree directory in place for inspection."""
    engine = Engine(seeded_repo)
    graph = validate(parse(goal_gate_unmet_workflow))
    events: list[EngineEvent] = []
    status = await engine.run(graph, inputs=(), events=events.append)
    assert status == RunStatus.INCOMPLETE

    # Worktree dir retained per §6.7.
    handles = engine.list()
    assert len(handles) == 1
    assert handles[0].status == RunStatus.INCOMPLETE


@pytest.mark.asyncio
# [int->REQ-EXEC-MAX-VISITS]
async def test_max_visits_cap_terminates_run_incomplete(
    seeded_repo: Path, max_visits_workflow: str
) -> None:
    """A verify-fixup loop where verify always fails should terminate
    after `max_visits` verify attempts, ending `INCOMPLETE`."""
    engine = Engine(seeded_repo)
    graph = validate(parse(max_visits_workflow))
    events: list[EngineEvent] = []
    status = await engine.run(graph, inputs=(), events=events.append)
    assert status == RunStatus.INCOMPLETE

    # `verify` should have been entered exactly max_visits times.
    verify_starts = [
        e for e in events
        if isinstance(e, NodeStarted) and e.node_id == "verify"
    ]
    # max_visits=2 → 2 verify attempts, then on the would-be 3rd entry
    # the engine caps and ends `incomplete`. The "would-be 3rd" never
    # emits NodeStarted because the cap fires before dispatch — only
    # the synthetic NodeOutcome+RunCompleted fire.
    assert len(verify_starts) == 2
