#!/usr/bin/env python3
"""Finalize first-iteration legacy Windows lab evidence for readiness."""

from __future__ import annotations

import argparse
from datetime import datetime, timezone
import hashlib
import importlib.util
import json
import subprocess
import sys
from pathlib import Path
from typing import Any


ROOT = Path(__file__).resolve().parents[1]
TOOL_NAME = "legacy-windows-first-iteration-finalize"
INTAKE_PATH = ROOT / "scripts" / "legacy-windows-lab-intake.py"
READINESS_PATH = ROOT / "scripts" / "legacy-windows-first-iteration-readiness.py"
FINAL_SUMMARY_NAME = "legacy-windows-first-iteration-final-summary.json"
FINAL_SUMMARY_SCHEMA = "legacy-windows-first-iteration-final-summary"
PILOT_CHECKLIST_NAME = "legacy-windows-first-iteration-pilot-checklist.json"
PILOT_CHECKLIST_SCHEMA = "legacy-windows-first-iteration-pilot-checklist"
READINESS_PASSED_MESSAGE = "legacy Windows first-iteration readiness passed"


def _load_module(name: str, path: Path) -> Any:
    spec = importlib.util.spec_from_file_location(name, path)
    if spec is None or spec.loader is None:
        raise RuntimeError(f"cannot load {name}: {path}")
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


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


def _run_ip_command(command: list[str]) -> list[str]:
    try:
        result = subprocess.run(
            command,
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.DEVNULL,
            check=False,
        )
    except OSError:
        return []
    if result.returncode != 0:
        return []
    return [item.strip() for item in result.stdout.split() if item.strip()]


def _collect_host_ips() -> list[str]:
    ips: list[str] = []
    for interface in ("en0", "en1"):
        ips.extend(_run_ip_command(["ipconfig", "getifaddr", interface]))
    try:
        result = subprocess.run(
            ["ifconfig"],
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.DEVNULL,
            check=False,
        )
    except OSError:
        result = None
    if result is not None and result.returncode == 0:
        for line in result.stdout.splitlines():
            parts = line.strip().split()
            if len(parts) >= 2 and parts[0] == "inet":
                ips.append(parts[1])
    ips.extend(_run_ip_command(["hostname", "-I"]))
    return list(dict.fromkeys(ips))


def _mac_studio_errors(require_mac_studio: bool, host_ips: list[str]) -> list[str]:
    if not require_mac_studio or "10.0.10.41" in host_ips:
        return []
    ip_text = " ".join(host_ips).strip() or "none"
    return [f"retained evidence finalization must run on Mac-Studio 10.0.10.41 (ips={ip_text})"]


def _build_manifest_mac_studio_errors(path: Path, require_mac_studio: bool) -> list[str]:
    manifest, error = _load_json(path)
    if error:
        return [error]
    assert manifest is not None
    host_ips = manifest.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)
    ):
        return [f"{path.name}: build manifest host_ips must be a non-empty string list"]
    if require_mac_studio and "10.0.10.41" not in host_ips:
        return [f"{path.name}: build manifest host_ips must include Mac-Studio 10.0.10.41"]
    return []


def _smoke_os_kind(collect_root: Path) -> tuple[str, list[str]]:
    smoke_path = collect_root / "legacy-windows-lab-smoke.json"
    smoke, error = _load_json(smoke_path)
    if error:
        return "unknown", [error]
    assert smoke is not None
    intake = _load_module("legacy_windows_lab_intake", INTAKE_PATH)
    return intake._classify_legacy_os(smoke), []


def _smoke_hostname(collect_root: Path) -> str:
    smoke_path = collect_root / "legacy-windows-lab-smoke.json"
    smoke, error = _load_json(smoke_path)
    if error or smoke is None:
        return ""
    hostname = smoke.get("hostname")
    if not isinstance(hostname, str):
        return ""
    return hostname.strip()


def _same_windows_hostname(left: str, right: str) -> bool:
    return left.strip().lower() == right.strip().lower()


def _duplicate_lab_hostname_errors(win7_root: Path, server_root: Path) -> list[str]:
    win7_hostname = _smoke_hostname(win7_root)
    server_hostname = _smoke_hostname(server_root)
    if win7_hostname and server_hostname and _same_windows_hostname(win7_hostname, server_hostname):
        return ["Windows 7 SP1 and Windows Server 2008 R2 SP1 retained lab hostnames must be different"]
    return []


def _platform_errors(win7_root: Path, server_root: Path) -> list[str]:
    errors: list[str] = []
    win7_kind, win7_errors = _smoke_os_kind(win7_root)
    server_kind, server_errors = _smoke_os_kind(server_root)
    errors.extend(win7_errors)
    errors.extend(server_errors)
    if win7_kind != "windows_7_sp1":
        errors.append("--win7-collect-root must contain Windows 7 SP1 evidence")
    if server_kind != "server_2008_r2":
        errors.append("--server-2008-r2-collect-root must contain Windows Server 2008 R2 SP1 evidence")
    return errors


def _utc_timestamp() -> str:
    return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")


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 _write_final_summary(args: argparse.Namespace, summaries: list[dict[str, Any]]) -> Path:
    summary_path = args.output_dir / FINAL_SUMMARY_NAME
    platforms = []
    for item in summaries:
        bundle = Path(str(item.get("path") or ""))
        platforms.append(
            {
                "bundle": item.get("path"),
                "collector": item.get("collector") or {},
                "hostname": item.get("hostname"),
                "operator_context": item.get("operator_context") or {},
                "os_kind": item.get("os_kind"),
                "roles": item.get("roles") or [],
                "sha256": _sha256(bundle),
                "size_bytes": bundle.stat().st_size,
                "smoke_checks": item.get("smoke_checks") or {},
                "win10_bridge": item.get("win10_bridge") or {},
                "win10_probe": item.get("win10_probe") or {},
            }
        )
    payload = {
        "schema": FINAL_SUMMARY_SCHEMA,
        "status": "passed",
        "generated_at": _utc_timestamp(),
        "readiness": READINESS_PASSED_MESSAGE,
        "build_manifest": str(args.build_manifest),
        "build_manifest_sha256": _sha256(args.build_manifest),
        "build_manifest_size_bytes": args.build_manifest.stat().st_size,
        "notes": args.note or [],
        "platforms": platforms,
    }
    summary_path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
    summary_path.with_suffix(summary_path.suffix + ".sha256").write_text(
        f"{_sha256(summary_path)}  {summary_path.name}\n",
        encoding="utf-8",
    )
    return summary_path


def _write_pilot_checklist(args: argparse.Namespace, summaries: list[dict[str, Any]], summary_path: Path) -> Path:
    checklist_path = args.output_dir / PILOT_CHECKLIST_NAME
    host_by_platform = {
        str(item.get("os_kind") or ""): str(item.get("hostname") or "").strip()
        for item in summaries
    }
    items = [
        {
            "key": "windows_7_sp1_lab_evidence",
            "status": "passed",
            "evidence": host_by_platform.get("windows_7_sp1", ""),
            "description": "Windows 7 SP1 lab bundle passed retained readiness.",
        },
        {
            "key": "server_2008_r2_sp1_lab_evidence",
            "status": "passed",
            "evidence": host_by_platform.get("server_2008_r2", ""),
            "description": "Windows Server 2008 R2 SP1 lab bundle passed retained readiness.",
        },
        {
            "key": "final_summary_hash_sidecar",
            "status": "passed",
            "evidence": str(summary_path.with_suffix(summary_path.suffix + ".sha256")),
            "description": "Final summary checksum sidecar was written for retained evidence.",
        },
        {
            "key": "win10_bridge_media_url_redacted",
            "status": "passed",
            "evidence": host_by_platform.get("windows_7_sp1", ""),
            "description": "Windows 10 bridge retained log passed media URL redaction proof.",
        },
        {
            "key": "customer_machine_selected",
            "status": "pending",
            "description": "Record the single doctor-office pilot endpoint before customer enablement.",
        },
        {
            "key": "customer_scoped_enablement_approved",
            "status": "pending",
            "description": "Confirm enablement is customer-scoped and not a broad fleet rollout.",
        },
        {
            "key": "backup_or_image_rollback_confirmed",
            "status": "pending",
            "description": "Confirm backup evidence or image rollback exists for the customer endpoint.",
        },
        {
            "key": "vendor_software_reviewed",
            "status": "pending",
            "description": "Confirm the clinical/vendor software owner reviewed Windows 10 bridge risk.",
        },
        {
            "key": "clinical_equipment_reviewed",
            "status": "pending",
            "description": "Confirm connected clinical equipment and peripherals were reviewed.",
        },
        {
            "key": "customer_approval_recorded",
            "status": "pending",
            "description": "Record explicit customer approval before running the pilot on office equipment.",
        },
    ]
    payload = {
        "schema": PILOT_CHECKLIST_SCHEMA,
        "status": "lab_passed_customer_approval_required",
        "customer_pilot_ready": False,
        "generated_at": _utc_timestamp(),
        "readiness": READINESS_PASSED_MESSAGE,
        "build_manifest": str(args.build_manifest),
        "build_manifest_sha256": _sha256(args.build_manifest),
        "final_summary": str(summary_path),
        "final_summary_sha256": _sha256(summary_path),
        "items": items,
    }
    checklist_path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
    checklist_path.with_suffix(checklist_path.suffix + ".sha256").write_text(
        f"{_sha256(checklist_path)}  {checklist_path.name}\n",
        encoding="utf-8",
    )
    return checklist_path


def finalize(args: argparse.Namespace) -> tuple[list[dict[str, Any]], list[str]]:
    errors = _mac_studio_errors(
        bool(getattr(args, "require_mac_studio", False)),
        list(getattr(args, "host_ips", []) or []),
    )
    errors.extend(
        _build_manifest_mac_studio_errors(
            args.build_manifest,
            bool(getattr(args, "require_mac_studio", False)),
        )
    )
    if errors:
        return [], errors
    errors = _platform_errors(args.win7_collect_root, args.server_2008_r2_collect_root)
    errors.extend(_duplicate_lab_hostname_errors(args.win7_collect_root, args.server_2008_r2_collect_root))
    if errors:
        return [], errors

    intake = _load_module("legacy_windows_lab_intake", INTAKE_PATH)
    readiness = _load_module("legacy_windows_first_iteration_readiness", READINESS_PATH)
    args.output_dir.mkdir(parents=True, exist_ok=True)

    outputs: list[Path] = []
    for collect_root, note in (
        (args.win7_collect_root, args.note or []),
        (args.server_2008_r2_collect_root, args.note or []),
    ):
        intake_args = argparse.Namespace(
            collect_root=collect_root,
            build_manifest=args.build_manifest,
            output_dir=args.output_dir,
            output=None,
            hostname=None,
            win10_probe=None,
            command_history=None,
            note=note,
        )
        output, intake_errors = intake.intake(intake_args)
        if intake_errors:
            errors.extend(intake_errors)
            continue
        assert output is not None
        outputs.append(output)

    if errors:
        return [], errors
    summaries, readiness_errors = readiness.validate(outputs)
    if readiness_errors:
        return summaries, readiness_errors
    summary_path = _write_final_summary(args, summaries)
    _write_pilot_checklist(args, summaries, summary_path)
    return summaries, []


def main(argv: list[str]) -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--win7-collect-root", required=True, type=Path, help="Windows 7 SP1 legacy-windows-lab-evidence-* folder")
    parser.add_argument("--server-2008-r2-collect-root", required=True, type=Path, help="Windows Server 2008 R2 SP1 legacy-windows-lab-evidence-* folder")
    parser.add_argument("--build-manifest", required=True, type=Path, help="Mac-Studio build manifest from bin/")
    parser.add_argument("--output-dir", required=True, type=Path, help="Directory for retained two-platform evidence ZIPs")
    parser.add_argument("--require-mac-studio", action="store_true", help="Fail unless this finalizer is running on Mac-Studio 10.0.10.41")
    parser.add_argument("--note", action="append", default=[], help="Operator note to include in both evidence bundles")
    args = parser.parse_args(argv)
    args.host_ips = _collect_host_ips()

    summaries, errors = finalize(args)
    if errors:
        print("legacy Windows first-iteration finalize failed", file=sys.stderr)
        for error in errors:
            print(f"- {error}", file=sys.stderr)
        return 2

    win7 = next(item for item in summaries if item.get("os_kind") == "windows_7_sp1")
    server = next(item for item in summaries if item.get("os_kind") == "server_2008_r2")
    print(f"Windows 7 SP1: {win7['hostname']}")
    print(f"Windows Server 2008 R2 SP1: {server['hostname']}")
    print(READINESS_PASSED_MESSAGE)
    print(f"retained evidence directory: {args.output_dir}")
    print(f"final summary: {args.output_dir / FINAL_SUMMARY_NAME}")
    print(f"pilot checklist: {args.output_dir / PILOT_CHECKLIST_NAME}")
    return 0


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