"""Engine-side coverage for `Engine.abort_run`. SPEC §13.

`abort_run` is a small primitive: PAUSED → INCOMPLETE via a new
journal entry, refs preserved. CLI-surface tests live in
`tests/test_cli/test_run_abort.py`.
"""

from __future__ import annotations

from pathlib import Path

import pygit2
import pytest

from attractor.engine import (
    AbortNotPaused,
    AbortResult,
    Engine,
    RunStatus,
    UnknownRun,
)
from attractor.workflow import parse, validate


# [unit->REQ-CLI-RUN-ABORT]
@pytest.mark.asyncio
async def test_abort_transitions_paused_run_to_incomplete(
    seeded_repo: Path, human_gate_workflow: str
) -> None:
    engine = Engine(seeded_repo)
    graph = validate(parse(human_gate_workflow))
    status = await engine.run(graph)
    assert status == RunStatus.PAUSED
    run_id = engine.list()[0].run_id

    result = engine.abort_run(run_id)

    assert isinstance(result, AbortResult)
    assert result.run_id == run_id
    assert result.previous_status == RunStatus.PAUSED
    assert result.new_status == RunStatus.INCOMPLETE
    assert result.seq > 0

    # `show()` reflects the new status.
    summary = engine.show(run_id)
    assert summary.status == RunStatus.INCOMPLETE


# [unit->REQ-CLI-RUN-ABORT]
@pytest.mark.asyncio
async def test_abort_preserves_refs_and_worktree(
    seeded_repo: Path, human_gate_workflow: str
) -> None:
    """Refs and worktree directory remain after abort (inspectable)."""
    engine = Engine(seeded_repo)
    graph = validate(parse(human_gate_workflow))
    await engine.run(graph)
    run_id = engine.list()[0].run_id

    engine.abort_run(run_id)

    repo = pygit2.Repository(str(seeded_repo))
    state_ref = f"refs/heads/attractor/run/{run_id}/state"
    worktree_ref = f"refs/heads/attractor/run/{run_id}/worktree"
    assert state_ref in list(repo.references)
    assert worktree_ref in list(repo.references)

    # Worktree directory is still on disk — paused runs retain it
    # per §6.7, and abort doesn't remove it.
    handles = [h for h in engine.list() if h.run_id == run_id]
    assert handles, "run vanished from engine.list()"
    assert handles[0].worktree_path.exists()


# [unit->REQ-CLI-RUN-ABORT]
def test_abort_unknown_run_raises(seeded_repo: Path) -> None:
    engine = Engine(seeded_repo)
    with pytest.raises(UnknownRun):
        engine.abort_run("00000000-0000-0000-0000-000000000000")


# [unit->REQ-CLI-RUN-ABORT]
@pytest.mark.asyncio
async def test_abort_refuses_completed_run(
    seeded_repo: Path, tool_only_workflow: str
) -> None:
    engine = Engine(seeded_repo)
    graph = validate(parse(tool_only_workflow))
    status = await engine.run(graph)
    assert status == RunStatus.COMPLETED
    run_id = engine.list_all_run_ids()[0]

    with pytest.raises(AbortNotPaused) as exc:
        engine.abort_run(run_id)
    assert "completed" in str(exc.value).lower()
    assert "prune" in str(exc.value).lower()


# [unit->REQ-CLI-RUN-ABORT]
@pytest.mark.asyncio
async def test_abort_refuses_incomplete_run(
    seeded_repo: Path, goal_gate_unmet_workflow: str
) -> None:
    engine = Engine(seeded_repo)
    graph = validate(parse(goal_gate_unmet_workflow))
    status = await engine.run(graph)
    assert status == RunStatus.INCOMPLETE
    run_id = engine.list_all_run_ids()[0]

    with pytest.raises(AbortNotPaused) as exc:
        engine.abort_run(run_id)
    assert "incomplete" in str(exc.value).lower()


# [unit->REQ-CLI-RUN-ABORT]
@pytest.mark.asyncio
async def test_aborted_run_is_pruneable_without_force(
    seeded_repo: Path, human_gate_workflow: str
) -> None:
    """After abort, status is INCOMPLETE so prune accepts it without --force."""
    engine = Engine(seeded_repo)
    graph = validate(parse(human_gate_workflow))
    await engine.run(graph)
    run_id = engine.list()[0].run_id

    engine.abort_run(run_id)

    # Plain prune (no force) succeeds — INCOMPLETE is pruneable.
    # `prune_run` refuses dirty worktrees too; abort leaves a clean
    # worktree so this should work.
    result = engine.prune_run(run_id)
    assert result.removed_state_ref
