#!/usr/bin/env python3
"""Independently verify the offline Task23 gateway and profile-package seal."""

from __future__ import annotations

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

EVIDENCE = Path(__file__).resolve().parent
GATEWAY = Path("/home/cube/projects/richard/hermes-agent")
PROFILE_PACKAGE = Path(
    "/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli"
)
PREDECESSOR = EVIDENCE / "dualcoach-task22-supplemental-compatibility-manifest.json"
MANIFEST = EVIDENCE / "dualcoach-task23-expiry-supersession-manifest.json"
WHEEL = EVIDENCE / "dualcoach-task23-expiry-supersession.whl"
WHEEL_RECEIPT = EVIDENCE / "dualcoach-task23-expiry-supersession-wheel.json"
FREEZE = EVIDENCE / "dualcoach-task23-expiry-supersession-freeze.json"
PROFILE_SOURCES = ("checkin_cli/customer_admin.py",)


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


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


def _object(path: Path) -> dict[str, object]:
    value = cast(object, json.loads(path.read_bytes()))
    if not isinstance(value, dict):
        raise RuntimeError(f"invalid JSON: {path.name}")
    return cast(dict[str, object], value)


def _run(argv: list[str]) -> bytes:
    result = subprocess.run(
        argv,
        cwd=GATEWAY,
        env={**os.environ, "LANG": "C.UTF-8", "LC_ALL": "C.UTF-8"},
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        check=False,
    )
    if result.returncode not in {0, 1} or result.stderr:
        raise RuntimeError(
            f"command failed: {argv!r}: {result.stderr.decode().strip()}"
        )
    return result.stdout


def _source_leaves(
    root: Path,
    paths: tuple[str, ...],
    *,
    namespace: str,
) -> tuple[list[dict[str, object]], str]:
    digest = hashlib.sha256()
    leaves: list[dict[str, object]] = []
    for path in sorted(paths):
        content = (root / path).read_bytes()
        leaves.append(
            {
                "path": path,
                "bytes": len(content),
                "sha256": _sha(content),
            }
        )
        digest.update(
            namespace.encode() + b":" + path.encode() + b"\0" + content + b"\0"
        )
    return leaves, digest.hexdigest()


def _patch(path: str) -> tuple[str, bytes, int]:
    tracked = (
        subprocess.run(
            ["git", "ls-files", "--error-unmatch", "--", path],
            cwd=GATEWAY,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
            check=False,
        ).returncode
        == 0
    )
    if tracked:
        return (
            "gateway-tracked",
            _run(
                [
                    "git",
                    "diff",
                    "--binary",
                    "--no-ext-diff",
                    "--no-textconv",
                    "HEAD",
                    "--",
                    path,
                ]
            ),
            0,
        )
    return (
        "gateway-untracked",
        _run(
            [
                "git",
                "diff",
                "--no-index",
                "--binary",
                "--no-ext-diff",
                "--",
                "/dev/null",
                f"./{path}",
            ]
        ),
        1,
    )


def _verify_wheel(manifest: dict[str, object], receipt: dict[str, object]) -> None:
    provenance = manifest.get("wheel_provenance")
    if not isinstance(provenance, dict):
        raise RuntimeError("wheel provenance is invalid")
    with zipfile.ZipFile(WHEEL) as archive:
        members = [
            {
                "filename": item.filename,
                "compress_size": item.compress_size,
                "file_size": item.file_size,
                "crc": item.CRC,
                "date_time": list(item.date_time),
            }
            for item in archive.infolist()
        ]
    expected = {
        "filename": WHEEL.name,
        "sha256": _sha(WHEEL.read_bytes()),
        "bytes": WHEEL.stat().st_size,
        "members": len(members),
        "member_index_sha256": _sha(_canonical(members)),
        "source_epochs": [1700000000, 1800000000],
        "builder_relative_path": "scripts/reproducible-wheel-build",
        "builder_sha256": _sha(
            (GATEWAY / "scripts" / "reproducible-wheel-build").read_bytes()
        ),
        "reproduced_exactly": True,
        "includes_profile_package_source": False,
        "live_profile_data_accessed": False,
    }
    if provenance != expected or any(
        member["date_time"] != [2000, 1, 1, 0, 0, 0] for member in members
    ):
        raise RuntimeError("wheel provenance does not match")
    unsigned = dict(receipt)
    digest = unsigned.pop("receipt_digest", None)
    expected_receipt = {
        "schema": "task23-expiry-supersession-wheel-v2",
        "candidate_digest": manifest.get("candidate_digest"),
        "manifest_sha256": _sha(MANIFEST.read_bytes()),
        "predecessor_manifest_sha256": _sha(PREDECESSOR.read_bytes()),
        "wheel_filename": WHEEL.name,
        "wheel_sha256": expected["sha256"],
        "wheel_bytes": expected["bytes"],
        "wheel_members": expected["members"],
        "wheel_member_index_sha256": expected["member_index_sha256"],
        "source_epochs": expected["source_epochs"],
        "profile_package_candidate_digest": manifest["profile_package"][
            "candidate_digest"
        ],
        "reproduced_exactly": True,
        "live_profile_data_accessed": False,
    }
    if unsigned != expected_receipt or digest != _sha(_canonical(unsigned)):
        raise RuntimeError("wheel receipt does not match")


def main() -> int:
    if len(sys.argv) != 1:
        raise SystemExit("usage: verify-task23-expiry-supersession.py")
    if (
        stat.S_IMODE(MANIFEST.stat().st_mode) != 0o600
        or stat.S_IMODE(WHEEL.stat().st_mode) != 0o444
        or stat.S_IMODE(WHEEL_RECEIPT.stat().st_mode) != 0o600
        or stat.S_IMODE(FREEZE.stat().st_mode) != 0o600
    ):
        raise RuntimeError("Task23 seal file permissions are invalid")
    manifest = _object(MANIFEST)
    predecessor = _object(PREDECESSOR)
    expected_predecessor = {
        "candidate_digest": predecessor.get("candidate_digest"),
        "manifest_sha256": _sha(PREDECESSOR.read_bytes()),
        "classification": "Task22 terminal authority retained as immutable historical provenance",
    }
    profile_leaves, profile_candidate = _source_leaves(
        PROFILE_PACKAGE,
        PROFILE_SOURCES,
        namespace="profile-package",
    )
    expected_profile_package = {
        "source_files": profile_leaves,
        "candidate_digest": profile_candidate,
        "accessed": True,
        "reason": (
            "Task23 G1 historical-evidence exception is implemented in "
            "profile-package source"
        ),
        "live_profile_data_accessed": False,
    }
    if (
        manifest.get("schema") != "task23-expiry-supersession-seal-v2"
        or manifest.get("roots")
        != {"gateway": str(GATEWAY), "profile_package": str(PROFILE_PACKAGE)}
        or manifest.get("predecessor") != expected_predecessor
        or manifest.get("profile_package") != expected_profile_package
        or manifest.get("verification")
        != {
            "mode": "offline-code-and-profile-package-task23-reseal",
            "live_profile_data_accessed": False,
            "profile_package_source_accessed": True,
            "telegram_accessed": False,
            "service_accessed": False,
        }
    ):
        raise RuntimeError("Task23 manifest binding is invalid")
    leaves = manifest.get("source_files")
    if not isinstance(leaves, list) or not leaves:
        raise RuntimeError("Task23 source list is invalid")
    candidate = hashlib.sha256()
    diff = hashlib.sha256()
    previous = ""
    paths: list[str] = []
    for raw in leaves:
        if not isinstance(raw, dict):
            raise RuntimeError("Task23 source leaf is invalid")
        leaf = cast(dict[str, object], raw)
        path = leaf.get("path")
        if not isinstance(path, str) or not path or path <= previous:
            raise RuntimeError("Task23 source order is invalid")
        previous = path
        content = (GATEWAY / path).read_bytes()
        category, patch, return_code = _patch(path)
        if leaf != {
            "path": path,
            "bytes": len(content),
            "sha256": _sha(content),
            "diff_category": category,
            "patch_bytes": len(patch),
            "patch_sha256": _sha(patch),
            "patch_return_code": return_code,
        }:
            raise RuntimeError(f"Task23 source leaf mismatch: {path}")
        paths.append(path)
        candidate.update(b"gateway:" + path.encode() + b"\0" + content + b"\0")
        diff.update(path.encode() + b"\0" + category.encode() + b"\0" + patch + b"\0")
    for leaf in profile_leaves:
        path = str(leaf["path"])
        candidate.update(
            b"profile-package:"
            + path.encode()
            + b"\0"
            + (PROFILE_PACKAGE / path).read_bytes()
            + b"\0"
        )
    status = _run(["git", "status", "--porcelain=v1", "-z", "--untracked-files=all"])
    if (
        candidate.hexdigest() != manifest.get("candidate_digest")
        or diff.hexdigest() != manifest.get("candidate_diff_sha256")
        or _sha(status) != manifest.get("gateway_status_snapshot_sha256")
        or len(status) != manifest.get("gateway_status_byte_length")
        or len([item for item in status.split(b"\0") if item])
        != manifest.get("gateway_status_entry_count")
    ):
        raise RuntimeError("Task23 source or repository-status seal drifted")
    receipt = _object(WHEEL_RECEIPT)
    _verify_wheel(manifest, receipt)
    freeze = _object(FREEZE)
    unsigned = dict(freeze)
    digest = unsigned.pop("receipt_digest", None)
    expected_freeze = {
        "schema": "task23-expiry-supersession-freeze-v2",
        "candidate_digest": manifest.get("candidate_digest"),
        "manifest_sha256": _sha(MANIFEST.read_bytes()),
        "wheel_receipt_sha256": _sha(WHEEL_RECEIPT.read_bytes()),
        "wheel_receipt_digest": receipt.get("receipt_digest"),
        "verification": manifest.get("verification"),
    }
    if unsigned != expected_freeze or digest != _sha(_canonical(unsigned)):
        raise RuntimeError("Task23 freeze receipt does not match")
    print(
        json.dumps(
            {
                "candidate": candidate.hexdigest(),
                "manifest": _sha(MANIFEST.read_bytes()),
                "wheel": receipt["wheel_sha256"],
                "wheel_receipt": receipt["receipt_digest"],
                "freeze": freeze["receipt_digest"],
                "paths": len(paths),
                "profile_paths": len(profile_leaves),
                "mode": "offline-code-and-profile-package-task23-verification",
            },
            sort_keys=True,
        )
    )
    return 0


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