"""Onboarding manager -- first-run experience and baseline capture.

On first run the manager:

1. Checks whether onboarding is complete (``~/.llmhosts/onboarding.json``).
2. If not, captures a spending baseline.
3. Discovers local capabilities (hardware, Ollama models).
4. Generates personalised recommendations.
"""

from __future__ import annotations

import json
import logging
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, Field

if TYPE_CHECKING:
    from pathlib import Path

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Spending ranges -- midpoint mapping
# ---------------------------------------------------------------------------

_RANGE_MIDPOINTS: dict[str, float] = {
    "$0-50": 25.0,
    "$50-100": 75.0,
    "$100-200": 150.0,
    "$200+": 300.0,
}

_VALID_RANGES = list(_RANGE_MIDPOINTS.keys())

_VALID_PROVIDERS = ["openai", "anthropic", "google", "mistral", "openrouter", "groq", "together", "deepseek", "other"]

_VALID_USE_CASES = ["coding", "writing", "analysis", "chat", "image-gen", "data-science", "translation", "other"]

# ---------------------------------------------------------------------------
# Models
# ---------------------------------------------------------------------------


class SpendingBaseline(BaseModel):
    """User's current AI spending baseline, captured during onboarding."""

    monthly_range: str = "$0-50"
    monthly_estimate: float = 25.0
    providers_used: list[str] = Field(default_factory=list)
    primary_use_cases: list[str] = Field(default_factory=list)
    captured_at: datetime = Field(default_factory=lambda: datetime.now(tz=timezone.utc))


class Recommendation(BaseModel):
    """A personalised setup recommendation."""

    id: str
    title: str
    description: str
    action: str  # CLI command to run
    priority: int = 3  # 1 (highest) - 5 (lowest)
    category: str = "config"  # "model", "key", "config", "upgrade"


class OnboardingState(BaseModel):
    """Persisted state of the onboarding flow."""

    is_complete: bool = False
    completed_at: datetime | None = None
    baseline: SpendingBaseline | None = None
    recommendations: list[Recommendation] = Field(default_factory=list)
    hardware_detected: bool = False


# ---------------------------------------------------------------------------
# Manager
# ---------------------------------------------------------------------------


class OnboardingManager:
    """Manages first-run experience and baseline capture.

    On first run:

    1. Check if onboarding is complete (``~/.llmhosts/onboarding.json``).
    2. If not, capture spending baseline.
    3. Discover local capabilities.
    4. Generate personalised recommendations.
    """

    def __init__(self, data_dir: Path) -> None:
        self._data_dir = data_dir
        self._state_file = data_dir / "onboarding.json"

    # ------------------------------------------------------------------
    # State persistence
    # ------------------------------------------------------------------

    def is_complete(self) -> bool:
        """Check if onboarding has been completed."""
        state = self.get_state()
        return state.is_complete

    def get_state(self) -> OnboardingState:
        """Get current onboarding state from disk."""
        if not self._state_file.exists():
            return OnboardingState()
        try:
            raw = json.loads(self._state_file.read_text(encoding="utf-8"))
            return OnboardingState.model_validate(raw)
        except (json.JSONDecodeError, Exception) as exc:
            logger.warning("Failed to read onboarding state: %s", exc)
            return OnboardingState()

    def _save_state(self, state: OnboardingState) -> None:
        """Persist onboarding state to disk."""
        self._data_dir.mkdir(parents=True, exist_ok=True)
        self._state_file.write_text(
            state.model_dump_json(indent=2),
            encoding="utf-8",
        )
        logger.debug("Onboarding state saved to %s", self._state_file)

    # ------------------------------------------------------------------
    # Baseline capture
    # ------------------------------------------------------------------

    def set_baseline(self, monthly_spend: SpendingBaseline) -> None:
        """Record user's current AI spending baseline."""
        state = self.get_state()
        state.baseline = monthly_spend
        self._save_state(state)
        logger.info(
            "Spending baseline captured: %s/mo, providers=%s",
            monthly_spend.monthly_range,
            monthly_spend.providers_used,
        )

    # ------------------------------------------------------------------
    # Completion
    # ------------------------------------------------------------------

    def complete(self, recommendations_accepted: list[str]) -> None:
        """Mark onboarding as complete.

        Parameters
        ----------
        recommendations_accepted:
            List of recommendation IDs the user chose to act on.
        """
        state = self.get_state()
        state.is_complete = True
        state.completed_at = datetime.now(tz=timezone.utc)

        # Mark accepted recommendations
        accepted_set = set(recommendations_accepted)
        for rec in state.recommendations:
            if rec.id in accepted_set:
                rec.priority = 1  # boost accepted ones

        self._save_state(state)
        logger.info("Onboarding completed (accepted %d recommendations)", len(recommendations_accepted))

    # ------------------------------------------------------------------
    # Recommendation engine
    # ------------------------------------------------------------------

    def get_recommendations(
        self,
        hardware: Any | None = None,
        ollama_models: list[str] | None = None,
        keys: list[str] | None = None,
    ) -> list[Recommendation]:
        """Generate personalised setup recommendations.

        Parameters
        ----------
        hardware:
            A :class:`~llmhost.discovery.models.HardwareProfile`, or *None*.
        ollama_models:
            List of Ollama model names currently installed.
        keys:
            List of cloud provider names with configured BYOK keys.

        Returns
        -------
        list[Recommendation]
            Prioritised list of recommendations.
        """
        recs: list[Recommendation] = []
        models = ollama_models or []
        configured_keys = keys or []

        # 1. No local models installed
        if not models:
            recs.append(
                Recommendation(
                    id="install-model",
                    title="Install a local model",
                    description=(
                        "No local models detected. Run LLMHosts to automatically "
                        "download the best AI model for your hardware."
                    ),
                    action="llmhosts serve",
                    priority=1,
                    category="model",
                )
            )

        # 2. No cloud keys
        if not configured_keys:
            recs.append(
                Recommendation(
                    id="add-cloud-key",
                    title="Add a cloud API key",
                    description=(
                        "Add at least one cloud API key for hybrid routing. "
                        "LLMHosts can intelligently route between local and cloud "
                        "based on model availability and cost."
                    ),
                    action="llmhosts keys add openai <your-key>",
                    priority=2,
                    category="key",
                )
            )

        # 3. Hardware-specific model recommendations
        if hardware is not None:
            total_vram = getattr(hardware, "total_vram_mb", 0)
            ram_gb = getattr(hardware, "ram_total_gb", 0.0)

            if total_vram >= 8000 and not any("codellama" in m.lower() or "coder" in m.lower() for m in models):
                recs.append(
                    Recommendation(
                        id="install-code-model",
                        title="Install a code model",
                        description=(
                            f"Your GPU has {total_vram // 1024}GB VRAM -- enough to run "
                            "a dedicated code model. Great for coding assistance."
                        ),
                        action="llmhosts serve",
                        priority=2,
                        category="model",
                    )
                )

            if total_vram >= 16000 and not any("70b" in m.lower() for m in models):
                recs.append(
                    Recommendation(
                        id="install-large-model",
                        title="Install a large model",
                        description=(
                            f"Your GPU has {total_vram // 1024}GB VRAM -- you can run "
                            "larger, more capable models for better quality."
                        ),
                        action="llmhosts serve",
                        priority=3,
                        category="model",
                    )
                )

            if total_vram == 0 and ram_gb >= 16:
                recs.append(
                    Recommendation(
                        id="cpu-model",
                        title="Install a CPU-optimised model",
                        description=(
                            "No GPU detected, but you have enough RAM for CPU inference. "
                            "Smaller quantised models work well on CPU."
                        ),
                        action="llmhosts serve",
                        priority=2,
                        category="model",
                    )
                )

        # 4. Multiple cloud keys -- suggest hybrid config
        if len(configured_keys) >= 2 and models:
            recs.append(
                Recommendation(
                    id="enable-hybrid",
                    title="Enable hybrid routing",
                    description=(
                        "You have both local models and cloud keys. LLMHosts will "
                        "automatically route to the cheapest option. Make sure "
                        "cost optimisation is enabled."
                    ),
                    action="llmhosts serve",
                    priority=3,
                    category="config",
                )
            )

        # 5. Smart tier recommendation
        if models and not _can_import("faiss"):
            recs.append(
                Recommendation(
                    id="upgrade-smart",
                    title="Upgrade to Smart tier",
                    description=(
                        "The Smart tier enables kNN + ModernBERT router for better "
                        "routing decisions. ~150MB additional install."
                    ),
                    action="pip install llmhosts[smart]",
                    priority=4,
                    category="upgrade",
                )
            )

        # 6. Baseline-specific recommendations
        state = self.get_state()
        if state.baseline is not None:
            if state.baseline.monthly_estimate >= 100 and not models:
                recs.append(
                    Recommendation(
                        id="savings-opportunity",
                        title="High savings opportunity",
                        description=(
                            f"You're spending ~${state.baseline.monthly_estimate:.0f}/mo on AI. "
                            "Running models locally could save you 70-90% of that."
                        ),
                        action="llmhosts serve",
                        priority=1,
                        category="model",
                    )
                )

            if "coding" in state.baseline.primary_use_cases and not any("code" in m.lower() for m in models):
                recs.append(
                    Recommendation(
                        id="code-use-case",
                        title="Optimise for coding",
                        description=(
                            "You primarily use AI for coding. Install a specialised "
                            "code model for faster, free coding assistance."
                        ),
                        action="llmhosts serve",
                        priority=2,
                        category="model",
                    )
                )

        # Sort by priority and save
        recs.sort(key=lambda r: r.priority)

        state = self.get_state()
        state.recommendations = recs
        state.hardware_detected = hardware is not None
        self._save_state(state)

        return recs


# ---------------------------------------------------------------------------
# Utility
# ---------------------------------------------------------------------------


def _can_import(module: str) -> bool:
    """Check if a Python module is importable."""
    import importlib

    try:
        importlib.import_module(module)
        return True
    except ImportError:
        return False
