#!/usr/bin/env python3
"""Verify the post-audit source-only hygiene closure without network actions."""
from __future__ import annotations

import hashlib
import json
import subprocess
from pathlib import Path, PurePosixPath

ROOT = Path(__file__).resolve().parents[1]
CONTROL = ROOT / "release-control/source-candidate-20260826"
DECISIONS = CONTROL / "inherited-evidence-decisions.json"
PURGES = CONTROL / "hygiene-purge-report.json"
ALLOWED = {
    "keep-current-authority",
    "keep-immutable-fixture",
    "purge-run-output",
}


def strict_json(path: Path) -> dict:
    def reject_duplicates(pairs: list[tuple[str, object]]) -> dict:
        value: dict = {}
        for key, item in pairs:
            if key in value:
                raise ValueError(f"duplicate JSON key in {path}: {key}")
            value[key] = item
        return value

    return json.loads(path.read_text("utf-8"), object_pairs_hook=reject_duplicates)


def git(*args: str, binary: bool = False) -> str | bytes:
    return subprocess.check_output(
        ["git", "-C", str(ROOT), *args],
        text=not binary,
    )


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


def require(condition: bool, message: str) -> None:
    if not condition:
        raise ValueError(message)


def main() -> None:
    decisions = strict_json(DECISIONS)
    purge_report = strict_json(PURGES)
    audited = decisions["auditedCandidateCommit"]
    rows = decisions["rows"]
    paths = [row["path"] for row in rows]
    inherited = sorted(
        git(
            "ls-tree",
            "-r",
            "--name-only",
            audited,
            "--",
            "audit-evidence",
            "release-evidence",
        ).splitlines()
    )
    require(len(inherited) == 126, "audited inherited evidence set is not 126 files")
    require(paths == inherited, "decision manifest omits, adds, or reorders inherited files")
    require(len(paths) == len(set(paths)), "decision manifest has duplicate paths")
    require(
        set(row["decision"] for row in rows) <= ALLOWED,
        "decision manifest contains an unsupported decision",
    )

    kept: set[str] = set()
    purged: set[str] = set()
    for row in rows:
        path = row["path"]
        before = git("show", f"{audited}:{path}", binary=True)
        require(
            sha256(before) == row["sha256BeforeDecision"],
            f"pre-decision digest mismatch: {path}",
        )
        candidate = ROOT / path
        if row["decision"] == "purge-run-output":
            require(not candidate.exists() and not candidate.is_symlink(), f"purged path remains: {path}")
            purged.add(path)
        else:
            require(candidate.is_file() and not candidate.is_symlink(), f"kept path missing or unsafe: {path}")
            current_sha256 = sha256(candidate.read_bytes())
            if row["decision"] == "keep-immutable-fixture":
                require(current_sha256 == row["sha256BeforeDecision"], f"kept fixture drift: {path}")
            else:
                require(
                    current_sha256 == row["sha256AfterDecision"],
                    f"current authority digest is not reconciled: {path}",
                )
            kept.add(path)
        require(row["reason"] and row["consumer"], f"incomplete decision rationale: {path}")

    current_evidence = {
        path
        for path in git("ls-files", "audit-evidence/**", "release-evidence/**").splitlines()
        if (ROOT / path).is_file()
    }
    require(current_evidence == kept, "current inherited evidence surface differs from keep decisions")
    require(len(kept) == 9 and len(purged) == 117, "evidence decision counts drifted")

    purge_rows = purge_report["rows"]
    purge_paths = [row["path"] for row in purge_rows]
    require(len(purge_paths) == len(set(purge_paths)) == 153, "post-audit purge coverage drifted")
    require(purged <= set(purge_paths), "evidence purge decisions are missing from purge report")
    for row in purge_rows:
        path = row["path"]
        require(not (ROOT / path).exists(), f"post-audit purge path remains: {path}")
        before = git("show", f"{audited}:{path}", binary=True)
        require(sha256(before) == row["sha256AtAuditedCommit"], f"purge digest mismatch: {path}")

    proposed = sorted(
        set(
            git(
                "ls-files",
                "--cached",
                "--others",
                "--exclude-standard",
            ).splitlines()
        )
    )
    prohibited: list[str] = []
    for path in proposed:
        parts = set(PurePosixPath(path).parts)
        candidate = ROOT / path
        if (
            "_next" in parts
            or "__pycache__" in parts
            or ".pytest_cache" in parts
            or "node_modules" in parts
            or "dist" in parts
            or path.endswith(
                (
                    ".pyc",
                    ".pyo",
                    ".pid",
                    ".log",
                    ".log.gz",
                    ".tar.gz",
                    ".tgz",
                    ".zip",
                    ".tsbuildinfo",
                )
            )
            or candidate.is_symlink()
        ):
            prohibited.append(path)
    require(not prohibited, f"prohibited tracked artifacts remain: {prohibited}")
    print(
        json.dumps(
            {
                "auditedInheritedFiles": len(inherited),
                "keepCurrentAuthority": sum(
                    row["decision"] == "keep-current-authority" for row in rows
                ),
                "keepImmutableFixture": sum(
                    row["decision"] == "keep-immutable-fixture" for row in rows
                ),
                "purgedInheritedRunOutputs": len(purged),
                "totalPostAuditPurges": len(purge_paths),
                "prohibitedTrackedArtifacts": 0,
                "status": "pass",
            },
            sort_keys=True,
        )
    )


if __name__ == "__main__":
    main()
