#!/usr/bin/env python3
"""Behavioral self-tests for dispatch_status.py."""

from __future__ import annotations

import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import time


ROOT = Path(__file__).resolve().parents[3]
TOOL = ROOT / "pipeline" / "tools" / "dispatch_status.py"


def run_cmd(*args: str, check: bool = True) -> subprocess.CompletedProcess[str]:
    result = subprocess.run(
        [sys.executable, str(TOOL), *args],
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        check=False,
    )
    if check and result.returncode != 0:
        raise AssertionError(
            f"command failed: {args}\nstdout={result.stdout}\nstderr={result.stderr}"
        )
    return result


def read_status(run_dir: Path) -> dict:
    return json.loads((run_dir / "status.json").read_text(encoding="utf-8"))


def write_json(path: Path, payload: dict) -> None:
    path.write_text(json.dumps(payload, sort_keys=True), encoding="utf-8")


def init_run(tmp: Path, name: str = "REC-77-20260606-000000") -> Path:
    run_dir = tmp / name
    run_cmd(
        "init",
        "--run-dir",
        str(run_dir),
        "--issue",
        "REC-77",
        "--branch",
        "codex/REC-77-test",
        "--worktree",
        str(tmp / "worktree"),
        "--spec",
        str(tmp / "BUILD_SPEC.md"),
        "--last-validated-commit",
        "abc123",
    )
    return run_dir


def terminal_payload(**overrides: object) -> dict:
    payload = {
        "run_id": "REC-77",
        "attempt": 1,
        "exit_code": 1,
        "converge_status": "FAILED",
        "phase": "phase-2",
        "gate": "validation",
        "validation_command": "pytest /tmp/example/test_file.py:123: pid 111 ran in 17s",
        "failing_test_ids": ["test_alpha"],
        "convergence_verdict_summary": "failed at 2026-06-06T12:00:00Z with 901 tokens 0xabc",
        "normalized_failure_excerpt": "irrelevant",
        "failure_signature": None,
        "cause_hint": "validation_failure",
        "pr_url": None,
        "branch": "codex/REC-77-test",
        "commit": "def456",
        "log_path": "/tmp/build-log.md",
        "written_at": "2026-06-06T12:00:01Z",
    }
    payload.update(overrides)
    return payload


def test_init_transition_projection(tmp: Path) -> None:
    run_dir = init_run(tmp)
    status = read_status(run_dir)
    assert status["state"] == "STARTED"
    assert status["attempt"] == 1
    assert status["last_validated_commit"] == "abc123"
    assert status["started_grace_until"]

    run_cmd("transition", "--run-dir", str(run_dir), "--state", "ATTEMPT_RUNNING")
    status = read_status(run_dir)
    assert status["state"] == "ATTEMPT_RUNNING"
    assert status["linear_projection_dirty"] is True

    run_cmd(
        "projection",
        "--run-dir",
        str(run_dir),
        "--clean",
        "--expected-state",
        "ATTEMPT_RUNNING",
    )
    status = read_status(run_dir)
    assert status["linear_projection_dirty"] is False
    assert status["linear_projected_at"]


def test_projection_clean_race_does_not_clobber_transition(tmp: Path) -> None:
    run_dir = init_run(tmp)
    run_cmd("transition", "--run-dir", str(run_dir), "--state", "ATTEMPT_RUNNING")
    run_cmd(
        "projection",
        "--run-dir",
        str(run_dir),
        "--clean",
        "--expected-state",
        "ATTEMPT_RUNNING",
    )
    before = read_status(run_dir)
    assert before["linear_projection_dirty"] is False

    clean = subprocess.Popen(
        [
            sys.executable,
            str(TOOL),
            "projection",
            "--run-dir",
            str(run_dir),
            "--clean",
            "--expected-state",
            "ATTEMPT_RUNNING",
        ],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
    )
    transition = subprocess.Popen(
        [
            sys.executable,
            str(TOOL),
            "transition",
            "--run-dir",
            str(run_dir),
            "--state",
            "ZOMBIE_SUSPECT",
            "--failure-signature",
            "sha256:race",
        ],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
    )
    for proc in (clean, transition):
        stdout, stderr = proc.communicate(timeout=10)
        if proc.returncode != 0:
            raise AssertionError(f"concurrent command failed\n{stdout}\n{stderr}")

    status = read_status(run_dir)
    assert status["state"] == "ZOMBIE_SUSPECT"
    assert status["last_failure_signature"] == "sha256:race"
    assert status["linear_projection_dirty"] is True


def test_concurrent_writers_preserve_both_field_updates(tmp: Path) -> None:
    run_dir = init_run(tmp)
    first = subprocess.Popen(
        [
            sys.executable,
            str(TOOL),
            "budget-check",
            "--run-dir",
            str(run_dir),
            "--set-wall-used",
            "9",
        ],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
    )
    second = subprocess.Popen(
        [
            sys.executable,
            str(TOOL),
            "transition",
            "--run-dir",
            str(run_dir),
            "--state",
            "ATTEMPT_RUNNING",
            "--pr-url",
            "https://github.example/pr/1",
        ],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
    )
    for proc in (first, second):
        stdout, stderr = proc.communicate(timeout=10)
        if proc.returncode != 0:
            raise AssertionError(f"concurrent command failed\n{stdout}\n{stderr}")

    status = read_status(run_dir)
    assert status["state"] == "ATTEMPT_RUNNING"
    assert status["pr_url"] == "https://github.example/pr/1"
    assert status["budget"]["wall_clock_s_used"] == 9


def test_retry_start_signature_and_zombie(tmp: Path) -> None:
    run_dir = init_run(tmp)
    run_cmd("retry-start", "--run-dir", str(run_dir), "--failure-signature", "sha256:first")
    status = read_status(run_dir)
    assert status["attempt"] == 2
    assert status["state"] == "RETRY_PENDING"
    assert "sha256:first" in status["prior_failure_signatures"]
    assert (run_dir / "attempt-002").is_dir()

    run_cmd("retry-start", "--run-dir", str(run_dir), "--zombie")
    status = read_status(run_dir)
    assert status["attempt"] == 3
    assert status["last_retry_cause"] == "zombie"
    assert status["prior_failure_signatures"] == ["sha256:first"]
    assert (run_dir / "attempt-003").is_dir()

    capped = run_cmd("retry-start", "--run-dir", str(run_dir), "--zombie", check=False)
    assert capped.returncode != 0


def test_signature_stability_and_difference(tmp: Path) -> None:
    one = tmp / "terminal-one.json"
    two = tmp / "terminal-two.json"
    three = tmp / "terminal-three.json"
    write_json(one, terminal_payload())
    write_json(
        two,
        terminal_payload(
            validation_command="pytest /var/folders/zz/random/test_file.py:456: pid 999 ran in 42s",
            convergence_verdict_summary="failed at 2026-06-06T13:45:33Z with 1200 tokens 0xdef",
            written_at="2026-06-06T13:45:34Z",
        ),
    )
    write_json(three, terminal_payload(failing_test_ids=["test_beta"]))

    sig_one = run_cmd("signature", "--terminal-status", str(one)).stdout.strip()
    sig_two = run_cmd("signature", "--terminal-status", str(two)).stdout.strip()
    sig_three = run_cmd("signature", "--terminal-status", str(three)).stdout.strip()
    assert sig_one.startswith("sha256:")
    assert sig_one == sig_two
    assert sig_one != sig_three


def test_classify(tmp: Path) -> None:
    run_dir = init_run(tmp)
    terminal = tmp / "terminal.json"
    write_json(terminal, terminal_payload(failure_signature="sha256:repeat"))

    assert run_cmd("classify", "--run-dir", str(run_dir), "--terminal-status", str(terminal)).stdout.strip() == "TRANSIENT"

    run_cmd("retry-start", "--run-dir", str(run_dir), "--failure-signature", "sha256:repeat")
    assert run_cmd("classify", "--run-dir", str(run_dir), "--terminal-status", str(terminal)).stdout.strip() == "DETERMINISTIC"

    frozen = tmp / "frozen.json"
    write_json(frozen, terminal_payload(cause_hint="frozen_gate", failure_signature="sha256:new"))
    assert run_cmd("classify", "--run-dir", str(run_dir), "--terminal-status", str(frozen)).stdout.strip() == "DETERMINISTIC"

    converged = tmp / "converged.json"
    write_json(converged, terminal_payload(exit_code=0, converge_status="CONVERGED"))
    assert run_cmd("classify", "--run-dir", str(run_dir), "--terminal-status", str(converged)).stdout.strip() == "CONVERGED"


def test_budget_check_caps_and_accounting(tmp: Path) -> None:
    run_dir = init_run(tmp)
    assert run_cmd("budget-check", "--run-dir", str(run_dir), "--set-wall-used", "10").returncode == 0
    assert run_cmd("budget-check", "--run-dir", str(run_dir), "--set-wall-used", "12").returncode == 0
    status = read_status(run_dir)
    assert status["budget"]["wall_clock_s_used"] == 12

    run_cmd("budget-check", "--run-dir", str(run_dir), "--add-rounds", "1")
    run_cmd("budget-check", "--run-dir", str(run_dir), "--add-rounds", "1")
    status = read_status(run_dir)
    assert status["budget"]["codex_rounds_used"] == 2

    wall_cap = run_cmd(
        "budget-check",
        "--run-dir",
        str(run_dir),
        "--set-wall-used",
        "21601",
        check=False,
    )
    assert wall_cap.returncode != 0

    run_dir_rounds = init_run(tmp, "REC-77-rounds")
    rounds_cap = run_cmd(
        "budget-check",
        "--run-dir",
        str(run_dir_rounds),
        "--add-rounds",
        "19",
        check=False,
    )
    assert rounds_cap.returncode != 0


def main() -> int:
    tests = [
        test_init_transition_projection,
        test_projection_clean_race_does_not_clobber_transition,
        test_concurrent_writers_preserve_both_field_updates,
        test_retry_start_signature_and_zombie,
        test_signature_stability_and_difference,
        test_classify,
        test_budget_check_caps_and_accounting,
    ]
    with tempfile.TemporaryDirectory(prefix="dispatch-status-tests-") as raw:
        tmp = Path(raw)
        for test in tests:
            test_dir = tmp / test.__name__
            test_dir.mkdir()
            test(test_dir)
    print("dispatch_status selftests passed")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
