"""Lifecycle mixin: VM creation, start, stop, pause, resume."""

from __future__ import annotations

import secrets
import time
from pathlib import Path
from typing import Any

import structlog

from proxym.virt._config import VMConfig
from proxym.virt._enums import VMBackend, VMState

logger = structlog.get_logger()


class _LifecycleMixin:
    """VM creation, start, stop, pause, resume."""

    def create_vm(self, config: VMConfig) -> VMConfig:
        """Create a new VM with overlay disk and virtiofs mount.

        Uses CloneBox if available, otherwise falls back to virsh.
        Automatically mounts projects_root via virtiofs.
        """
        if not config.id:
            # Include a random suffix to avoid collisions with existing libvirt domains.
            config.id = f"proxym-{int(time.time())}-{secrets.token_hex(3)}"

        # Default code mount to projects_root
        if not config.code_mount_host:
            config.code_mount_host = str(self.projects_root)

        # backend = self._resolve_backend(config)
        backend = VMBackend.VIRSH  # Force virsh for now
        config.backend = backend.value

        if backend == VMBackend.CLONEBOX:
            self._create_vm_clonebox(config)
        else:
            self._create_vm_virsh(config)

        config.state = VMState.STOPPED
        self.vms[config.id] = config
        self._save_config(config)
        logger.info("vm_created", id=config.id, name=config.name, backend=backend.value)
        return config

    def _prepare_clonebox_args(self, config: VMConfig, cb: Any) -> tuple[Path | None, dict[str, Any]]:
        """Build profile path and extra args for clonebox create."""
        profile_path = cb.get_profile_path(config.tool) if config.tool else None
        extra: dict[str, Any] = {}
        if config.memory_mb:
            extra["memory"] = str(config.memory_mb)
        if config.vcpus:
            extra["vcpus"] = str(config.vcpus)
        return profile_path, extra

    def _write_cloud_init_file(self, cloud_init_config: dict[str, Any]) -> Path | None:
        """Write cloud-init config to a temp YAML file; return path or None on failure."""
        import tempfile
        user_data_path = Path(tempfile.mktemp(suffix="-cloud-init.yaml"))
        try:
            import yaml
            user_data = {
                "write_files": cloud_init_config.get("write_files", []),
                "runcmd": cloud_init_config.get("runcmd", []),
            }
            user_data_path.write_text(yaml.safe_dump(user_data, default_flow_style=False))
            return user_data_path
        except ImportError:
            logger.warning("pyyaml_not_installed", hint="pip install pyyaml")
            if user_data_path.exists():
                user_data_path.unlink()
            return None

    def _create_vm_clonebox(self, config: VMConfig) -> None:
        """Create VM via clonebox CLI with injected cloud-init config."""
        cb = self._get_clonebox()
        if cb is None:
            raise RuntimeError("CloneBox not available")

        cloud_init_config = self._inject_vm_config(config)
        profile_path, extra = self._prepare_clonebox_args(config, cb)

        user_data_path: Path | None = None
        if cloud_init_config:
            user_data_path = self._write_cloud_init_file(cloud_init_config)
            if user_data_path:
                extra["cloud-init"] = str(user_data_path)

        try:
            result = cb.create_vm(config.id, profile_path=profile_path, extra_args=extra)
            if result.success:
                logger.info("clonebox_vm_created", vm_id=config.id, tool=config.tool,
                            has_cloud_init=user_data_path is not None)
            else:
                logger.error("clonebox_create_failed", error=result.stderr)
        finally:
            if user_data_path and user_data_path.exists():
                user_data_path.unlink()

    def _create_vm_virsh(self, config: VMConfig) -> None:
        """Create VM via direct virsh/qemu-img commands (fallback)."""
        overlay = self.overlays_dir / f"{config.id}.qcow2"
        base = self.images_dir / f"{config.base_image}.qcow2"

        if base.exists():
            result = self._run(f"qemu-img create -f qcow2 -b {base} -F qcow2 {overlay}")
            if result.returncode != 0:
                raise RuntimeError(f"Failed to create overlay: {result.stderr}")
            config.overlay_path = str(overlay)
        else:
            logger.warning("base_image_missing", image=config.base_image,
                          hint=f"Place {config.base_image}.qcow2 in {self.images_dir}")
            # Create empty overlay
            result = self._run(f"qemu-img create -f qcow2 {overlay} 10G")
            if result.returncode != 0:
                raise RuntimeError(f"Failed to create overlay: {result.stderr}")
            config.overlay_path = str(overlay)

        self._generate_domain_xml(config)

    def start_vm(self, vm_id: str) -> bool:
        """Start a VM using the appropriate backend."""
        vm = self.vms.get(vm_id)
        if not vm:
            return False

        backend = self._resolve_backend(vm)
        if backend == VMBackend.CLONEBOX:
            return self._start_vm_clonebox(vm, vm_id)

        # Pre-flight: check if domain is defined in libvirt
        check = self._run(f"virsh dominfo {vm.id}", check=False)
        if check.returncode != 0:
            logger.error("vm_not_defined_in_libvirt", id=vm_id,
                         hint="Domain not defined. Re-create the VM or define it manually.")
            raise RuntimeError(
                f"VM '{vm_id}' has no libvirt domain defined. "
                f"The VM config exists but was never fully provisioned. "
                f"Delete and re-create it, or run: virsh define <xml>"
            )

        result = self._run(f"virsh start {vm.id}", check=False)
        if result.returncode == 0:
            vm.state = VMState.RUNNING
            vm.last_started_at = time.time()
            self._save_config(vm)
            logger.info("vm_started", id=vm_id)
            return True

        logger.error("vm_start_failed", id=vm_id, error=result.stderr)
        return False

    def _start_vm_clonebox(self, vm: VMConfig, vm_id: str) -> bool:
        """Start VM via clonebox backend."""
        cb = self._get_clonebox()
        if not cb:
            return False
        result = cb.start_vm(vm.id)
        if result.success:
            vm.state = VMState.RUNNING
            vm.last_started_at = time.time()
            self._save_config(vm)
            return True
        logger.error("vm_start_failed", id=vm_id, error=result.stderr)
        return False

    def stop_vm(self, vm_id: str, force: bool = False) -> bool:
        """Stop a VM (graceful shutdown or force destroy)."""
        vm = self.vms.get(vm_id)
        if not vm:
            return False

        backend = self._resolve_backend(vm)
        if backend == VMBackend.CLONEBOX:
            return self._stop_vm_clonebox(vm)

        cmd = f"virsh destroy {vm.id}" if force else f"virsh shutdown {vm.id}"
        result = self._run(cmd, check=False)
        if result.returncode == 0:
            vm.state = VMState.STOPPED
            self._save_config(vm)
            logger.info("vm_stopped", id=vm_id, force=force)
            return True
        return False

    def _stop_vm_clonebox(self, vm: VMConfig) -> bool:
        """Stop VM via clonebox backend."""
        cb = self._get_clonebox()
        if not cb:
            return False
        result = cb.stop_vm(vm.id)
        if result.success:
            vm.state = VMState.STOPPED
            self._save_config(vm)
            return True
        return False

    def pause_vm(self, vm_id: str) -> bool:
        """Pause (suspend) a VM."""
        vm = self.vms.get(vm_id)
        if not vm:
            return False
        result = self._run(f"virsh suspend {vm.id}", check=False)
        if result.returncode == 0:
            vm.state = VMState.PAUSED
            self._save_config(vm)
            return True
        return False

    def resume_vm(self, vm_id: str) -> bool:
        """Resume a paused VM."""
        vm = self.vms.get(vm_id)
        if not vm:
            return False
        result = self._run(f"virsh resume {vm.id}", check=False)
        if result.returncode == 0:
            vm.state = VMState.RUNNING
            self._save_config(vm)
            return True
        return False
