#!/usr/bin/env python3
"""Independently recompute the sealed Task22 session-classifier successor."""
from __future__ import annotations

import hashlib
import json
import os
import subprocess
import sys
from collections import Counter
from pathlib import Path
from typing import Final, TypeGuard, cast

EVIDENCE = (
    Path(sys.argv[2]).resolve()
    if len(sys.argv) == 3 and sys.argv[1] == "--evidence-root"
    else Path(__file__).resolve().parent
)
MANIFEST = EVIDENCE / "dualcoach-callback-adoption-successor-manifest.json"
FREEZE = EVIDENCE / "dualcoach-callback-adoption-successor-freeze-receipt.json"
PRE_ADOPTION = EVIDENCE / "dualcoach-task-22-pre-callback-adoption-evidence.json"
EXPECTED = {
    "candidate": "37a1b4ad915303a20510518580aef4e5add4a6b58579e069ae86ac471078f119",
    "package": "3090dc7ca9e93236394dd4da88647b100ea70b22f00080950a24e1d254f575e6",
    "manifest": "43d805a8b4d41c67277bc75fc55d7b0955bcef7ca3f239ccb844ed9e1f8801ee",
    "freeze": "bb91a72f58cfafa5b26de71e8c42ed11037b86afb1db68f38454b363051cffdc",
    "pre_adoption": "c62fe20d005c64854bc8f9950c768e6b0595c519f4e1dca5824c7bbee2b3f04d",
    "status": "f8a6731706568ea377f17d1511b6ce0372da3483e2b539a1b39dded8f7fed2dc",
    "diff": "487584ebb18fbc580e89e0f2e779334e88b3fc0a60b40b6ab33d3caa048c2a67",
    "inventory": "8d19835ad98c10f2b8b8ce1474e50364a21ea41121bdf14811e5732a59612aed",
    "snapshot": "7c94b088652501e7bf06f5d02343f2dcc487622bf69a7d9696eed001f7f12e2c",
    "transaction": "d5944005415def96b913d71d630d739eee84f12981bb4b9b1192c4320971b1bd",
}
SUPERVISED_PINS: Final = {
    "task22_child_protocol.py": "1dbbc8295042dd9dc18d804d99975c3c533f90fd1f8cfb88bc529c652770c5d3",
    "task22_dependency_closure.py": "9b04dd4749bc862d2805609a8191eb70f59164880b0f87dede256f4dee7cae95",
    "task22_launcher_runtime.py": "7c8c7360231bc8ee645fd7a19756d877036cafdfdb1986a05434c43595cb9100",
    "task22_launcher_test_support.py": "a06368b8d4198ff8e8c1065a0d4c62704f2e834f468587ca55798af94dbbc0ae",
    "task22_lifecycle.py": "4c4500af727ac9b75688ff63382498ee5807cb20443da01dc15d72dd38e1f72a",
    "task22_resource_ownership.py": "e47cdcfc9fe16bfe34260987c6c85e288ce5b285e86d8a4cce1f78ed84669e5e",
    "test_task22_launcher_complete_closure.py": "0ef7a088708a340ea9256ade97de032955669c29027fc84a53d1a255307f7a90",
    "test_task22_launcher_venv_fork.py": "3c76cdfca1584a8ecd4e4b19363a927d65c5863b80e68e44e2269e15e48327a6",
    "test_task22_parent_death_lifecycle.py": "c78385d62797961d7058c76a0844b8857f8e55fac0ec63a43892e146e1476550",
    "test_task22_pidfd_contract.py": "b55c34220537ce29858e2d2be369d20a0838d1482adf57fd462ba14d6bcbf483",
    "test_task22_pidfd_timeout.py": "42080ea1644c24acd21f79434da024f86d85e567ea11771334713f506d03dfc6",
    "test_task22_sealed_module_identity.py": "17e69145aead8776774cddc604392e6cf6c71515b600a8e00e9561d46711bad6",
    "test_task22_trainer_removal_launcher_hardening.py": "41ca3b127700f1574863fc2d2b83293f4451247c4a60dcae1815bd364def62b4",
}
CURRENT_WHEEL: Final = "bc7136b8614fb766cfba6b6017b61ab532479790f4bf2c259899272ceafc1e0d"
HISTORICAL_WHEEL: Final = "252f8669199b9701f9246e20045009b4be58df8f625368927a591a9f5b7b3db2"
PROFILE_WHEEL: Final = "104cfdda58eeb3487a075426598a3548fbb7a7d16f50eb81cd8a47e0fc090347"
SUCCESS: Final = frozenset({0})


def require(condition: bool, message: str) -> None:
    """Raise explicitly: verification must remain active under ``python -O``."""
    if not condition:
        raise RuntimeError(message)


def is_dict(value: object) -> TypeGuard[dict[str, object]]:
    if not isinstance(value, dict):
        return False
    return all(isinstance(key, str) for key in cast(dict[object, object], value))


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


def run(argv: list[str], cwd: Path, accepted: frozenset[int] = SUCCESS) -> bytes:
    environment = {**os.environ, "LANG": "C.UTF-8", "LC_ALL": "C.UTF-8"}
    result = subprocess.run(argv, cwd=cwd, env=environment, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if result.returncode not in accepted:
        raise RuntimeError((argv, result.returncode, result.stderr.decode(errors="replace")))
    return result.stdout


def main() -> int:
    require(
        len(sys.argv) in {1, 3}
        and (len(sys.argv) == 1 or sys.argv[1] == "--evidence-root"),
        "usage: verifier [--evidence-root PATH]",
    )
    manifest_bytes = MANIFEST.read_bytes()
    manifest_value = cast(object, json.loads(manifest_bytes))
    if not is_dict(manifest_value):
        raise RuntimeError("manifest must be a JSON object")
    manifest = manifest_value
    roots_value = manifest.get("roots")
    if not is_dict(roots_value):
        raise RuntimeError("manifest roots must be an object")
    roots: dict[str, Path] = {}
    for key, value in roots_value.items():
        if not isinstance(value, str):
            raise RuntimeError(f"manifest root {key} must be a string")
        roots[key] = Path(value)
    leaves_value = manifest.get("candidate_files")
    if not isinstance(leaves_value, list):
        raise RuntimeError("manifest candidate_files must be a list")
    leaves = cast(list[object], leaves_value)
    candidate = hashlib.sha256()
    package = hashlib.sha256()
    category_diff = hashlib.sha256()
    categories: Counter[str] = Counter()
    patch_bytes: Counter[str] = Counter()
    patch_codes: Counter[tuple[str, int]] = Counter()
    category_digests = {name: hashlib.sha256() for name in (
        "gateway-tracked", "gateway-untracked", "profile-package-external",
    )}
    previous: tuple[bytes, bytes] | None = None

    for index, leaf_value in enumerate(leaves):
        if not is_dict(leaf_value):
            raise RuntimeError(f"candidate leaf {index} must be an object")
        leaf = leaf_value
        root, path, category = leaf.get("root"), leaf.get("path"), leaf.get("diff_category")
        if not (
            isinstance(root, str)
            and isinstance(path, str)
            and isinstance(category, str)
        ):
            raise RuntimeError(f"candidate leaf {index} identity must contain strings")
        require(root in roots, f"candidate leaf {index} names an unknown root")
        key = root.encode(), path.encode()
        require(previous is None or key > previous, "candidate leaves are not strictly ordered")
        previous = key
        content = (roots[root] / path).read_bytes()
        require(len(content) == leaf.get("bytes"), f"candidate leaf {root}:{path} length changed")
        require(
            hashlib.sha256(content).hexdigest() == leaf.get("sha256"),
            f"candidate leaf {root}:{path} digest changed",
        )
        candidate.update(key[0] + b"\0" + key[1] + b"\0" + content + b"\0")
        if root == "profile_package":
            package.update(key[1] + b"\0" + content + b"\0")
        tracked = root == "gateway" and subprocess.run(
            ["git", "ls-files", "--error-unmatch", "--", path],
            cwd=roots[root], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
        ).returncode == 0
        expected_category = (
            "gateway-tracked" if tracked else
            "gateway-untracked" if root == "gateway" else
            "profile-package-external"
        )
        require(category == expected_category, f"candidate leaf {root}:{path} category changed")
        argv = (
            ["git", "diff", "--binary", "--no-ext-diff", "--no-textconv", "HEAD", "--", path]
            if tracked else
            ["git", "diff", "--no-index", "--binary", "--no-ext-diff", "--", "/dev/null", "./" + path]
        )
        accepted = frozenset({0}) if tracked else frozenset({0, 1})
        environment = {**os.environ, "LANG": "C.UTF-8", "LC_ALL": "C.UTF-8"}
        result = subprocess.run(argv, cwd=roots[root], env=environment, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        require(result.returncode in accepted and not result.stderr, f"candidate leaf {root}:{path} patch command failed")
        delta = result.stdout
        require(leaf.get("patch_return_code") == result.returncode, f"candidate leaf {root}:{path} patch return code changed")
        require(leaf.get("patch_bytes") == len(delta), f"candidate leaf {root}:{path} patch length changed")
        require(leaf.get("patch_sha256") == hashlib.sha256(delta).hexdigest(), f"candidate leaf {root}:{path} patch digest changed")
        for frame in (*key, category.encode(), delta):
            category_diff.update(frame + b"\0")
            category_digests[category].update(frame + b"\0")
        categories[category] += 1
        patch_bytes[category] += len(delta)
        patch_codes[(category, result.returncode)] += 1

    status = run(
        ["git", "status", "--porcelain=v1", "-z", "--untracked-files=all"],
        roots["gateway"],
    )
    status_records = [record for record in status.split(b"\0") if record]
    require(len(leaves) == manifest.get("candidate_path_count") == 173, "candidate leaf count changed")
    require(candidate.hexdigest() == manifest.get("candidate_digest") == EXPECTED["candidate"], "candidate digest changed")
    require(package.hexdigest() == manifest.get("profile_package_digest") == EXPECTED["package"], "profile package digest changed")
    require(category_diff.hexdigest() == manifest.get("candidate_diff_sha256") == EXPECTED["diff"], "candidate diff digest changed")
    require(dict(categories) == manifest.get("candidate_diff_category_counts"), "candidate diff categories changed")
    summary = {name: {"patch_digest": category_digests[name].hexdigest(), "patch_bytes": patch_bytes[name], "leaf_count": categories[name], "return_code_histogram": {str(code): count for (item, code), count in patch_codes.items() if item == name}} for name in sorted(categories)}
    require(summary == manifest.get("candidate_diff_categories"), "candidate diff category provenance changed")
    contract_value = manifest.get("candidate_diff_git_contract")
    if not is_dict(contract_value):
        raise RuntimeError("candidate diff Git contract must be an object")
    contract = contract_value
    require(contract.get("head") == run(["git", "rev-parse", "HEAD"], roots["gateway"]).decode().strip(), "candidate diff Git HEAD changed")
    require(contract.get("version") == run(["git", "--version"], roots["gateway"]).decode().strip() and contract.get("core.filemode") == run(["git", "config", "--get", "core.filemode"], roots["gateway"]).decode().strip(), "candidate diff Git contract changed")
    tracked_command = {"argv": ["git", "diff", "--binary", "--no-ext-diff", "--no-textconv", "HEAD", "--", "<relative-path>"], "accepted_return_codes": [0]}
    external_command = {"argv": ["git", "diff", "--no-index", "--binary", "--no-ext-diff", "--", "/dev/null", "./<relative-path>"], "accepted_return_codes": [0, 1]}
    require(contract.get("environment") == {"LANG": "C.UTF-8", "LC_ALL": "C.UTF-8"} and contract.get("gateway-tracked") == tracked_command, "candidate diff tracked command contract changed")
    require(contract.get("gateway-untracked") == external_command and contract.get("profile-package-external") == external_command, "candidate diff no-index command contract changed")
    require(len(status) == manifest.get("gateway_status_byte_length") == 46023, "gateway status byte length changed")
    require(len(status_records) == manifest.get("gateway_status_entry_count") == 992, "gateway status entry count changed")
    require(hashlib.sha256(status).hexdigest() == manifest.get("gateway_status_snapshot_sha256") == EXPECTED["status"], "gateway status digest changed")
    require(hashlib.sha256(manifest_bytes).hexdigest() == EXPECTED["manifest"], "successor manifest digest changed")

    pre_value = cast(object, json.loads(PRE_ADOPTION.read_bytes()))
    if not is_dict(pre_value):
        raise RuntimeError("pre-adoption evidence must be an object")
    pre = pre_value
    pre_unsigned = {key: value for key, value in pre.items() if key != "evidence_digest"}
    require(hashlib.sha256(canonical(pre_unsigned)).hexdigest() == pre.get("evidence_digest") == EXPECTED["pre_adoption"], "pre-adoption evidence digest changed")
    preflight = pre.get("preflight")
    if not is_dict(preflight):
        raise RuntimeError("pre-adoption preflight must be an object")
    require(preflight.get("exit_code") == 0 and preflight.get("stderr") == "", "pre-adoption preflight process failed")
    require(preflight.get("ready") is True and preflight.get("adopted") is False, "pre-adoption preflight state changed")
    require(preflight.get("mode") == "preflight" and preflight.get("execute_invoked") is False, "pre-adoption execution boundary changed")
    require(preflight.get("callback_record_count") == 0 and preflight.get("grants") == [], "pre-adoption callback authority changed")
    require(preflight.get("transaction_id") == EXPECTED["transaction"], "pre-adoption transaction changed")
    snapshot = pre.get("snapshot")
    if not is_dict(snapshot):
        raise RuntimeError("pre-adoption snapshot must be an object")
    require(snapshot.get("identical") is True and snapshot.get("records") == 3014, "pre-adoption snapshot shape changed")
    require(snapshot.get("before_sha256") == snapshot.get("after_sha256") == EXPECTED["snapshot"], "pre-adoption snapshot digest changed")
    inventory = pre.get("protected_process_inventory")
    require(hashlib.sha256(canonical(inventory)).hexdigest() == pre.get("protected_process_inventory_digest") == EXPECTED["inventory"], "protected process inventory changed")
    require(pre.get("protected_process_inventory_summary") == {
        "classes": {
            "kernel_fileless_zombie": 1,
            "openssh_session_monitor": 11,
            "stock_ssh_agent_service": 1,
            "systemd_user_sd_pam": 1,
            "user_systemd_manager": 1,
        },
        "records": 15,
    }, "protected process inventory summary changed")
    provenance = pre.get("capture_provenance")
    if not is_dict(provenance):
        raise RuntimeError("capture provenance must be an object")
    require(provenance.get("worker_task_id") == "st_019ff03e", "capture worker task changed")
    require(provenance.get("worker_session_id") == "019ff03e-aa9a-7e39-a0e9-2c73ff768e34", "capture worker session changed")
    quiescence = pre.get("quiescence_window")
    if not is_dict(quiescence):
        raise RuntimeError("quiescence window must be an object")
    require(quiescence.get("current_inventory_required_for_seal") is False, "quiescence inventory contract changed")
    require(manifest.get("pre_adoption_evidence_digest") == pre.get("evidence_digest"), "manifest pre-adoption binding changed")
    supervised = manifest.get("supervised_launcher_provenance")
    if not is_dict(supervised):
        raise RuntimeError("supervised launcher provenance must be an object")
    require(supervised.get("pins") == SUPERVISED_PINS, "supervised launcher artifact pins changed")
    require(supervised.get("processes_after_parent_death") == 0, "parent-death process residue changed")
    require(supervised.get("profile_unchanged") is True, "parent-death profile non-touch changed")
    wheel = manifest.get("wheel_provenance")
    if not is_dict(wheel):
        raise RuntimeError("wheel provenance must be an object")
    current_gateway = wheel.get("current_gateway")
    historical_gateway = wheel.get("historical_gateway")
    profile_wheel = wheel.get("profile")
    require(is_dict(current_gateway) and is_dict(historical_gateway) and is_dict(profile_wheel), "wheel provenance records changed")
    current_record = cast(dict[str, object], current_gateway)
    historical_record = cast(dict[str, object], historical_gateway)
    profile_record = cast(dict[str, object], profile_wheel)
    require(current_record.get("sha256") == CURRENT_WHEEL and current_record.get("members") == 991, "current gateway wheel changed")
    require(historical_record.get("sha256") == HISTORICAL_WHEEL and historical_record.get("members") == 991, "historical gateway wheel provenance changed")
    require(profile_record.get("sha256") == PROFILE_WHEEL and profile_record.get("members") == 50 and profile_record.get("preserved_exact") is True, "profile wheel changed")
    require(wheel.get("source_epochs") == [1700000000, 1800000000] and wheel.get("fixed_timestamp") == "2000-01-01T00:00:00Z", "wheel reproducibility contract changed")
    require(wheel.get("offline_install_import") is True, "wheel offline install/import proof changed")

    freeze_value = cast(object, json.loads(FREEZE.read_bytes()))
    if not is_dict(freeze_value):
        raise RuntimeError("freeze receipt must be an object")
    freeze = freeze_value
    freeze_unsigned = {key: value for key, value in freeze.items() if key != "receipt_digest"}
    require(hashlib.sha256(canonical(freeze_unsigned)).hexdigest() == freeze.get("receipt_digest") == EXPECTED["freeze"], "freeze receipt digest changed")
    require(freeze.get("release_candidate_digest") == manifest.get("candidate_digest"), "freeze candidate binding changed")
    require(freeze.get("profile_package_digest") == manifest.get("profile_package_digest"), "freeze package binding changed")
    require(freeze.get("manifest_digest") == EXPECTED["manifest"], "freeze manifest binding changed")
    require(freeze.get("pre_adoption_evidence_digest") == EXPECTED["pre_adoption"], "freeze pre-adoption binding changed")
    require(freeze.get("predecessor_freeze_receipt_digest") == "056e1c2cec6c0215878c73badcf482ef271f166e134fec1e2a775976e002b3d2", "freeze predecessor binding changed")
    require(freeze.get("supervised_launcher_digest_set") == {
        "parent_death_test": SUPERVISED_PINS["test_task22_parent_death_lifecycle.py"],
        "protocol": SUPERVISED_PINS["task22_child_protocol.py"],
        "runtime": SUPERVISED_PINS["task22_launcher_runtime.py"],
        "supervisor": SUPERVISED_PINS["task22_lifecycle.py"],
    }, "freeze supervised launcher binding changed")
    require(freeze.get("wheel_provenance") == {
        "current_gateway_members": 991,
        "current_gateway_sha256": CURRENT_WHEEL,
        "fixed_timestamp": "2000-01-01T00:00:00Z",
        "historical_gateway_sha256": HISTORICAL_WHEEL,
        "profile_members": 50,
        "profile_sha256": PROFILE_WHEEL,
        "source_epochs": [1700000000, 1800000000],
    }, "freeze wheel binding changed")

    output: dict[str, object] = {
        key: EXPECTED[key]
        for key in ("candidate", "package", "manifest", "freeze", "pre_adoption", "status", "diff")
    }
    output.update({
        "leaves": 173,
        "lifecycle": SUPERVISED_PINS["task22_lifecycle.py"],
        "parent_death": SUPERVISED_PINS["test_task22_parent_death_lifecycle.py"],
        "protocol": SUPERVISED_PINS["task22_child_protocol.py"],
        "runtime": SUPERVISED_PINS["task22_launcher_runtime.py"],
        "wheel": CURRENT_WHEEL,
    })
    print(json.dumps(output, sort_keys=True))
    return 0


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