"""Customer-facing progress reporting for LLMHosts infrastructure setup.

Provides a clean UX layer that ensures users NEVER see internal
implementation details like "Ollama", "winget", "qwen3:8b". They see
friendly progress about THEIR infrastructure.

Two implementations:
- ``RichProgressReporter`` -- uses Rich console with spinners and progress bars
- ``LogProgressReporter`` -- plain ``logger.info()`` output for ``--no-tui`` mode
"""

from __future__ import annotations

import logging
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# Progress phases
# ---------------------------------------------------------------------------


class ProgressPhase(Enum):
    """Phases of the infrastructure setup pipeline."""

    DETECT_HARDWARE = "detect_hardware"
    SETUP_ENGINE = "setup_engine"
    DOWNLOAD_MODEL = "download_model"
    START_PROXY = "start_proxy"
    CONNECT = "connect"
    READY = "ready"


# ---------------------------------------------------------------------------
# Progress event
# ---------------------------------------------------------------------------


@dataclass(frozen=True, slots=True)
class ProgressEvent:
    """A single progress update event.

    ``message`` is the customer-facing string -- it must NEVER contain
    internal implementation names like "Ollama", "winget", or model tags
    like "qwen3:8b".

    ``detail`` is optional debug-level information only shown in verbose mode.
    """

    phase: ProgressPhase
    message: str
    progress_pct: float | None = None  # 0.0-100.0; None = indeterminate
    detail: str | None = None


# ---------------------------------------------------------------------------
# Message mapping -- internal actions to customer-friendly messages
# ---------------------------------------------------------------------------

# These are the canonical customer-facing messages. All code that reports
# progress to users should use these or equivalent phrasing. Internal
# names like "Ollama", "winget", and specific model tags MUST NOT appear.

MESSAGES = {
    "check_engine": "Checking for inference engine...",
    "engine_found": "Inference engine already installed",
    "install_engine": "Setting up inference engine...",
    "install_engine_success": "Inference engine installed successfully",
    "install_engine_failed": "Inference engine installation failed",
    "install_engine_timeout": "Inference engine installation timed out",
    "install_engine_unsupported": "Automatic installation not available for this platform",
    "install_engine_manual_windows": "Please download the inference engine from: https://llmhosts.com/docs/install",
    "start_engine": "Starting inference engine...",
    "engine_started": "Inference engine started",
    "pull_model": "Downloading AI model for your hardware...",
    "pull_model_progress": "Downloading AI model for your hardware... {pct:.0f}% ({completed:.1f}/{total:.1f} GB)",
    "pull_model_complete": "AI model ready",
    "pull_model_complete_gpu": "AI model ready -- optimized for your {vram_gb:.0f}GB GPU",
    "pull_model_failed": "AI model download failed",
    "pull_model_timeout": "AI model download timed out (10 min limit)",
    "engine_blocked_security": "Security software is preventing the inference engine from starting. "
    "Please allow it in your security settings and try again (see llmhosts.com/docs/troubleshooting)",
    "no_gpu": "No GPU detected -- setting up CPU inference (slower but works)",
    "insufficient_disk": "Need {needed_gb:.1f}GB free, only {available_gb:.1f}GB available -- free up space and retry",
    "stale_drivers": "GPU driver v{version} is outdated -- update recommended (see llmhosts.com/drivers)",
    "preflight_disk": "Checking available disk space...",
    "preflight_vram": "Checking GPU compatibility...",
    "preflight_network": "Checking network connectivity...",
    "preflight_port": "Checking port availability...",
}


def customer_message(key: str, **kwargs: object) -> str:
    """Look up a customer-facing message and format it with the given values.

    Falls back to the raw key if the template is not found (should not happen
    in production -- indicates a missing mapping).
    """
    template = MESSAGES.get(key)
    if template is None:
        logger.warning("Missing customer message template: %s", key)
        return str(key)
    try:
        return template.format(**kwargs)
    except KeyError:
        return template


# ---------------------------------------------------------------------------
# Abstract reporter
# ---------------------------------------------------------------------------


class ProgressReporter(ABC):
    """Protocol for reporting progress events to the user."""

    @abstractmethod
    def report(self, event: ProgressEvent) -> None:
        """Report a progress event to the user."""

    @abstractmethod
    def finish(self) -> None:
        """Signal that all progress reporting is complete."""


# ---------------------------------------------------------------------------
# Rich console reporter
# ---------------------------------------------------------------------------


class RichProgressReporter(ProgressReporter):
    """Reports progress using Rich console with spinners and progress bars."""

    def __init__(self) -> None:
        from rich.console import Console

        self._console = Console()
        self._download_started = False

    def report(self, event: ProgressEvent) -> None:
        """Report a progress event using Rich formatting."""
        if event.phase == ProgressPhase.DOWNLOAD_MODEL and event.progress_pct is not None:
            # Show progress bar style for downloads
            pct = event.progress_pct
            bar_width = 30
            filled = int(bar_width * pct / 100)
            bar = "[green]" + "#" * filled + "[/green]" + "[dim]" + "-" * (bar_width - filled) + "[/dim]"
            self._console.print(f"\r  {bar} {event.message}", end="", highlight=False)
            if pct >= 100:
                self._console.print()  # newline after completion
                self._download_started = False
            else:
                self._download_started = True
        else:
            if self._download_started:
                self._console.print()  # newline after progress bar
                self._download_started = False
            phase_icon = _PHASE_ICONS.get(event.phase, "  ")
            self._console.print(f"  {phase_icon} {event.message}")

    def finish(self) -> None:
        """Signal completion -- ensures no dangling progress bar output."""
        if self._download_started:
            from rich.console import Console

            Console().print()
            self._download_started = False


_PHASE_ICONS: dict[ProgressPhase, str] = {
    ProgressPhase.DETECT_HARDWARE: "[cyan]>[/cyan]",
    ProgressPhase.SETUP_ENGINE: "[cyan]>[/cyan]",
    ProgressPhase.DOWNLOAD_MODEL: "[cyan]>[/cyan]",
    ProgressPhase.START_PROXY: "[cyan]>[/cyan]",
    ProgressPhase.CONNECT: "[cyan]>[/cyan]",
    ProgressPhase.READY: "[green]>[/green]",
}


# ---------------------------------------------------------------------------
# Log-only reporter (--no-tui mode)
# ---------------------------------------------------------------------------


class LogProgressReporter(ProgressReporter):
    """Reports progress using standard logging (for --no-tui / CI environments)."""

    def report(self, event: ProgressEvent) -> None:
        """Log the customer-facing message at INFO level."""
        logger.info(event.message)

    def finish(self) -> None:
        """No-op for log reporter."""


# ---------------------------------------------------------------------------
# Null reporter (for testing / silent operation)
# ---------------------------------------------------------------------------


@dataclass
class NullProgressReporter(ProgressReporter):
    """Collects events without displaying them. Useful for testing."""

    events: list[ProgressEvent] = field(default_factory=list)

    def report(self, event: ProgressEvent) -> None:
        self.events.append(event)

    def finish(self) -> None:
        pass
