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

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

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


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(manifest: dict, key: str) -> Path:
    value = (manifest.get("active_paths") or {}).get(key)
    if not isinstance(value, str) or not value:
        raise RuntimeError(f"missing active_paths.{key}")
    path = Path(value)
    if not path.is_absolute():
        path = ROOT / path
    path.resolve().relative_to(ROOT.resolve())
    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(dir=path.parent, prefix=f".{path.name}.", suffix=".tmp")
    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 locate_after(text: str, aliases: list[str], start: int) -> int:
    positions = [text.find(alias.casefold(), start) for alias in aliases]
    positions = [position for position in positions if position >= 0]
    return min(positions) if positions else -1


def main() -> int:
    manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
    candidate = resolve(manifest, "candidate")
    close_path = resolve(manifest, "producer_close_receipt")
    output = resolve(manifest, "semantic_markers_receipt")
    close = json.loads(close_path.read_text(encoding="utf-8"))
    candidate_hash = sha256(candidate)
    byte_count = close.get("bytes", close.get("byte_count"))
    if (
        close.get("status") != "completed"
        or close.get("writer_closed") is not True
        or close.get("candidate_sha256") != candidate_hash
        or byte_count != candidate.stat().st_size
    ):
        raise RuntimeError("producer-close absent or stale")

    text = candidate.read_text(encoding="utf-8")
    folded = text.casefold()
    anchors = [
        ("timeline_return", ["bốn ngày trước"]),
        ("deadline", ["bảy mươi hai giờ", "72 giờ"]),
        ("first_corridor_success", ["con thứ ba mang một mẩu cỏ khô trong mỏ, do dự hai vòng mới đi qua"]),
        ("midpoint_shared_plan", ["trong mười phút, gần nửa đàn dùng hành lang"]),
        ("anchor_slip", ["góc neo phía bắc trượt khỏi dấu sơn"]),
        ("storm_early", ["mười hai giờ", "12 giờ"]),
        ("wrong_choice_consequence", ["rõ ràng vẫn nhớ hai giờ chúng tôi vừa mất", "mất hai giờ"]),
        ("blocked_vent_reveal", ["sơn bít gần kín miệng khe"]),
        ("shared_authority", ["cô giữ bảng này. tôi giữ khóa tải tổng"]),
        ("catchup", ["y như khoảnh khắc đã mở đầu câu chuyện này"]),
        ("climax_choice", ["“mở,” tôi nói", '"mở," tôi nói']),
        ("bird_rescue", ["chỉ còn con non mắc ở vành"]),
        ("aftermath", ["ba tháng sau"]),
        ("coda", ["mùa chim năm sau"]),
    ]
    positions: dict[str, int] = {}
    cursor = 0
    ordered = True
    for name, aliases in anchors:
        position = locate_after(folded, aliases, cursor)
        positions[name] = position
        if position < 0:
            ordered = False
            continue
        cursor = position + 1

    catchup = positions["catchup"]
    catchup_window = folded[max(0, catchup - 400):catchup + 2200] if catchup >= 0 else ""
    first_transition = positions["timeline_return"]
    cold_open = folded[:first_transition] if first_transition >= 0 else ""
    checks = {
        "beat_order": ordered and all(value >= 0 for value in positions.values()),
        "cold_open_has_choice": "hạ linh" in cold_open and "dịch thần" in cold_open and "đóng" in cold_open and "mở" in cold_open,
        "catchup_after_75_percent": catchup >= int(len(folded) * 0.75),
        "catchup_repeats_people": "hạ linh" in catchup_window and "dịch thần" in catchup_window,
        "catchup_repeats_props": "bảng điều khiển" in catchup_window and "chim non" in catchup_window and "vành" in catchup_window,
        "catchup_repeats_question": "đóng cửa" in catchup_window and "mở hành lang" in catchup_window,
        "safety_boundary_paid_off": "tôi tự khóa dây" in folded,
        "choice_authority_paid_off": "cô giữ bảng này. tôi giữ khóa tải tổng" in folded,
        "ending_echo": "mái kính mở ra một vùng trời đủ rộng cho cả một mùa chim" in folded[-3000:],
    }
    verified = all(checks.values())
    receipt = {
        "status": "completed" if verified else "failed",
        "verified": verified,
        "candidate_sha256": candidate_hash,
        "candidate_path": str(candidate),
        "producer_close_receipt": str(close_path),
        "producer_close_sha256": sha256(close_path),
        "anchor_positions": positions,
        "anchor_ratios": {key: (value / len(folded) if value >= 0 else None) for key, value in positions.items()},
        "checks": {key: {"verified": value, "value": value} for key, value in checks.items()},
        "blockers": [key for key, value in checks.items() if not value],
        "checked_at": datetime.datetime.now(datetime.timezone.utc).isoformat(),
    }
    atomic_json(output, receipt)
    print(json.dumps({
        "status": receipt["status"],
        "verified": verified,
        "blockers": receipt["blockers"],
        "catchup_ratio": receipt["anchor_ratios"]["catchup"],
        "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)
