"""Pre-flight checks before provisioning.

Validates system readiness (disk space, VRAM, network, port) and returns
customer-friendly blocker/warning messages. All messages use
:func:`~llmhosts.progress.customer_message` to ensure no internal
implementation details leak to the user.
"""

from __future__ import annotations

import logging
import shutil
import socket
from dataclasses import dataclass, field
from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from llmhosts.discovery.models import HardwareProfile

logger = logging.getLogger(__name__)

# Minimum model download size in GB (smallest model: qwen3:1.7b = 1.4 GB)
_MIN_MODEL_SIZE_GB = 1.4

# Overhead factor for model downloads (model size + 20%)
_OVERHEAD_FACTOR = 1.2

# Default proxy port
_DEFAULT_PORT = 4000


@dataclass
class PreflightResult:
    """Result of pre-flight checks.

    ``blockers`` prevent provisioning and must be resolved first.
    ``warnings`` allow provisioning to proceed but inform the user.

    All messages are customer-friendly (no internal implementation details).
    """

    blockers: list[str] = field(default_factory=list)
    warnings: list[str] = field(default_factory=list)

    @property
    def can_proceed(self) -> bool:
        """True if there are no blockers."""
        return len(self.blockers) == 0


class PreflightChecker:
    """Runs pre-flight checks before provisioning.

    Checks:
    - Disk space: model size + 20% overhead vs available space
    - VRAM sufficiency: does hardware support at least the smallest model
    - Network connectivity: can reach the model registry
    - Port conflicts: is the proxy port available
    """

    def __init__(self, port: int = _DEFAULT_PORT) -> None:
        self._port = port

    async def check_all(
        self,
        hardware: HardwareProfile,
        model_size_gb: float = _MIN_MODEL_SIZE_GB,
    ) -> PreflightResult:
        """Run all pre-flight checks and return results.

        Parameters
        ----------
        hardware:
            Detected hardware profile.
        model_size_gb:
            Expected model download size in GB (used for disk check).
        """
        result = PreflightResult()

        self._check_disk_space(hardware, model_size_gb, result)
        self._check_vram(hardware, result)
        await self._check_network(result)
        self._check_port(result)

        return result

    # ------------------------------------------------------------------
    # Individual checks
    # ------------------------------------------------------------------

    def _check_disk_space(
        self,
        hardware: HardwareProfile,
        model_size_gb: float,
        result: PreflightResult,
    ) -> None:
        """Check that enough disk space is available for the model download."""
        needed_gb = model_size_gb * _OVERHEAD_FACTOR

        # Use hardware profile if available, otherwise check directly
        disk_free_gb = hardware.disk_free_gb
        if disk_free_gb <= 0:
            # Fallback: check home directory
            try:
                usage = shutil.disk_usage(str(Path.home()))
                disk_free_gb = usage.free / (1024**3)
            except OSError:
                result.warnings.append("Could not check disk space -- proceeding anyway")
                return

        if disk_free_gb < needed_gb:
            result.blockers.append(
                f"Need {needed_gb:.1f}GB free, only {disk_free_gb:.1f}GB available -- free up space and retry"
            )
        elif disk_free_gb < needed_gb * 2:
            result.warnings.append(
                f"Disk space is tight ({disk_free_gb:.1f}GB free, need {needed_gb:.1f}GB) -- consider freeing up space"
            )

    def _check_vram(self, hardware: HardwareProfile, result: PreflightResult) -> None:
        """Check VRAM / RAM sufficiency for at least the smallest model."""
        if hardware.has_gpu:
            # Any GPU can run the smallest model
            if hardware.total_vram_mb < 512:
                result.warnings.append(
                    "GPU detected but with very little VRAM -- CPU inference will be used for larger models"
                )
        else:
            # CPU-only: need at least ~4GB RAM (smallest model uses ~2GB)
            if hardware.ram_total_gb < 4.0:
                result.blockers.append(
                    f"Only {hardware.ram_total_gb:.1f}GB RAM available -- at least 4GB required for AI inference"
                )
            else:
                result.warnings.append("No GPU detected -- setting up CPU inference (slower but works)")

    async def _check_network(self, result: PreflightResult) -> None:
        """Check network connectivity to the model registry."""
        try:
            import httpx

            async with httpx.AsyncClient(timeout=5.0) as client:
                # Check if we can reach the model registry
                resp = await client.head("https://registry.ollama.ai/v2/")
                if resp.status_code >= 500:
                    result.warnings.append("Model registry may be experiencing issues -- downloads might be slow")
        except Exception:
            result.warnings.append("Cannot reach model registry -- check your internet connection")

    def _check_port(self, result: PreflightResult) -> None:
        """Check if the proxy port is available."""
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.settimeout(1)
                s.bind(("0.0.0.0", self._port))  # nosec B104 -- port availability check, not a server
        except OSError:
            result.blockers.append(
                f"Port {self._port} is already in use -- stop the other process or use --port to pick another"
            )
