"""ArtifactInstaller: remote artifact installation on cluster nodes via SSH."""

from __future__ import annotations

import asyncio
import logging

from vmcluster_mcp.exec.ssh import SSHConnectionManager
from vmcluster_mcp.models import BuildArtifact, InstallNodeResult, InstallStatus

logger = logging.getLogger("vmcluster_mcp.artifact")


class ArtifactInstaller:
    """Install build artifacts on cluster nodes via SSH with four install modes.

    Modes:
        modprobe_path: Copy .ko files to /lib/modules/{kver}/extra/vmcluster/,
            run depmod -a. (default)
        make_install: Run 'make install' in the synced artifact directory.
        rpm: Find and install .rpm packages via 'rpm -ivh'.
        deb: Find and install .deb packages via 'dpkg -i'.
    """

    def __init__(self, ssh_manager: SSHConnectionManager) -> None:
        self._ssh = ssh_manager

    async def install_node(
        self,
        ip: str,
        port: int,
        user: str,
        key_path: str,
        artifact: BuildArtifact,
        dest_base: str,
        install_mode: str,
    ) -> InstallNodeResult:
        """Run the install command for one node.

        Args:
            ip: Node IP address.
            port: SSH port.
            user: SSH username.
            key_path: Path to SSH private key.
            artifact: The BuildArtifact to install.
            dest_base: Base directory where artifact was synced.
            install_mode: One of modprobe_path, make_install, rpm, deb.

        Returns:
            InstallNodeResult with exit_code, installed_module_paths, depmod_output.
        """
        dest_path = f"{dest_base}/{artifact.artifact_id}"
        if install_mode == "modprobe_path":
            return await self._install_modprobe_path(ip, port, user, key_path, artifact, dest_path)
        elif install_mode == "make_install":
            return await self._install_make(ip, port, user, key_path, dest_path)
        elif install_mode == "rpm":
            return await self._install_rpm(ip, port, user, key_path, dest_path)
        elif install_mode == "deb":
            return await self._install_deb(ip, port, user, key_path, dest_path)
        else:
            return InstallNodeResult(
                node_name=ip,
                exit_code=-1,
                installed_module_paths=[],
                depmod_output=f"Unknown install_mode: {install_mode}",
            )

    async def _install_modprobe_path(
        self,
        ip: str,
        port: int,
        user: str,
        key_path: str,
        artifact: BuildArtifact,
        dest_path: str,
    ) -> InstallNodeResult:
        kver = artifact.kernel_version
        cmd = (
            f"KVER={kver}\n"
            f"MODDIR=/lib/modules/$KVER/extra/vmcluster\n"
            f"mkdir -p $MODDIR\n"
            f"find {dest_path} -name '*.ko' -exec cp {{}} $MODDIR/ \\;\n"
            f"depmod -a $KVER\n"
            f"find $MODDIR -name '*.ko' -type f"
        )
        result = await self._ssh.execute(ip, port, user, key_path, cmd)
        installed = [
            line.strip()
            for line in result.stdout.splitlines()
            if line.strip().endswith(".ko")
        ]
        return InstallNodeResult(
            node_name=ip,
            exit_code=result.exit_code,
            installed_module_paths=installed,
            depmod_output=result.stderr,
        )

    async def _install_make(
        self,
        ip: str,
        port: int,
        user: str,
        key_path: str,
        dest_path: str,
    ) -> InstallNodeResult:
        cmd = f"cd {dest_path} && make install 2>&1"
        result = await self._ssh.execute(ip, port, user, key_path, cmd)
        return InstallNodeResult(
            node_name=ip,
            exit_code=result.exit_code,
            installed_module_paths=[],
            depmod_output=result.stdout,
        )

    async def _install_rpm(
        self,
        ip: str,
        port: int,
        user: str,
        key_path: str,
        dest_path: str,
    ) -> InstallNodeResult:
        cmd = (
            f"PKGS=$(find {dest_path} -name '*.rpm' -type f)\n"
            "if [ -n \"$PKGS\" ]; then rpm -ivh $PKGS 2>&1; "
            "else echo 'No .rpm files found'; exit 1; fi"
        )
        result = await self._ssh.execute(ip, port, user, key_path, cmd)
        return InstallNodeResult(
            node_name=ip,
            exit_code=result.exit_code,
            installed_module_paths=[],
            depmod_output=result.stdout,
        )

    async def _install_deb(
        self,
        ip: str,
        port: int,
        user: str,
        key_path: str,
        dest_path: str,
    ) -> InstallNodeResult:
        cmd = (
            f"PKGS=$(find {dest_path} -name '*.deb' -type f)\n"
            "if [ -n \"$PKGS\" ]; then dpkg -i $PKGS 2>&1; "
            "else echo 'No .deb files found'; exit 1; fi"
        )
        result = await self._ssh.execute(ip, port, user, key_path, cmd)
        return InstallNodeResult(
            node_name=ip,
            exit_code=result.exit_code,
            installed_module_paths=[],
            depmod_output=result.stdout,
        )

    async def install_all(
        self,
        nodes,
        ssh_spec,
        artifact: BuildArtifact,
        dest_base: str,
        install_mode: str,
    ) -> InstallStatus:
        """Install artifact on all target nodes concurrently (best-effort).

        Each node install runs independently; per-node failures do not abort
        other nodes. Exceptions are caught and reported as
        InstallNodeResult(exit_code=-1).

        Args:
            nodes: List of NodeSpec-like objects with name, ip attributes.
            ssh_spec: SshSpec-like object with key_path, user attributes.
            artifact: The BuildArtifact to install.
            dest_base: Base directory where artifact was synced.
            install_mode: One of modprobe_path, make_install, rpm, deb.

        Returns:
            InstallStatus with per_node results map keyed by node name.
        """

        async def _install_one(node) -> tuple[str, InstallNodeResult]:
            result = await self.install_node(
                ip=node.ip,
                port=22,
                user=ssh_spec.user,
                key_path=ssh_spec.key_path,
                artifact=artifact,
                dest_base=dest_base,
                install_mode=install_mode,
            )
            return node.name, result

        tasks = [_install_one(n) for n in nodes]
        outcomes = await asyncio.gather(*tasks, return_exceptions=True)

        per_node: dict[str, InstallNodeResult] = {}
        for node, outcome in zip(nodes, outcomes):
            if isinstance(outcome, BaseException):
                per_node[node.name] = InstallNodeResult(
                    node_name=node.name,
                    exit_code=-1,
                    installed_module_paths=[],
                    depmod_output=str(outcome),
                )
            else:
                name, install_result = outcome
                per_node[name] = InstallNodeResult(
                    node_name=name,
                    exit_code=install_result.exit_code,
                    installed_module_paths=install_result.installed_module_paths,
                    depmod_output=install_result.depmod_output,
                )

        return InstallStatus(per_node=per_node)
