"""Deployment manager: orchestrates VPS provisioning and Dobby deployment."""

from __future__ import annotations

import asyncio
import os
from typing import Any

import yaml

from dobby.deploy.vultr import VPSInfo, VultrProvisioner

DEFAULT_CONFIG_PATH = "dobby.yaml"


class DeployManager:
    """High-level deployment orchestration for Dobby VPS instances."""

    def __init__(self, config_path: str = DEFAULT_CONFIG_PATH) -> None:
        self.config_path = config_path

    def _load_config(self) -> dict[str, Any]:
        if os.path.exists(self.config_path):
            with open(self.config_path) as f:
                return yaml.safe_load(f) or {}
        return {}

    def _save_config(self, data: dict[str, Any]) -> None:
        with open(self.config_path, "w") as f:
            yaml.dump(data, f, default_flow_style=False, sort_keys=False)

    def get_vps_info(self) -> VPSInfo | None:
        """Load VPS info from dobby.yaml if it exists."""
        cfg = self._load_config()
        vps_data = cfg.get("vps")
        if vps_data:
            return VPSInfo.from_dict(vps_data)
        return None

    def save_vps_info(self, vps: VPSInfo) -> None:
        """Persist VPS info to dobby.yaml."""
        cfg = self._load_config()
        cfg["vps"] = vps.to_dict()
        self._save_config(cfg)

    def remove_vps_info(self) -> None:
        """Remove VPS info from dobby.yaml."""
        cfg = self._load_config()
        cfg.pop("vps", None)
        self._save_config(cfg)

    async def provision(
        self,
        label: str = "dobby-vps",
        region: str = "ewr",
        plan: str = "vc2-1c-1gb",
        api_key: str | None = None,
        install_source: str = "",
    ) -> VPSInfo:
        """Provision a new VPS, wait for it to be active, and save info to config."""
        # Check if we already have a VPS before doing anything else
        existing = self.get_vps_info()
        if existing:
            raise RuntimeError(
                f"VPS already provisioned: {existing.instance_id} ({existing.ip}). "
                f"Run 'dobby deploy destroy' first."
            )

        provisioner = VultrProvisioner(api_key=api_key)
        vps = await provisioner.provision(
            label=label,
            region=region,
            plan=plan,
            install_source=install_source,
        )

        # Wait for the instance to become active
        vps = await provisioner.wait_for_active(vps.instance_id)

        # Save to config
        self.save_vps_info(vps)
        return vps

    async def destroy(self, api_key: str | None = None) -> None:
        """Destroy the provisioned VPS and remove from config."""
        vps = self.get_vps_info()
        if not vps:
            raise RuntimeError("No VPS found in config. Nothing to destroy.")

        provisioner = VultrProvisioner(api_key=api_key)
        await provisioner.destroy(vps.instance_id)
        self.remove_vps_info()

    async def status(self, api_key: str | None = None) -> VPSInfo | None:
        """Get current status of the deployed VPS."""
        vps = self.get_vps_info()
        if not vps:
            return None

        provisioner = VultrProvisioner(api_key=api_key)
        return await provisioner.get_instance(vps.instance_id)

    async def ssh_command(self, command: str) -> tuple[int, str]:
        """Execute a command on the remote VPS via SSH."""
        vps = self.get_vps_info()
        if not vps:
            raise RuntimeError("No VPS found in config.")

        return await run_ssh_command(vps.ip, command)

    async def verify_deployment(self) -> dict[str, Any]:
        """Verify that Dobby is running on the remote VPS."""
        vps = self.get_vps_info()
        if not vps:
            raise RuntimeError("No VPS found in config.")

        results: dict[str, Any] = {"ip": vps.ip, "checks": {}}

        # Check if SSH is reachable
        try:
            rc, output = await run_ssh_command(vps.ip, "echo ok")
            results["checks"]["ssh"] = rc == 0
        except Exception:
            results["checks"]["ssh"] = False
            return results

        # Check if dobby is installed
        rc, output = await run_ssh_command(vps.ip, "which dobby || echo not_found")
        results["checks"]["dobby_installed"] = "not_found" not in output

        # Check systemd service
        rc, output = await run_ssh_command(
            vps.ip, "systemctl is-active dobby 2>/dev/null || echo inactive"
        )
        results["checks"]["service_active"] = output.strip() == "active"

        # Check if API is responding
        rc, output = await run_ssh_command(
            vps.ip, "curl -sf http://127.0.0.1:8420/health || echo unhealthy"
        )
        results["checks"]["api_healthy"] = "unhealthy" not in output

        return results


async def run_ssh_command(
    host: str,
    command: str,
    user: str = "root",
    timeout: int = 30,
) -> tuple[int, str]:
    """Execute a command on a remote host via SSH.

    Returns (exit_code, combined_output).
    """
    ssh_args = [
        "ssh",
        "-o", "StrictHostKeyChecking=no",
        "-o", "UserKnownHostsFile=/dev/null",
        "-o", f"ConnectTimeout={timeout}",
        "-o", "BatchMode=yes",
        f"{user}@{host}",
        command,
    ]

    proc = await asyncio.create_subprocess_exec(
        *ssh_args,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.STDOUT,
    )
    stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=timeout + 5)
    output = stdout.decode(errors="replace") if stdout else ""
    return proc.returncode or 0, output
