#!/usr/bin/env python3
"""Create a ZIP lab handoff kit for the legacy Windows first iteration."""

from __future__ import annotations

import argparse
import hashlib
import json
import sys
import zipfile
from datetime import datetime, timezone
from pathlib import Path
from typing import Any


ROOT = Path(__file__).resolve().parents[1]

REQUIRED_ARTIFACTS = (
    ("agent_windows", "bin/sk-agent.exe"),
    ("recover_legacy", "bin/sk-recover-legacy.exe"),
    ("recover_legacy_version", "bin/sk-recover-legacy.exe.version"),
    ("checksums", "bin/sk-agent-checksums.txt"),
    ("build_manifest", "bin/legacy-windows-first-iteration-build-manifest.json"),
    ("legacy_agent_script", "client/sk-agent-legacy.ps1"),
    ("legacy_watchdog_script", "client/sk-watchdog-legacy.ps1"),
    ("legacy_tray_script", "client/sk-tray-legacy.ps1"),
    ("lab_smoke_script", "client/legacy-windows-lab-smoke.ps1"),
    ("lab_collect_script", "client/legacy-windows-lab-collect.ps1"),
    ("lab_run_script", "client/legacy-windows-lab-run.ps1"),
    ("win10_bridge_script", "client/upgrade_win10_media.ps1"),
    ("lab_evidence_checker", "scripts/legacy-windows-lab-evidence-check.py"),
    ("lab_intake_helper", "scripts/legacy-windows-lab-intake.py"),
    ("evidence_bundle_helper", "scripts/legacy-windows-first-iteration-evidence-bundle.py"),
    ("readiness_gate", "scripts/legacy-windows-first-iteration-readiness.py"),
    ("first_iteration_finalizer", "scripts/legacy-windows-first-iteration-finalize.py"),
    ("pilot_approval_helper", "scripts/legacy-windows-first-iteration-pilot-approval.py"),
    ("pilot_readiness_helper", "scripts/legacy-windows-first-iteration-pilot-readiness.py"),
    ("lab_kit_verify_helper", "scripts/legacy-windows-lab-kit-verify.py"),
    ("lab_kit_builder", "scripts/legacy-windows-lab-kit.py"),
    ("runbook", "docs/legacy-windows-first-iteration-runbook.md"),
    ("ship_note", "docs/ship-notes/2026-07-08-legacy-windows-first-iteration.md"),
)

CHECKSUM_SIDECAR_CONTRACT = {
    "format": "<sha256> <artifact-filename>",
    "token_count": 2,
    "rejects": [
        "missing_filename",
        "filename_mismatch",
        "extra_tokens",
    ],
}

BUILD_MANIFEST_REQUIRED_ARTIFACTS = (
    "bin/sk-agent.exe",
    "bin/sk-recover-legacy.exe",
    "bin/sk-recover-legacy.exe.version",
    "bin/sk-agent-checksums.txt",
    "client/sk-agent-legacy.ps1",
    "client/sk-watchdog-legacy.ps1",
    "client/sk-tray-legacy.ps1",
    "client/legacy-windows-lab-smoke.ps1",
    "client/legacy-windows-lab-collect.ps1",
    "client/legacy-windows-lab-run.ps1",
    "client/upgrade_win10_media.ps1",
    "scripts/legacy-windows-lab-evidence-check.py",
    "scripts/legacy-windows-lab-intake.py",
    "scripts/legacy-windows-first-iteration-evidence-bundle.py",
    "scripts/legacy-windows-first-iteration-readiness.py",
    "scripts/legacy-windows-first-iteration-finalize.py",
    "scripts/legacy-windows-first-iteration-pilot-approval.py",
    "scripts/legacy-windows-first-iteration-pilot-readiness.py",
    "scripts/legacy-windows-lab-kit-verify.py",
    "scripts/legacy-windows-lab-kit.py",
    "docs/legacy-windows-first-iteration-runbook.md",
    "docs/ship-notes/2026-07-08-legacy-windows-first-iteration.md",
)


def _readme_text() -> str:
    lines = [
        "SiteKings Legacy Windows First-Iteration Lab Kit",
        "",
        "Use this kit only on Windows 7 SP1 and Windows Server 2008 R2 SP1 lab endpoints.",
        "",
        "Transfer verification:",
        "1. Keep legacy-windows-lab-kit.zip, legacy-windows-lab-kit.zip.sha256, legacy-windows-lab-kit-verify.py, and legacy-windows-lab-kit-verify.py.sha256 together.",
        "2. All checksum sidecars must contain exactly two tokens: <sha256> <artifact-filename>.",
        "3. Missing filenames, filename mismatches, or extra tokens are not valid retained evidence.",
        "4. Verify the ZIP hash before extraction.",
        "5. If Python is available on the receiving machine, keep legacy-windows-lab-kit-verify.py and legacy-windows-lab-kit-verify.py.sha256 beside the ZIP and run: python legacy-windows-lab-kit-verify.py --kit legacy-windows-lab-kit.zip --require-verifier-sidecar",
        "6. Otherwise on Windows 7, run: certutil -hashfile legacy-windows-lab-kit.zip SHA256",
        "7. Compare the SHA256 output with legacy-windows-lab-kit.zip.sha256 before extraction.",
        "",
        "Endpoint evidence capture:",
        "1. Extract the ZIP on the lab endpoint.",
        "2. Open an elevated PowerShell prompt from the extracted folder.",
        "3. Run: powershell.exe -NoProfile -ExecutionPolicy Bypass -File payload\\client\\legacy-windows-lab-run.ps1 -ExercisePeerRecover -ExerciseSelfRescue -ApiKey <test-org-key>",
        "4. Confirm operator-context.json exists; it must prove the runner used an elevated PowerShell session.",
        "5. Confirm operator-context.json computer_name must match the retained bundle hostname.",
        "6. Confirm legacy-windows-lab-smoke.json reports process:sk-agent-legacy, process:sk-tray, watchdog.log, peer_recover, self_rescue, and os_kind.",
        "7. Confirm legacy-windows-lab-smoke.json and legacy-windows-lab-collector-manifest.json os_kind values must match: windows_7_sp1 for Windows 7 SP1, or server_2008_r2 for Windows Server 2008 R2 SP1.",
        "8. Confirm legacy-windows-lab-collector-manifest.json build_manifest_sha256 must match the retained build manifest.",
        "9. Confirm legacy-windows-lab-collector-manifest.json reports collector_exit: 0 and smoke_exit: 0.",
        "10. Copy the entire legacy-windows-lab-evidence-* folder back to Mac-Studio 10.0.10.41.",
        "",
        "Mac-Studio retained evidence finalization:",
        "1. Copy both the Windows 7 SP1 folder and the Windows Server 2008 R2 SP1 folder to Mac-Studio.",
        "2. From the release root, run:",
        "   scripts/legacy-windows-first-iteration-finalize.py --win7-collect-root <win7-folder> --server-2008-r2-collect-root <server-2008-r2-folder> --build-manifest bin/legacy-windows-first-iteration-build-manifest.json --output-dir retained-legacy-windows-evidence --require-mac-studio",
        "3. Retain both ZIPs plus legacy-windows-first-iteration-final-summary.json, legacy-windows-first-iteration-final-summary.json.sha256, legacy-windows-first-iteration-pilot-checklist.json, and legacy-windows-first-iteration-pilot-checklist.json.sha256.",
        "4. Runbook reminder: verify the final summary and pilot checklist hash sidecars before customer pilot approval.",
        "5. After customer approval is recorded, run scripts\\legacy-windows-first-iteration-pilot-approval.py to write legacy-windows-first-iteration-pilot-approval.json and legacy-windows-first-iteration-pilot-approval.json.sha256.",
        "6. Before touching the doctor-office endpoint, run scripts\\legacy-windows-first-iteration-pilot-readiness.py --approval retained-legacy-windows-evidence\\legacy-windows-first-iteration-pilot-approval.json.",
        "",
        "Windows 10 bridge:",
        "Run the Windows 10 bridge only on a sacrificial Windows 7 SP1 machine after backup, rollback, vendor software, and clinical equipment approvals are recorded.",
        "The bridge form/command must include non-empty evidence notes for backup evidence, rollback plan, vendor software review, and clinical equipment review.",
        "Retain win10_upgrade.log and confirm it contains Operator acknowledgements recorded and Operator evidence notes recorded.",
        "Do not run the Windows 10 bridge on Windows Server 2008 R2 SP1 for this first iteration.",
        "",
    ]
    return "\r\n".join(lines)


def _sha256(path: Path) -> str:
    h = hashlib.sha256()
    with path.open("rb") as f:
        for chunk in iter(lambda: f.read(1024 * 1024), b""):
            h.update(chunk)
    return h.hexdigest()


def _artifact_entry(root: Path, role: str, relative: str) -> dict[str, Any]:
    source = root / relative
    return {
        "role": role,
        "source_path": str(source),
        "archive_path": f"payload/{relative}",
        "sha256": _sha256(source),
        "size_bytes": source.stat().st_size,
    }


def _require_file(root: Path, relative: str) -> str | None:
    path = root / relative
    if not path.exists() or not path.is_file() or path.stat().st_size <= 0:
        return f"missing required lab kit artifact: {relative}"
    return None


def _is_sha256_hex(value: str) -> bool:
    if len(value) != 64:
        return False
    try:
        int(value, 16)
    except ValueError:
        return False
    return True


def _validate_build_manifest(path: Path, root: Path) -> list[str]:
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except Exception as exc:
        return [f"{path.name}: cannot read build manifest JSON: {exc}"]
    if not isinstance(payload, dict):
        return [f"{path.name}: build manifest root JSON value must be an object"]

    errors: list[str] = []
    if payload.get("schema") != "legacy-windows-first-iteration-build-manifest":
        errors.append(f"{path.name}: build manifest schema mismatch")
    if "go1.20" not in str(payload.get("legacy_go_version") or ""):
        errors.append(f"{path.name}: build manifest legacy_go_version must be Go 1.20")
    host_ips = payload.get("host_ips")
    if (
        not isinstance(host_ips, list)
        or not host_ips
        or any(not isinstance(item, str) or not item.strip() for item in host_ips)
    ):
        errors.append(f"{path.name}: build manifest host_ips must be a non-empty string list")
    elif "10.0.10.41" not in host_ips:
        errors.append(f"{path.name}: build manifest host_ips must include Mac-Studio 10.0.10.41")
    raw_artifacts = payload.get("artifacts")
    artifacts: dict[str, dict[str, Any]] = {}
    if not isinstance(raw_artifacts, list):
        errors.append(f"{path.name}: build manifest artifacts must be a list")
    else:
        seen_artifacts: set[str] = set()
        for item in raw_artifacts:
            if not isinstance(item, dict):
                errors.append(f"{path.name}: build manifest artifact entries must be objects")
                continue
            artifact_path = str(item.get("path") or "")
            if artifact_path in seen_artifacts:
                errors.append(f"{path.name}: build manifest duplicate artifact path: {artifact_path}")
            seen_artifacts.add(artifact_path)
            artifacts[artifact_path] = item
    for required in BUILD_MANIFEST_REQUIRED_ARTIFACTS:
        item = artifacts.get(required)
        if not item:
            errors.append(f"{path.name}: build manifest missing artifact metadata: {required}")
            continue
        raw_sha = item.get("sha256")
        sha = raw_sha.strip() if isinstance(raw_sha, str) else ""
        if not _is_sha256_hex(sha):
            errors.append(f"{path.name}: build manifest artifact sha256 must be a 64-character hex string: {required}")
        raw_size = item.get("size_bytes")
        if isinstance(raw_size, bool) or not isinstance(raw_size, int) or raw_size <= 0:
            errors.append(f"{path.name}: build manifest artifact size_bytes must be a positive integer: {required}")
        local_artifact = root / required
        if local_artifact.exists():
            if _is_sha256_hex(sha) and sha != _sha256(local_artifact):
                errors.append(f"{path.name}: build manifest artifact sha256 mismatch: {required}")
            if (
                isinstance(raw_size, int)
                and not isinstance(raw_size, bool)
                and raw_size > 0
                and raw_size != local_artifact.stat().st_size
            ):
                errors.append(f"{path.name}: build manifest artifact size_bytes mismatch: {required}")
    return errors


def build_lab_kit(args: argparse.Namespace) -> list[str]:
    root = args.root.resolve()
    errors: list[str] = []
    for _role, relative in REQUIRED_ARTIFACTS:
        error = _require_file(root, relative)
        if error:
            errors.append(error)
    if errors:
        return errors

    errors.extend(_validate_build_manifest(root / "bin/legacy-windows-first-iteration-build-manifest.json", root))
    if errors:
        return errors

    artifacts = [_artifact_entry(root, role, relative) for role, relative in REQUIRED_ARTIFACTS]
    manifest = {
        "schema": "legacy-windows-lab-kit",
        "generated_at": datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z"),
        "notes": args.note or [],
        "checksum_sidecar_contract": CHECKSUM_SIDECAR_CONTRACT,
        "artifacts": artifacts,
    }

    args.output.parent.mkdir(parents=True, exist_ok=True)
    with zipfile.ZipFile(args.output, "w", compression=zipfile.ZIP_DEFLATED) as zf:
        zf.writestr("manifest.json", json.dumps(manifest, indent=2, sort_keys=True) + "\n")
        zf.writestr("README-FIRST.txt", _readme_text())
        for _role, relative in REQUIRED_ARTIFACTS:
            zf.write(root / relative, f"payload/{relative}")
    checksum_path = args.output.with_suffix(args.output.suffix + ".sha256")
    checksum_path.write_text(f"{_sha256(args.output)}  {args.output.name}\n", encoding="utf-8")
    verifier_source = root / "scripts/legacy-windows-lab-kit-verify.py"
    verifier_output = args.output.with_name("legacy-windows-lab-kit-verify.py")
    verifier_output.write_text(verifier_source.read_text(encoding="utf-8"), encoding="utf-8")
    verifier_checksum_path = verifier_output.with_suffix(verifier_output.suffix + ".sha256")
    verifier_checksum_path.write_text(f"{_sha256(verifier_output)}  {verifier_output.name}\n", encoding="utf-8")
    return []


def main(argv: list[str]) -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--root", type=Path, default=ROOT, help="Release root containing bin/, client/, and docs/")
    parser.add_argument("--output", required=True, type=Path, help="Output ZIP path")
    parser.add_argument("--note", action="append", default=[], help="Operator note to include in manifest")
    args = parser.parse_args(argv)

    errors = build_lab_kit(args)
    if errors:
        print("legacy Windows lab kit failed", file=sys.stderr)
        for error in errors:
            print(f"- {error}", file=sys.stderr)
        return 2

    print(f"legacy Windows lab kit written: {args.output}")
    print(f"legacy Windows lab kit checksum written: {args.output.with_suffix(args.output.suffix + '.sha256')}")
    print(f"legacy Windows lab kit verifier written: {args.output.with_name('legacy-windows-lab-kit-verify.py')}")
    print(f"legacy Windows lab kit verifier checksum written: {args.output.with_name('legacy-windows-lab-kit-verify.py.sha256')}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main(sys.argv[1:]))
