"""Vultr VPS provisioning and deployment for Dobby."""

from __future__ import annotations

import asyncio
import os
import time
from dataclasses import dataclass
from typing import Any

import aiohttp

VULTR_API = "https://api.vultr.com/v2"

# Cloud-init script to bootstrap a Dobby VPS
CLOUD_INIT_TEMPLATE = """#!/bin/bash
set -euo pipefail

# Install system deps
apt-get update -qq
apt-get install -y -qq git curl build-essential libssl-dev libffi-dev python3-dev

# Install uv (fast Python package manager)
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"

# Install Python 3.12 via uv
uv python install 3.12

# Install dobby
uv tool install dobby --python 3.12 {install_source}

# Create systemd service
cat > /etc/systemd/system/dobby.service << 'UNIT'
[Unit]
Description=Dobby Meta-Agent Orchestrator
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/root
Environment=PATH=/root/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
ExecStart=/root/.local/bin/dobby start --config /root/dobby.yaml
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
UNIT

# Create default config
cat > /root/dobby.yaml << 'CONFIG'
host: "0.0.0.0"
port: 8420
max_concurrent_agents: 3
default_timeout: 3600
CONFIG

# Enable and start
systemctl daemon-reload
systemctl enable dobby
systemctl start dobby
"""


@dataclass
class VPSInfo:
    """Information about a provisioned VPS instance."""
    instance_id: str
    label: str
    ip: str
    region: str
    plan: str
    os_name: str
    status: str

    def to_dict(self) -> dict[str, Any]:
        return {
            "instance_id": self.instance_id,
            "label": self.label,
            "ip": self.ip,
            "region": self.region,
            "plan": self.plan,
            "os_name": self.os_name,
            "status": self.status,
        }

    @classmethod
    def from_dict(cls, d: dict[str, Any]) -> VPSInfo:
        return cls(
            instance_id=d["instance_id"],
            label=d["label"],
            ip=d["ip"],
            region=d["region"],
            plan=d["plan"],
            os_name=d.get("os_name", ""),
            status=d.get("status", "unknown"),
        )


class VultrProvisioner:
    """Provision and manage VPS instances on Vultr for Dobby deployment."""

    def __init__(self, api_key: str | None = None) -> None:
        self.api_key = api_key or os.environ.get("VULTR_API_KEY", "")
        if not self.api_key:
            raise ValueError(
                "Vultr API key required. Set VULTR_API_KEY env var or pass api_key."
            )

    def _headers(self) -> dict[str, str]:
        return {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
        }

    async def _request(
        self,
        method: str,
        path: str,
        json: dict | None = None,
    ) -> dict:
        url = f"{VULTR_API}{path}"
        async with aiohttp.ClientSession() as session, session.request(
            method, url, headers=self._headers(), json=json,
            timeout=aiohttp.ClientTimeout(total=30),
        ) as resp:
            if resp.status >= 400:
                body = await resp.text()
                raise RuntimeError(
                    f"Vultr API {method} {path} returned {resp.status}: {body}"
                )
            if resp.status == 204:
                return {}
            return await resp.json()

    async def list_regions(self) -> list[dict]:
        data = await self._request("GET", "/regions")
        return data.get("regions", [])

    async def list_plans(self, region: str = "ewr") -> list[dict]:
        data = await self._request("GET", "/plans?type=vc2&per_page=50")
        return [
            p for p in data.get("plans", [])
            if region in p.get("locations", [])
        ]

    async def list_os(self) -> list[dict]:
        data = await self._request("GET", "/os?per_page=100")
        return data.get("os", [])

    async def provision(
        self,
        label: str = "dobby-vps",
        region: str = "ewr",
        plan: str = "vc2-1c-1gb",
        os_id: int = 2284,  # Ubuntu 24.04 LTS x64
        install_source: str = "",
    ) -> VPSInfo:
        """Provision a new VPS instance with Dobby pre-installed.

        Args:
            label: Instance label/hostname.
            region: Vultr region code (default: ewr = New Jersey).
            plan: Vultr plan ID (default: vc2-1c-1gb = 1 vCPU, 1GB RAM).
            os_id: Vultr OS ID (default: 2284 = Ubuntu 24.04 LTS).
            install_source: Extra pip install source for dobby (e.g. git+https://...).
        """
        import base64

        user_data = CLOUD_INIT_TEMPLATE.format(
            install_source=install_source or "",
        )
        user_data_b64 = base64.b64encode(user_data.encode()).decode()

        payload = {
            "region": region,
            "plan": plan,
            "os_id": os_id,
            "label": label,
            "hostname": label,
            "user_data": user_data_b64,
            "backups": "disabled",
            "enable_ipv6": False,
        }

        data = await self._request("POST", "/instances", json=payload)
        instance = data.get("instance", {})

        return VPSInfo(
            instance_id=instance.get("id", ""),
            label=label,
            ip=instance.get("main_ip", "pending"),
            region=region,
            plan=plan,
            os_name="Ubuntu 24.04 LTS",
            status=instance.get("status", "pending"),
        )

    async def wait_for_active(
        self,
        instance_id: str,
        timeout: int = 300,
        poll_interval: int = 10,
    ) -> VPSInfo:
        """Poll until the instance is active and has an IP address."""
        deadline = time.time() + timeout
        while time.time() < deadline:
            data = await self._request("GET", f"/instances/{instance_id}")
            instance = data.get("instance", {})
            status = instance.get("status", "pending")
            ip = instance.get("main_ip", "")
            power = instance.get("power_status", "")

            if status == "active" and ip and ip != "0.0.0.0" and power == "running":
                return VPSInfo(
                    instance_id=instance_id,
                    label=instance.get("label", ""),
                    ip=ip,
                    region=instance.get("region", ""),
                    plan=instance.get("plan", ""),
                    os_name=instance.get("os", ""),
                    status="active",
                )
            await asyncio.sleep(poll_interval)

        raise TimeoutError(
            f"Instance {instance_id} did not become active within {timeout}s"
        )

    async def destroy(self, instance_id: str) -> None:
        """Destroy a VPS instance."""
        await self._request("DELETE", f"/instances/{instance_id}")

    async def get_instance(self, instance_id: str) -> VPSInfo:
        """Get info about an existing instance."""
        data = await self._request("GET", f"/instances/{instance_id}")
        instance = data.get("instance", {})
        return VPSInfo(
            instance_id=instance_id,
            label=instance.get("label", ""),
            ip=instance.get("main_ip", ""),
            region=instance.get("region", ""),
            plan=instance.get("plan", ""),
            os_name=instance.get("os", ""),
            status=instance.get("status", "unknown"),
        )

    async def list_instances(self, label_filter: str | None = None) -> list[VPSInfo]:
        """List all VPS instances, optionally filtered by label prefix."""
        data = await self._request("GET", "/instances?per_page=100")
        instances = []
        for inst in data.get("instances", []):
            if label_filter and not inst.get("label", "").startswith(label_filter):
                continue
            instances.append(VPSInfo(
                instance_id=inst.get("id", ""),
                label=inst.get("label", ""),
                ip=inst.get("main_ip", ""),
                region=inst.get("region", ""),
                plan=inst.get("plan", ""),
                os_name=inst.get("os", ""),
                status=inst.get("status", "unknown"),
            ))
        return instances
