"""Direct Firecracker boot validation for a curated environment."""

from __future__ import annotations

import argparse
import shutil
import subprocess
import tempfile
import time
from dataclasses import dataclass
from pathlib import Path
from types import SimpleNamespace

from pyro_mcp.runtime import resolve_runtime_paths
from pyro_mcp.vm_environments import EnvironmentStore, get_environment
from pyro_mcp.vm_firecracker import build_launch_plan


@dataclass(frozen=True)
class BootCheckResult:
    environment: str
    workdir: Path
    firecracker_started: bool
    vm_alive_after_wait: bool
    process_returncode: int | None
    kernel_panic: bool
    failure_reason: str | None
    serial_log: str
    firecracker_log: str


def _read_text(path: Path) -> str:  # pragma: no cover - thin file helper
    if not path.exists():
        return ""
    return path.read_text(encoding="utf-8", errors="ignore")


def _classify_result(*, firecracker_log: str, serial_log: str, vm_alive: bool) -> str | None:
    if vm_alive:
        return None
    if "Could not initialize logger" in serial_log:
        return "firecracker logger initialization failed"
    if "Kernel panic" in serial_log:
        return "guest kernel panic during boot"
    if "Successfully started microvm" not in firecracker_log:
        return "firecracker did not fully start the microVM"
    return "microVM exited before boot validation window elapsed"


def run_boot_check(
    *,
    environment: str = "debian:12-base",
    vcpu_count: int = 1,
    mem_mib: int = 1024,
    wait_seconds: int = 8,
    keep_workdir: bool = False,
) -> BootCheckResult:  # pragma: no cover - integration helper
    get_environment(environment)
    if wait_seconds <= 0:
        raise ValueError("wait_seconds must be positive")

    runtime_paths = resolve_runtime_paths()
    environment_store = EnvironmentStore(runtime_paths=runtime_paths)
    installed_environment = environment_store.ensure_installed(environment)

    workdir = Path(tempfile.mkdtemp(prefix="pyro-boot-check-"))
    try:
        rootfs_copy = workdir / "rootfs.ext4"
        shutil.copy2(installed_environment.rootfs_image, rootfs_copy)
        instance = SimpleNamespace(
            vm_id="abcd00000001",
            vcpu_count=vcpu_count,
            mem_mib=mem_mib,
            workdir=workdir,
            metadata={
                "kernel_image": str(installed_environment.kernel_image),
                "rootfs_image": str(rootfs_copy),
            },
            network=None,
        )
        launch_plan = build_launch_plan(instance)
        serial_path = workdir / "serial.log"
        firecracker_log_path = workdir / "firecracker.log"
        firecracker_log_path.touch()

        with serial_path.open("w", encoding="utf-8") as serial_fp:
            proc = subprocess.Popen(  # noqa: S603
                [
                    str(runtime_paths.firecracker_bin),
                    "--no-api",
                    "--config-file",
                    str(launch_plan.config_path),
                    "--log-path",
                    str(firecracker_log_path),
                    "--level",
                    "Info",
                ],
                stdout=serial_fp,
                stderr=subprocess.STDOUT,
                text=True,
            )
            time.sleep(wait_seconds)
            vm_alive = proc.poll() is None
            process_returncode = proc.poll()
            if vm_alive:
                proc.kill()
                proc.wait(timeout=10)
                process_returncode = proc.returncode

        serial_log = _read_text(serial_path)
        firecracker_log = _read_text(firecracker_log_path)
        failure_reason = _classify_result(
            firecracker_log=firecracker_log,
            serial_log=serial_log,
            vm_alive=vm_alive,
        )
        return BootCheckResult(
            environment=environment,
            workdir=workdir,
            firecracker_started="Successfully started microvm" in firecracker_log,
            vm_alive_after_wait=vm_alive,
            process_returncode=process_returncode,
            kernel_panic="Kernel panic" in serial_log,
            failure_reason=failure_reason,
            serial_log=serial_log,
            firecracker_log=firecracker_log,
        )
    finally:
        if not keep_workdir:
            shutil.rmtree(workdir, ignore_errors=True)


def main() -> None:  # pragma: no cover - CLI wiring
    parser = argparse.ArgumentParser(description="Run a direct Firecracker boot check.")
    parser.add_argument("--environment", default="debian:12-base")
    parser.add_argument("--vcpu-count", type=int, default=1)
    parser.add_argument("--mem-mib", type=int, default=1024)
    parser.add_argument("--wait-seconds", type=int, default=8)
    parser.add_argument("--keep-workdir", action="store_true")
    parser.add_argument("-v", "--verbose", action="store_true")
    args = parser.parse_args()

    result = run_boot_check(
        environment=args.environment,
        vcpu_count=args.vcpu_count,
        mem_mib=args.mem_mib,
        wait_seconds=args.wait_seconds,
        keep_workdir=args.keep_workdir,
    )
    print(f"[boot] environment={result.environment}")
    print(f"[boot] firecracker_started={result.firecracker_started}")
    print(f"[boot] vm_alive_after_wait={result.vm_alive_after_wait}")
    print(f"[boot] process_returncode={result.process_returncode}")
    if result.failure_reason is None:
        print("[boot] result=success")
        return
    print(f"[boot] result=failure reason={result.failure_reason}")
    if args.keep_workdir:
        print(f"[boot] workdir={result.workdir}")
    if args.verbose:
        print("[serial]")
        print(result.serial_log.rstrip())
        print("[firecracker]")
        print(result.firecracker_log.rstrip())
    raise SystemExit(1)
