#!/usr/bin/env python3
"""Create the offline Task23 expiry-supersession implementation seal.

Task22's terminal seal remains an immutable predecessor hash. Task23 seals its
Gateway leaves and the profile-workspace ``customer_admin`` source that
implements the narrow G1 historical-evidence exception. It never opens live
profile data, imports either package, contacts Telegram, or accesses services.
"""

from __future__ import annotations

import hashlib
import json
import os
import shutil
import subprocess
import sys
import tempfile
import zipfile
from pathlib import Path

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"
SOURCES = (
    "gateway/channel_directory.py",
    "gateway/platforms/telegram.py",
    "gateway/platforms/telegram_nutrition_onboarding_task23_supersession.py",
    "gateway/platforms/telegram_nutrition_onboarding_runtime.py",
    "gateway/platforms/telegram_nutrition_onboarding_runtime_authority.py",
    "gateway/platforms/telegram_nutrition_onboarding_runtime_callback.py",
    "gateway/platforms/telegram_room_bootstrap.py",
    "gateway/platforms/telegram_room_bootstrap_activation.py",
    "gateway/platforms/telegram_room_bootstrap_cutover.py",
    "scripts/nutrition-room-bootstrap",
    "tests/gateway/test_channel_directory.py",
    "tests/gateway/test_task23_supersession.py",
    "tests/gateway/test_telegram_room_bootstrap.py",
)
SOURCE_EPOCHS = (1700000000, 1800000000)
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 _run(argv: list[str], *, cwd: Path) -> bytes:
    result = subprocess.run(
        argv,
        cwd=cwd,
        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:
        output = _run(
            [
                "git",
                "diff",
                "--binary",
                "--no-ext-diff",
                "--no-textconv",
                "HEAD",
                "--",
                path,
            ],
            cwd=GATEWAY,
        )
        return "gateway-tracked", output, 0
    output = _run(
        [
            "git",
            "diff",
            "--no-index",
            "--binary",
            "--no-ext-diff",
            "--",
            "/dev/null",
            f"./{path}",
        ],
        cwd=GATEWAY,
    )
    return "gateway-untracked", output, 1


def _write(path: Path, value: object, mode: int) -> None:
    descriptor, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
    try:
        os.fchmod(descriptor, mode)
        data = json.dumps(value, ensure_ascii=False, indent=2).encode() + b"\n"
        with os.fdopen(descriptor, "wb", closefd=True) as handle:
            handle.write(data)
            handle.flush()
            os.fsync(handle.fileno())
        descriptor = -1
        os.replace(temporary, path)
        directory = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY | os.O_CLOEXEC)
        try:
            os.fsync(directory)
        finally:
            os.close(directory)
    finally:
        if descriptor >= 0:
            os.close(descriptor)
        try:
            os.unlink(temporary)
        except FileNotFoundError:
            pass


def _copy_source(destination: Path, epoch: int) -> None:
    ignored = shutil.ignore_patterns(
        ".git",
        ".venv",
        "build",
        ".gjc",
        ".pytest_cache",
        ".ruff_cache",
        "__pycache__",
        "*.pyc",
    )
    shutil.copytree(GATEWAY, destination, ignore=ignored)
    for path in sorted(destination.rglob("*")):
        if path.is_file():
            os.utime(path, (epoch, epoch), follow_symlinks=False)


def _build_wheel() -> tuple[bytes, dict[str, object]]:
    built: list[bytes] = []
    for epoch in SOURCE_EPOCHS:
        with tempfile.TemporaryDirectory(prefix="task23-wheel-") as temporary:
            root = Path(temporary)
            source = root / "source"
            output = root / "wheel"
            _copy_source(source, epoch)
            result = subprocess.run(
                [
                    str(GATEWAY / "scripts" / "reproducible-wheel-build"),
                    str(source),
                    str(output),
                ],
                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 != 0:
                raise RuntimeError(
                    result.stderr.decode().strip() or "offline wheel build failed"
                )
            wheels = tuple(output.glob("*.whl"))
            if len(wheels) != 1:
                raise RuntimeError("offline wheel build did not produce one wheel")
            built.append(wheels[0].read_bytes())
    if built[0] != built[1]:
        raise RuntimeError("distinct source mtimes produced different wheels")
    with tempfile.TemporaryDirectory(prefix="task23-wheel-index-") as temporary:
        candidate = Path(temporary) / "candidate.whl"
        candidate.write_bytes(built[0])
        with zipfile.ZipFile(candidate) 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()
            ]
    if any(item["date_time"] != [2000, 1, 1, 0, 0, 0] for item in members):
        raise RuntimeError("wheel ZIP timestamp is not reproducible")
    return built[0], {
        "sha256": _sha(built[0]),
        "bytes": len(built[0]),
        "members": len(members),
        "member_index_sha256": _sha(_canonical(members)),
    }


def main() -> int:
    if len(sys.argv) not in {1, 2} or sys.argv[1:] not in ([], ["--replace"]):
        raise SystemExit("usage: reseal-task23-expiry-supersession.py [--replace]")
    artifacts = (MANIFEST, WHEEL, WHEEL_RECEIPT, FREEZE)
    if any(path.exists() for path in artifacts):
        if sys.argv[1:] != ["--replace"]:
            raise RuntimeError("Task23 expiry supersession seal already exists")
        for path in artifacts:
            path.unlink(missing_ok=True)
    predecessor = PREDECESSOR.read_bytes()
    predecessor_value = json.loads(predecessor)
    if not isinstance(predecessor_value, dict) or not isinstance(
        predecessor_value.get("candidate_digest"), str
    ):
        raise RuntimeError("Task22 predecessor manifest is invalid")

    candidate = hashlib.sha256()
    diff = hashlib.sha256()
    leaves: list[dict[str, object]] = []
    for path in sorted(SOURCES):
        content = (GATEWAY / path).read_bytes()
        category, patch, return_code = _patch(path)
        leaves.append(
            {
                "path": path,
                "bytes": len(content),
                "sha256": _sha(content),
                "diff_category": category,
                "patch_bytes": len(patch),
                "patch_sha256": _sha(patch),
                "patch_return_code": return_code,
            }
        )
        candidate.update(b"gateway:" + path.encode() + b"\0" + content + b"\0")
        diff.update(path.encode() + b"\0" + category.encode() + b"\0" + patch + b"\0")
    profile_leaves, profile_candidate = _source_leaves(
        PROFILE_PACKAGE,
        PROFILE_SOURCES,
        namespace="profile-package",
    )
    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"
        )

    wheel, wheel_values = _build_wheel()
    WHEEL.write_bytes(wheel)
    WHEEL.chmod(0o444)
    status = _run(
        ["git", "status", "--porcelain=v1", "-z", "--untracked-files=all"], cwd=GATEWAY
    )
    manifest = {
        "schema": "task23-expiry-supersession-seal-v2",
        "task": "Task23 expired bootstrap supersession",
        "roots": {
            "gateway": str(GATEWAY),
            "profile_package": str(PROFILE_PACKAGE),
        },
        "predecessor": {
            "candidate_digest": predecessor_value["candidate_digest"],
            "manifest_sha256": _sha(predecessor),
            "classification": "Task22 terminal authority retained as immutable historical provenance",
        },
        "source_files": leaves,
        "candidate_digest": candidate.hexdigest(),
        "candidate_diff_sha256": diff.hexdigest(),
        "gateway_status_snapshot_sha256": _sha(status),
        "gateway_status_byte_length": len(status),
        "gateway_status_entry_count": len(
            [item for item in status.split(b"\0") if item]
        ),
        "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,
        },
        "wheel_provenance": {
            "filename": WHEEL.name,
            **wheel_values,
            "source_epochs": list(SOURCE_EPOCHS),
            "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,
        },
        "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,
        },
    }
    _write(MANIFEST, manifest, 0o600)
    manifest_sha = _sha(MANIFEST.read_bytes())
    wheel_receipt = {
        "schema": "task23-expiry-supersession-wheel-v2",
        "candidate_digest": manifest["candidate_digest"],
        "manifest_sha256": manifest_sha,
        "predecessor_manifest_sha256": _sha(predecessor),
        "wheel_filename": WHEEL.name,
        "wheel_sha256": wheel_values["sha256"],
        "wheel_bytes": wheel_values["bytes"],
        "wheel_members": wheel_values["members"],
        "wheel_member_index_sha256": wheel_values["member_index_sha256"],
        "source_epochs": list(SOURCE_EPOCHS),
        "profile_package_candidate_digest": profile_candidate,
        "reproduced_exactly": True,
        "live_profile_data_accessed": False,
    }
    wheel_receipt["receipt_digest"] = _sha(_canonical(wheel_receipt))
    _write(WHEEL_RECEIPT, wheel_receipt, 0o600)
    freeze = {
        "schema": "task23-expiry-supersession-freeze-v2",
        "candidate_digest": manifest["candidate_digest"],
        "manifest_sha256": manifest_sha,
        "wheel_receipt_sha256": _sha(WHEEL_RECEIPT.read_bytes()),
        "wheel_receipt_digest": wheel_receipt["receipt_digest"],
        "verification": manifest["verification"],
    }
    freeze["receipt_digest"] = _sha(_canonical(freeze))
    _write(FREEZE, freeze, 0o600)
    print(
        json.dumps(
            {
                "candidate": manifest["candidate_digest"],
                "manifest": manifest_sha,
                "wheel": wheel_values["sha256"],
                "wheel_receipt": wheel_receipt["receipt_digest"],
                "freeze": freeze["receipt_digest"],
                "paths": len(leaves),
                "profile_paths": len(profile_leaves),
                "mode": "offline-code-and-profile-package-task23-reseal",
            },
            sort_keys=True,
        )
    )
    return 0


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