"""VM readiness tool: polls GuestProperty until VM is truly booted.

Uses VBoxManage guestproperty enumerate to detect when Guest Additions
reports the VM is fully booted - not just powered on.

For Windows VMs, includes additional settling time to ensure the desktop
is truly usable for GUI automation, not just that the OS has booted.
"""

from __future__ import annotations

import asyncio
import time
from typing import TYPE_CHECKING, Annotated

from fastmcp.tools import tool
from pydantic import Field

from mcp_vm_blackbox._helpers import _resolve, _ssh_run
from mcp_vm_blackbox.models import VmReadyResult

if TYPE_CHECKING:
    from mcp_vm_blackbox.targets import TargetConfig

# Poll interval for GuestProperty checks
_POLL_INTERVAL_S: float = 2.5

# Minimum parts expected in a guestproperty line
_MIN_PROPERTY_PARTS: int = 2

# Default settling time for Windows desktop to become truly interactive
_WINDOWS_DESKTOP_SETTLE_S: float = 10.0

# Readiness indicator properties (checked in order)
# /VirtualBox/GuestAdd/Vbgl/SysNt/ServiceCurrent indicates Guest Additions service is running
# /VirtualBox/GuestInfo/Net/0/V4/IP indicates guest has acquired an IP
_READY_PROPERTIES: tuple[str, ...] = (
    "/VirtualBox/GuestAdd/Vbgl/SysNt/ServiceCurrent",
    "/VirtualBox/GuestInfo/Net/0/V4/IP",
)

# OS detection property
_OS_PRODUCT_PROPERTY: str = "/VirtualBox/GuestInfo/OS/Product"


async def _enumerate_guest_properties(cfg: TargetConfig, vm_name: str) -> dict[str, str]:
    """Run VBoxManage guestproperty enumerate and parse output.

    Args:
        cfg: Resolved TargetConfig.
        vm_name: VirtualBox VM name.

    Returns:
        Dict of property name -> value.
    """
    result = await _ssh_run(cfg, ["VBoxManage", "guestproperty", "enumerate", vm_name])
    if result.returncode != 0:
        return {}

    # Parse output like:
    # Name: /VirtualBox/GuestInfo/Net/0/V4/IP, value: 10.0.2.15, timestamp: ...
    properties: dict[str, str] = {}
    for raw_line in result.stdout.strip().splitlines():
        line = raw_line.strip()
        if not line or not line.startswith("Name:"):
            continue
        # Parse: Name: /path/to/prop, value: actual_value, timestamp: ...
        parts = line.split(", ")
        if len(parts) < _MIN_PROPERTY_PARTS:
            continue
        # Extract name
        name_part = parts[0]
        if ":" not in name_part:
            continue
        name = name_part.split(":", 1)[1].strip()
        # Extract value (may be empty or contain commas)
        value = ""
        for part in parts[1:]:
            if part.startswith("value:"):
                value = part.split(":", 1)[1].strip()
                break
        properties[name] = value

    return properties


def _check_readiness(properties: dict[str, str]) -> tuple[bool, str, str | None]:
    """Check if properties indicate VM is ready.

    Args:
        properties: Dict of GuestProperty name -> value.

    Returns:
        Tuple of (is_ready, strategy_used, os_product).
    """
    # Extract OS product for caller
    os_product = properties.get(_OS_PRODUCT_PROPERTY)

    for prop_name in _READY_PROPERTIES:
        if properties.get(prop_name):
            # ServiceCurrent has a numeric value, IP has the address
            strategy = "guest_additions_service" if "ServiceCurrent" in prop_name else "guest_info_ip"
            return True, strategy, os_product
    return False, "", os_product


@tool
async def vm_wait_ready(
    vm_name: Annotated[str, Field(description="VirtualBox VM name.")],
    timeout_s: Annotated[int, Field(description="Timeout in seconds (default 120).")] = 120,
    target: Annotated[
        str, Field(description='Named target ("local", "ci") or raw SSH string ("user@host").')
    ] = "local",
    desktop_settle_s: Annotated[
        float,
        Field(
            description="Additional settling time after basic readiness for GUI automation. Windows VMs get 10s by default. Set to 0 to skip."
        ),
    ] = -1.0,
) -> VmReadyResult:
    """Wait until a VM is truly booted (not just powered on).

    Polls VBoxManage guestproperty enumerate until Guest Additions reports
    the VM is ready. Checks for:
    - /VirtualBox/GuestAdd/Vbgl/SysNt/ServiceCurrent (Guest Additions service running)
    - /VirtualBox/GuestInfo/Net/0/V4/IP (Guest has acquired an IP)

    For Windows VMs, adds a settling period after basic readiness signals are
    detected to ensure the desktop is truly usable for GUI automation.

    Args:
        vm_name: VirtualBox VM name as shown in ``VBoxManage list vms``.
        timeout_s: Maximum seconds to wait for readiness (default 120).
        target: Named target or raw SSH string.
        desktop_settle_s: Additional settling time for GUI automation.
            -1 (default): Auto-detect based on OS (10s for Windows, 0s for others).
            0: No additional settling time.
            >0: Use specified value.

    Returns:
        VmReadyResult with status, elapsed_s, strategy, properties found,
        os_product, and blocking_reason on failure.
    """
    cfg = _resolve(target)
    start_time = time.monotonic()

    while True:
        elapsed = time.monotonic() - start_time

        if elapsed >= timeout_s:
            return VmReadyResult(
                status="error",
                vm_name=vm_name,
                elapsed_s=elapsed,
                strategy="",
                properties={},
                error=f"Timeout after {elapsed:.1f}s: VM not ready within {timeout_s}s",
                blocking_reason="timeout",
            )

        # Try to enumerate guest properties
        properties = await _enumerate_guest_properties(cfg, vm_name)

        # If empty, VM may not be running or Guest Additions not installed
        if not properties:
            # Check if VM is running at all
            result = await _ssh_run(cfg, ["VBoxManage", "showvminfo", vm_name, "--machinereadable"])
            if result.returncode != 0:
                return VmReadyResult(
                    status="error",
                    vm_name=vm_name,
                    elapsed_s=elapsed,
                    strategy="",
                    properties={},
                    error=f"VM '{vm_name}' not found or VBoxManage error: {result.stderr.strip()}",
                    blocking_reason="vm_not_found",
                )

            # Check VM state
            for raw_line in result.stdout.splitlines():
                if raw_line.startswith('VMState="'):
                    state = raw_line.split('"')[1]
                    if state != "running":
                        return VmReadyResult(
                            status="error",
                            vm_name=vm_name,
                            elapsed_s=elapsed,
                            strategy="",
                            properties={},
                            error=f"VM '{vm_name}' is not running (state: {state})",
                            blocking_reason="vm_not_running",
                        )
                    break

            # VM is running but no properties - Guest Additions may not be installed
            # Continue polling, but check timeout

        # Check readiness
        is_ready, strategy, os_product = _check_readiness(properties)
        if is_ready:
            # Determine settle time based on OS and parameter
            effective_settle_s = desktop_settle_s
            if desktop_settle_s < 0:
                # Auto-detect: Windows gets default settle time
                effective_settle_s = _WINDOWS_DESKTOP_SETTLE_S if os_product and "Windows" in os_product else 0.0

            # Apply settling time for GUI automation
            if effective_settle_s > 0:
                await asyncio.sleep(effective_settle_s)
                elapsed = time.monotonic() - start_time

            return VmReadyResult(
                status="ok",
                vm_name=vm_name,
                elapsed_s=elapsed,
                strategy=strategy,
                properties=properties,
                os_product=os_product,
                desktop_settle_s=effective_settle_s,
            )

        # Wait before next poll
        await asyncio.sleep(_POLL_INTERVAL_S)
