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

import argparse
import datetime
import hashlib
import json
import os
import sys
import tempfile
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
MANIFEST_PATH = ROOT / "script/project-manifest.json"
REQUIRED_GATES = [
    "pov_knowledge", "outline_24_beats", "timeline", "reveal_payoff",
    "continuity_props", "agency", "consent_boundaries", "romance_arc",
    "profession_engine", "identity_names", "fictional_world", "ending_coda",
    "tts_suitability",
]


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_active(manifest: dict, key: str) -> Path:
    value = (manifest.get("active_paths") or {}).get(key)
    if not isinstance(value, str) or not value:
        raise RuntimeError(f"active_paths.{key} is missing")
    path = Path(value)
    if not path.is_absolute():
        path = ROOT / path
    try:
        path.resolve().relative_to(ROOT.resolve())
    except ValueError as exc:
        raise RuntimeError(f"active path escapes project: {key}") from exc
    return path


def atomic_json(path: Path, value: dict) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    data = (json.dumps(value, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
    fd, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
    try:
        with os.fdopen(fd, "wb") as handle:
            handle.write(data)
            handle.flush()
            os.fsync(handle.fileno())
        os.replace(name, path)
    finally:
        if os.path.exists(name):
            os.unlink(name)


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--review-json", required=True, help="Path to reviewer-produced JSON")
    args = parser.parse_args()
    source = Path(args.review_json)
    manifest = json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
    candidate = resolve_active(manifest, "candidate")
    producer_close = resolve_active(manifest, "producer_close_receipt")
    markers_path = resolve_active(manifest, "semantic_markers_receipt")
    output = resolve_active(manifest, "semantic_receipt")

    close = json.loads(producer_close.read_text(encoding="utf-8"))
    candidate_hash = sha256(candidate)
    if close.get("status") != "completed" or close.get("writer_closed") is not True:
        raise RuntimeError("candidate writer is not closed")
    if close.get("candidate_sha256") != candidate_hash or close.get("bytes") != candidate.stat().st_size:
        raise RuntimeError("candidate differs from producer-close marker")

    review = json.loads(source.read_text(encoding="utf-8"))
    markers = json.loads(markers_path.read_text(encoding="utf-8"))
    if review.get("candidate_sha256") != candidate_hash:
        raise RuntimeError("semantic review candidate hash is absent or stale")
    gates = review.get("gates")
    if not isinstance(gates, dict):
        raise RuntimeError("semantic review gates must be an object")
    missing = [name for name in REQUIRED_GATES if name not in gates]
    if missing:
        raise RuntimeError(f"semantic review missing gates: {missing}")
    malformed = [name for name in REQUIRED_GATES if not isinstance(gates[name], dict) or "verified" not in gates[name]]
    if malformed:
        raise RuntimeError(f"semantic review malformed gates: {malformed}")
    findings = review.get("findings", [])
    blocking = [item for item in findings if str(item.get("severity", "")).upper() in {"BLOCKER", "HIGH"}]
    verified = (
        review.get("verdict") == "PASS"
        and not blocking
        and all(gates[name].get("verified") is True for name in REQUIRED_GATES)
        and markers.get("verified") is True
        and markers.get("candidate_sha256") == candidate_hash
    )
    result = {
        "status": "completed" if verified else "failed",
        "verified": verified,
        "candidate_path": str(candidate),
        "candidate_sha256": candidate_hash,
        "producer_close_receipt": str(producer_close),
        "producer_close_sha256": sha256(producer_close),
        "words_whitespace": len(candidate.read_text(encoding="utf-8").split()),
        "reviewer_verdict": review.get("verdict"),
        "gates": gates,
        "findings": findings,
        "blocking_findings": blocking,
        "semantic_markers_path": str(markers_path),
        "semantic_markers_sha256": sha256(markers_path),
        "review_source_path": str(source),
        "review_source_sha256": sha256(source),
        "recorded_at": datetime.datetime.now(datetime.timezone.utc).isoformat(),
    }
    atomic_json(output, result)
    print(json.dumps({"status": result["status"], "verified": verified, "blocking_findings": len(blocking), "receipt": str(output)}, ensure_ascii=False))
    return 0 if verified else 1


if __name__ == "__main__":
    try:
        sys.exit(main())
    except Exception as exc:
        print(json.dumps({"status": "failed", "verified": False, "error": f"{type(exc).__name__}: {exc}"}, ensure_ascii=False), file=sys.stderr)
        sys.exit(1)
