#!/usr/bin/env python3
"""Independent offline verifier for the Task26 token-rotation successor."""
from __future__ import annotations

import argparse
import hashlib
import json
import stat
import subprocess
import sys
import zipfile
from pathlib import Path
from typing import cast


CONTROLS = {
    "candidate-manifest.json",
    "candidate-seal.json",
    "hash-inventory.json",
    "verifier-input.json",
    "independent-verification.json",
}


def canonical(value: object) -> bytes:
    return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()


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


def sha256_file(path: Path) -> str:
    return sha256(path.read_bytes())


def assert_file(path: Path) -> None:
    info = path.lstat()
    assert not path.is_symlink() and stat.S_ISREG(info.st_mode) and info.st_nlink == 1
    assert stat.S_IMODE(info.st_mode) == 0o400, f"unsealed file: {path}"


def verify(root: Path) -> dict[str, object]:
    root = root.resolve()
    assert root.is_dir() and not root.is_symlink() and stat.S_IMODE(root.stat().st_mode) == 0o500
    for directory in (path for path in root.rglob("*") if path.is_dir()):
        assert not directory.is_symlink() and stat.S_IMODE(directory.stat().st_mode) == 0o500

    manifest_path = root / "candidate-manifest.json"
    inventory_path = root / "hash-inventory.json"
    seal_path = root / "candidate-seal.json"
    verifier_input_path = root / "verifier-input.json"
    for path in (manifest_path, inventory_path, seal_path, verifier_input_path):
        assert_file(path)
    manifest = cast(dict[str, object], json.loads(manifest_path.read_text()))
    inventory = cast(dict[str, object], json.loads(inventory_path.read_text()))
    seal = cast(dict[str, object], json.loads(seal_path.read_text()))
    verifier_input = cast(dict[str, object], json.loads(verifier_input_path.read_text()))

    full = str(manifest["full_candidate_digest"])
    manifest_core = dict(manifest)
    del manifest_core["full_candidate_digest"]
    assert sha256(canonical(manifest_core)) == full
    core_material = cast(dict[str, object], manifest["core_material"])
    assert sha256(canonical(core_material)) == manifest["core_candidate_digest"]
    assert seal == {
        "schema": "task26-token-rotation-successor-seal-v1",
        "status": "PASS_SEALED_UNDEPLOYED",
        "full_candidate_digest": full,
        "core_candidate_digest": manifest["core_candidate_digest"],
        "manifest_sha256": sha256_file(manifest_path),
        "inventory_sha256": sha256_file(inventory_path),
        "wheel_sha256": manifest["profile_wheel"]["sha256"],
        "predecessor_full_digest": manifest["predecessor"]["full_digest"],
        "verifier_sha256": manifest["verifier_sha256"],
    }
    assert verifier_input == {
        "schema": "task26-token-rotation-independent-verifier-input-v1",
        "full_candidate_digest": full,
        "manifest_sha256": sha256_file(manifest_path),
        "seal_sha256": sha256_file(seal_path),
    }

    entries = cast(list[dict[str, object]], inventory["entries"])
    indexed = {str(row["path"]) for row in entries}
    actual = {path.relative_to(root).as_posix() for path in root.rglob("*") if path.is_file()}
    allowed_controls = CONTROLS & actual
    assert actual == indexed | allowed_controls, sorted(actual ^ (indexed | allowed_controls))
    assert len(indexed) == len(entries)
    for row in entries:
        path = root / str(row["path"])
        assert_file(path)
        assert path.stat().st_size == row["bytes"] and sha256_file(path) == row["sha256"]
    assert inventory["entry_count"] == len(entries)
    assert inventory["entries_sha256"] == sha256(canonical(entries))
    assert sha256_file(inventory_path) == manifest["inventory_sha256"]

    predecessor = root / "historical/predecessor-candidate"
    predecessor_info = cast(dict[str, object], manifest["predecessor"])
    predecessor_files = [path for path in predecessor.rglob("*") if path.is_file()]
    assert len(predecessor_files) == predecessor_info["file_count"]
    assert sha256_file(predecessor / "candidate-manifest.json") == predecessor_info["manifest_sha256"]
    prior_manifest = json.loads((predecessor / "candidate-manifest.json").read_text())
    assert prior_manifest["full_candidate_digest"] == predecessor_info["full_digest"]
    assert sha256_file(predecessor / "artifacts/hermes_agent-0.17.0-py3-none-any.whl") == predecessor_info["wheel_sha256"]
    subprocess.run(
        [sys.executable, "-B", str(predecessor / "verify_candidate.py"), str(predecessor)],
        check=True,
        stdout=subprocess.DEVNULL,
    )

    wheel_info = cast(dict[str, object], manifest["profile_wheel"])
    wheel = root / str(wheel_info["path"])
    assert sha256_file(wheel) == wheel_info["sha256"] and wheel.stat().st_size == wheel_info["bytes"]
    source_root = root / "snapshot/package-runtime"
    with zipfile.ZipFile(wheel) as archive:
        infos = archive.infolist()
        assert len(infos) == wheel_info["member_count"]
        assert len({item.filename for item in infos}) == len(infos)
        assert {item.date_time for item in infos} == {(2000, 1, 1, 0, 0, 0)}
        package_members = {item.filename for item in infos if item.filename.startswith("checkin_cli/")}
        source_members = {
            path.relative_to(source_root).as_posix()
            for path in source_root.rglob("*")
            if path.is_file()
        }
        assert package_members == source_members
        for member in package_members:
            assert archive.read(member) == (source_root / member).read_bytes()
        assert "checkin_cli/activation_token_rotation_policy.py" in package_members

    delta = cast(dict[str, object], json.loads((root / "bindings/source-delta.json").read_text()))
    assert delta["predecessor_full_digest"] == predecessor_info["full_digest"]
    files = cast(list[dict[str, object]], delta["files"])
    assert [row["path"] for row in files] == [
        "checkin_cli/customer_admin.py",
        "checkin_cli/activation_token_rotation_policy.py",
        "tests/test_customer_admin.py",
    ]
    for row in files:
        after = root / "source-delta/after" / str(row["path"])
        assert sha256_file(after) == row["after_sha256"]
        if row["before_sha256"] is None:
            assert row["change"] == "added"
        else:
            before = root / "source-delta/before" / str(row["path"])
            assert sha256_file(before) == row["before_sha256"] and row["change"] == "modified"

    quality = cast(dict[str, object], json.loads((root / "artifacts/quality-summary.json").read_text()))
    assert quality["status"] == "PASS_WITH_PREEXISTING_STATIC_BASELINE"
    assert quality["focused_waiver_tests"] == {"passed": 43, "failed": 0, "deselected": 85}
    assert quality["full_package_tests"] == {"passed": 679, "failed": 0}
    assert quality["reproducible_wheel_builds"] == 2
    assert quality["wheel_builds_byte_identical"] is True
    assert quality["wheel_source_parity"] is True
    assert quality["new_module_ty_clean"] is True
    assert quality["ruff_regression_delta"] == 0
    assert quality["ty_diagnostic_delta"] == 0

    verification_path = root / "independent-verification.json"
    if verification_path.exists():
        assert_file(verification_path)
        receipt = json.loads(verification_path.read_text())
        assert receipt["status"] == "PASS" and receipt["full_candidate_digest"] == full
        assert receipt["seal_sha256"] == sha256_file(seal_path)

    return {
        "schema": "task26-token-rotation-successor-verification-v1",
        "status": "PASS",
        "state": "PASS_SEALED_UNDEPLOYED",
        "full_candidate_digest": full,
        "core_candidate_digest": manifest["core_candidate_digest"],
        "wheel_sha256": wheel_info["sha256"],
        "inventory_count": len(entries),
        "unindexed_count": 0,
        "predecessor_file_count": len(predecessor_files),
        "wheel_member_count": wheel_info["member_count"],
        "source_parity_count": wheel_info["package_member_count"],
    }


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("root", type=Path)
    args = parser.parse_args()
    try:
        result = verify(args.root)
    except Exception as exc:
        print(json.dumps({"status": "FAIL", "error": str(exc)}, sort_keys=True))
        return 1
    print(json.dumps(result, sort_keys=True))
    return 0


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