"""Embedded runtime resolver and diagnostics."""

from __future__ import annotations

import hashlib
import importlib.resources as resources
import json
import os
import stat
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from pyro_mcp.vm_network import TapNetworkManager

DEFAULT_PLATFORM = "linux-x86_64"


@dataclass(frozen=True)
class RuntimePaths:
    """Resolved paths for bundled runtime components."""

    bundle_root: Path
    manifest_path: Path
    firecracker_bin: Path
    jailer_bin: Path
    guest_agent_path: Path | None
    guest_init_path: Path | None
    artifacts_dir: Path
    notice_path: Path
    manifest: dict[str, Any]


@dataclass(frozen=True)
class RuntimeCapabilities:
    """Feature flags inferred from the bundled runtime."""

    supports_vm_boot: bool
    supports_guest_exec: bool
    supports_guest_network: bool
    reason: str | None = None


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


def _ensure_executable(path: Path) -> None:
    mode = path.stat().st_mode
    if mode & stat.S_IXUSR:
        return
    path.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)


def _default_bundle_parent() -> Path:
    return Path(str(resources.files("pyro_mcp.runtime_bundle")))


def resolve_runtime_paths(
    *,
    platform: str = DEFAULT_PLATFORM,
    verify_checksums: bool = True,
) -> RuntimePaths:
    """Resolve and validate embedded runtime assets."""
    bundle_parent = Path(os.environ.get("PYRO_RUNTIME_BUNDLE_DIR", _default_bundle_parent()))
    bundle_root = bundle_parent / platform
    manifest_path = bundle_root / "manifest.json"
    notice_path = bundle_parent / "NOTICE"

    if not manifest_path.exists():
        raise RuntimeError(
            f"bundled runtime manifest not found at {manifest_path}; reinstall package or "
            "use a wheel that includes bundled runtime assets"
        )
    if not notice_path.exists():
        raise RuntimeError(f"runtime NOTICE file missing at {notice_path}")

    manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    if not isinstance(manifest, dict):
        raise RuntimeError("invalid runtime manifest format")

    binaries = manifest.get("binaries")
    if not isinstance(binaries, dict):
        raise RuntimeError("runtime manifest is missing `binaries`")
    firecracker_entry = binaries.get("firecracker")
    jailer_entry = binaries.get("jailer")
    if not isinstance(firecracker_entry, dict) or not isinstance(jailer_entry, dict):
        raise RuntimeError("runtime manifest does not define firecracker/jailer binaries")

    firecracker_bin = bundle_root / str(firecracker_entry.get("path", ""))
    jailer_bin = bundle_root / str(jailer_entry.get("path", ""))
    guest_agent_path: Path | None = None
    guest_init_path: Path | None = None
    guest = manifest.get("guest")
    if isinstance(guest, dict):
        agent_entry = guest.get("agent")
        if isinstance(agent_entry, dict):
            raw_agent_path = agent_entry.get("path")
            if isinstance(raw_agent_path, str):
                guest_agent_path = bundle_root / raw_agent_path
        init_entry = guest.get("init")
        if isinstance(init_entry, dict):
            raw_init_path = init_entry.get("path")
            if isinstance(raw_init_path, str):
                guest_init_path = bundle_root / raw_init_path
    artifacts_dir = bundle_root / "profiles"

    required_paths = [firecracker_bin, jailer_bin]
    if guest_agent_path is not None:
        required_paths.append(guest_agent_path)
    if guest_init_path is not None:
        required_paths.append(guest_init_path)

    for path in required_paths:
        if not path.exists():
            raise RuntimeError(f"runtime asset missing: {path}")

    _ensure_executable(firecracker_bin)
    _ensure_executable(jailer_bin)

    if verify_checksums:
        for entry in (firecracker_entry, jailer_entry):
            raw_path = entry.get("path")
            raw_hash = entry.get("sha256")
            if not isinstance(raw_path, str) or not isinstance(raw_hash, str):
                raise RuntimeError("runtime binary manifest entry is malformed")
            full_path = bundle_root / raw_path
            actual = _sha256(full_path)
            if actual != raw_hash:
                raise RuntimeError(
                    f"runtime checksum mismatch for {full_path}; expected {raw_hash}, got {actual}"
                )
        if isinstance(guest, dict):
            for entry_name, malformed_message in (
                ("agent", "runtime guest agent manifest entry is malformed"),
                ("init", "runtime guest init manifest entry is malformed"),
            ):
                guest_entry = guest.get(entry_name)
                if not isinstance(guest_entry, dict):
                    continue
                raw_path = guest_entry.get("path")
                raw_hash = guest_entry.get("sha256")
                if not isinstance(raw_path, str) or not isinstance(raw_hash, str):
                    raise RuntimeError(malformed_message)
                full_path = bundle_root / raw_path
                actual = _sha256(full_path)
                if actual != raw_hash:
                    raise RuntimeError(
                        f"runtime checksum mismatch for {full_path}; "
                        f"expected {raw_hash}, got {actual}"
                    )
    return RuntimePaths(
        bundle_root=bundle_root,
        manifest_path=manifest_path,
        firecracker_bin=firecracker_bin,
        jailer_bin=jailer_bin,
        guest_agent_path=guest_agent_path,
        guest_init_path=guest_init_path,
        artifacts_dir=artifacts_dir,
        notice_path=notice_path,
        manifest=manifest,
    )


def runtime_capabilities(paths: RuntimePaths) -> RuntimeCapabilities:
    """Infer what the current bundled runtime can actually do."""
    binary_text = paths.firecracker_bin.read_text(encoding="utf-8", errors="ignore")
    if "bundled firecracker shim" in binary_text:
        return RuntimeCapabilities(
            supports_vm_boot=False,
            supports_guest_exec=False,
            supports_guest_network=False,
            reason="bundled runtime uses shim firecracker/jailer binaries",
        )

    capabilities = paths.manifest.get("capabilities")
    if not isinstance(capabilities, dict):
        return RuntimeCapabilities(
            supports_vm_boot=False,
            supports_guest_exec=False,
            supports_guest_network=False,
            reason="runtime manifest does not declare guest boot/exec/network capabilities",
        )

    supports_vm_boot = bool(capabilities.get("vm_boot"))
    supports_guest_exec = bool(capabilities.get("guest_exec"))
    supports_guest_network = bool(capabilities.get("guest_network"))
    reason = None
    if not supports_vm_boot:
        reason = "runtime manifest does not advertise real VM boot support"
    return RuntimeCapabilities(
        supports_vm_boot=supports_vm_boot,
        supports_guest_exec=supports_guest_exec,
        supports_guest_network=supports_guest_network,
        reason=reason,
    )


def doctor_report(*, platform: str = DEFAULT_PLATFORM) -> dict[str, Any]:
    """Build a runtime diagnostics report."""
    report: dict[str, Any] = {
        "platform": platform,
        "runtime_ok": False,
        "issues": [],
        "kvm": {
            "exists": Path("/dev/kvm").exists(),
            "readable": os.access("/dev/kvm", os.R_OK),
            "writable": os.access("/dev/kvm", os.W_OK),
        },
        "networking": {
            "enabled_by_default": TapNetworkManager().enabled,
        },
    }
    network = TapNetworkManager.diagnostics()
    report["networking"].update(
        {
            "tun_available": network.tun_available,
            "ip_binary": network.ip_binary,
            "nft_binary": network.nft_binary,
            "iptables_binary": network.iptables_binary,
            "ip_forward_enabled": network.ip_forward_enabled,
        }
    )
    try:
        paths = resolve_runtime_paths(platform=platform, verify_checksums=True)
    except Exception as exc:  # noqa: BLE001
        report["issues"] = [str(exc)]
        return report

    capabilities = runtime_capabilities(paths)
    from pyro_mcp.vm_environments import EnvironmentStore

    environment_store = EnvironmentStore(runtime_paths=paths)
    report["runtime_ok"] = True
    report["runtime"] = {
        "bundle_root": str(paths.bundle_root),
        "manifest_path": str(paths.manifest_path),
        "firecracker_bin": str(paths.firecracker_bin),
        "jailer_bin": str(paths.jailer_bin),
        "guest_agent_path": str(paths.guest_agent_path) if paths.guest_agent_path else None,
        "guest_init_path": str(paths.guest_init_path) if paths.guest_init_path else None,
        "artifacts_dir": str(paths.artifacts_dir),
        "artifacts_present": paths.artifacts_dir.exists(),
        "notice_path": str(paths.notice_path),
        "bundle_version": paths.manifest.get("bundle_version"),
        "component_versions": paths.manifest.get("component_versions", {}),
        "capabilities": {
            "supports_vm_boot": capabilities.supports_vm_boot,
            "supports_guest_exec": capabilities.supports_guest_exec,
            "supports_guest_network": capabilities.supports_guest_network,
            "reason": capabilities.reason,
        },
        "catalog_version": environment_store.catalog_version,
        "cache_dir": str(environment_store.cache_dir),
        "environments": environment_store.list_environments(),
    }
    if not report["kvm"]["exists"]:
        report["issues"] = ["/dev/kvm is not available on this host"]
    return report
