#!/usr/bin/env python3
from __future__ import annotations

import hashlib
import json
import subprocess
import sys
import time
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
MANIFEST = ROOT / "script/project-manifest.json"
TIMEOUT_SECONDS = 7200
POLL_SECONDS = 5


def sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as handle:
        for chunk in iter(lambda: handle.read(8 * 1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def resolve(root: Path, value: str) -> Path:
    path = Path(value)
    if not path.is_absolute():
        path = root / path
    path.resolve().relative_to(root.resolve())
    return path


def fail(message: str) -> int:
    print(json.dumps({"status": "failed", "verified": False, "error": message}, ensure_ascii=False), flush=True)
    return 1


def main() -> int:
    started = time.monotonic()
    initial = json.loads(MANIFEST.read_text(encoding="utf-8"))
    run = initial.get("story_run") or {}
    active = initial.get("active_paths") or {}
    close_path = resolve(ROOT, active["producer_close_receipt"])
    candidate_path = resolve(ROOT, active["candidate"])

    while not close_path.is_file():
        if time.monotonic() - started >= TIMEOUT_SECONDS:
            return fail("producer-close wait timed out")
        time.sleep(POLL_SECONDS)

    try:
        close = json.loads(close_path.read_text(encoding="utf-8"))
    except Exception as exc:
        return fail(f"producer-close is unreadable: {type(exc).__name__}: {exc}")
    if close.get("status") != "completed" or close.get("writer_closed") is not True:
        return fail("producer-close is not terminal")
    if not candidate_path.is_file():
        return fail("candidate is missing after producer-close")
    observed_hash = sha256(candidate_path)
    observed_bytes = candidate_path.stat().st_size
    if close.get("candidate_sha256") != observed_hash or close.get("bytes", close.get("byte_count")) != observed_bytes:
        return fail("candidate differs from producer-close marker")

    current = json.loads(MANIFEST.read_text(encoding="utf-8"))
    if (current.get("story_run") or {}).get("run_id") != run.get("run_id"):
        return fail("active story run changed while waiting")
    locked = run.get("authority_hashes") or {}
    for key, record in locked.items():
        path = resolve(ROOT, record["path"])
        if not path.is_file() or sha256(path) != record.get("sha256"):
            return fail(f"authority drift detected: {key}")

    commands = [
        [sys.executable, "script/validate-candidate.py"],
        [sys.executable, "script/run-originality.py"],
        [sys.executable, "script/check-semantic-markers.py"],
    ]
    results = []
    for command in commands:
        completed = subprocess.run(command, cwd=ROOT, text=True, capture_output=True)
        results.append({
            "command": " ".join(command),
            "exit_code": completed.returncode,
            "stdout": completed.stdout.strip(),
            "stderr": completed.stderr.strip(),
        })
        if completed.returncode != 0:
            print(json.dumps({
                "status": "failed",
                "verified": False,
                "candidate_sha256": observed_hash,
                "failed_command": " ".join(command),
                "results": results,
            }, ensure_ascii=False), flush=True)
            return completed.returncode

    print(json.dumps({
        "status": "completed",
        "verified": True,
        "candidate_sha256": observed_hash,
        "candidate_bytes": observed_bytes,
        "word_count": close.get("word_count"),
        "results": results,
    }, ensure_ascii=False), flush=True)
    return 0


if __name__ == "__main__":
    sys.exit(main())
