#!/usr/bin/env python3
"""Independent read-only verifier for sealed Task26 cleanup v4."""

from __future__ import annotations

import ast
import hashlib
import json
import os
import stat
import subprocess
from pathlib import Path
from typing import Any

ROOT = Path(__file__).parent
V3 = ROOT.parent / "task26-post-lifecycle-cleanup-v3-4a6c7ee5-st_01a00e50"
PROFILE = Path("/home/cube/.hermes/profiles/dualcoachtest")
ARCHIVE = PROFILE / "data/post-lifecycle-cleanup-archives/task26-current-4a6c7ee5-20260817"
EXPECTED_ARCHIVE_ROOT = "9f5d72840b4cc1fcb57720a61ebc53529fa00202d9201615e4ebdbb3e047a635"
EXPECTED_V3_SEAL = "84d937ef67c1517bfdbca4cabce9fb4bfbe39aa1165458bb3aedd14decc4bff0"


def canon(value: Any) -> bytes:
    return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode()


def sha(raw: bytes) -> str:
    return hashlib.sha256(raw).hexdigest()


def check(condition: bool, message: str) -> None:
    if not condition:
        raise RuntimeError(message)


def inventory(root: Path) -> tuple[str, int]:
    rows: list[dict[str, Any]] = []
    for base, dirs, files in os.walk(root, followlinks=False):
        dirs.sort()
        files.sort()
        for name in dirs:
            path = Path(base) / name
            st = path.lstat()
            check(stat.S_ISDIR(st.st_mode), f"unsafe archive directory: {path}")
            rows.append({"path": path.relative_to(root).as_posix(), "type": "D", "mode": f"{stat.S_IMODE(st.st_mode):04o}"})
        for name in files:
            path = Path(base) / name
            st = path.lstat()
            check(stat.S_ISREG(st.st_mode), f"unsafe archive entry: {path}")
            rows.append({"path": path.relative_to(root).as_posix(), "type": "F", "mode": f"{stat.S_IMODE(st.st_mode):04o}", "sha256": sha(path.read_bytes())})
    return sha(canon(rows)), len(rows)


def verify_write_surface() -> None:
    tree = ast.parse((ROOT / "cleanup_controller.py").read_text())
    direct: list[str] = []
    for node in ast.walk(tree):
        if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name) and node.func.value.id == "os" and node.func.attr == "write":
            owner = next((item.name for item in ast.walk(tree) if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)) and node in ast.walk(item)), "module")
            direct.append(owner)
    check(direct == ["write_all"], f"unexpected os.write call sites: {direct}")


def main() -> int:
    contract_raw = (ROOT / "schema-contract-v4.json").read_bytes()
    contract = json.loads(contract_raw)
    permission = json.loads((ROOT / "permission-seal-v4.json").read_text())
    completed = contract["completed_cleanup"]
    check(contract["schema"] == "task26-post-lifecycle-cleanup-contract-v4", "contract schema")
    check(contract["bindings"]["candidate_digest"] == "4a6c7ee54cf9526a30de8bb576c1d71b411938beba33a04914738f6e1b6ed1cb", "candidate")
    check(contract["bindings"]["lifecycle_seal_sha256"] == "50e7556e58a876d8136fbfb2023ede8bcb46ac816d97d4b85e1d0559f463d008", "lifecycle seal")
    check(permission["execute_allowed_once"] is False and permission["controller_sha256"] == sha((ROOT / "cleanup_controller.py").read_bytes()), "verification-only permission")
    check(permission["contract_sha256"] == sha(contract_raw), "permission contract binding")
    check(sha((V3 / "SEAL.json").read_bytes()) == EXPECTED_V3_SEAL == completed["v3_seal_sha256"], "v3 seal")
    check(sha((V3 / "inventory.json").read_bytes()) == completed["v3_inventory_sha256"], "v3 inventory")
    check(sha((V3 / "cleanup_controller.py").read_bytes()) == completed["v3_controller_sha256"], "v3 controller")
    archive_root, archive_entries = inventory(ARCHIVE)
    check(archive_root == EXPECTED_ARCHIVE_ROOT == completed["archive_root_sha256"], "archive root")
    manifest_raw = (ARCHIVE / "manifest.json").read_bytes()
    manifest = json.loads(manifest_raw)
    check(sha(manifest_raw) == completed["archive_manifest_sha256"], "manifest binding")
    claimed = manifest.pop("evidence_digest")
    check(claimed == sha(canon(manifest)), "manifest evidence seal")
    manifest["evidence_digest"] = claimed
    for row in manifest["entries"]:
        raw = (ARCHIVE / "payload" / row["path"]).read_bytes()
        check(len(raw) == row["size"] and sha(raw) == row["sha256"], f"payload: {row['path']}")
    check(sha((ARCHIVE / "poststate.json").read_bytes()) == completed["poststate_sha256"], "poststate binding")
    check(sha((PROFILE / "config.yaml").read_bytes()) == completed["current_config_sha256"], "current config")
    for rel in ("customers", "data/customers", "data/onboarding", "data/owner-actions", "gateway.lock", "gateway.pid", "gateway_state.json", "state.db", "state.db-shm", "state.db-wal"):
        check(not (PROFILE / rel).exists(), f"live residue: {rel}")
    show = subprocess.run(["/usr/bin/systemctl", "--user", "show", contract["service"]["name"], "--property=ActiveState,SubState,MainPID"], capture_output=True, text=True, check=True)
    service = dict(line.split("=", 1) for line in show.stdout.splitlines() if "=" in line)
    check(service == {"MainPID": "0", "ActiveState": "inactive", "SubState": "dead"}, "service state")
    verify_write_surface()
    result = {"schema": "task26-post-lifecycle-independent-verification-v4", "status": "PASS", "archive_root_sha256": archive_root, "archive_entries": archive_entries, "archive_manifest_sha256": sha(manifest_raw), "v3_seal_sha256": EXPECTED_V3_SEAL, "candidate_digest": contract["bindings"]["candidate_digest"], "lifecycle_seal_sha256": contract["bindings"]["lifecycle_seal_sha256"], "checks": 17 + len(manifest["entries"]), "write_all_only": True, "clean_baseline": True, "cleanup_replayed": False, "delivery_history_preserved": True, "active_history_preserved": True}
    print(json.dumps(result, sort_keys=True, separators=(",", ":")))
    return 0


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