"""
Auto-Setup — Zero-friction onboarding that detects local LLMs and spawns welcome agents.

Detects Ollama, recommends the best models from those available, spawns
a pair of welcome agents on a private channel, and tracks the user's setup
state so the frontend can render the right onboarding UI.

All stdlib + internal imports only.

Functions:
    detect_ollama          — Ping Ollama, return available models.
    get_recommended_models — Pick the best 2-3 models for a new user.
    spawn_welcome_agents   — Create a welcome channel and spawn agents with greeting messages.
    check_user_setup_status — Return the full onboarding state for a user.
"""

from __future__ import annotations

import json
import logging
import os
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Optional

from .llm_router import _OLLAMA_URL, _http_json, _probe_url, _PROBE_TIMEOUT, discover_backends
from .spawner import _post_to_channel, spawn_agent, list_agents, AGENTS_DIR

log = logging.getLogger("brynq.auto_setup")

# ---------------------------------------------------------------------------
# Model catalog — descriptions + priority order for recommendations
# ---------------------------------------------------------------------------

_MODEL_CATALOG: list[dict[str, str]] = [
    {
        "family": "llama3",
        "description": "Meta's Llama 3 — excellent all-rounder for chat, analysis, and code.",
    },
    {
        "family": "mistral",
        "description": "Mistral — fast and capable, great for writing and reasoning.",
    },
    {
        "family": "phi3",
        "description": "Microsoft Phi-3 — compact but surprisingly strong on logic and code.",
    },
    {
        "family": "gemma",
        "description": "Google Gemma — lightweight model optimized for helpfulness.",
    },
    {
        "family": "codellama",
        "description": "Code Llama — specialized for programming tasks and code generation.",
    },
]

# Priority order for recommendation — index = priority (lower is better)
_MODEL_PRIORITY = [entry["family"] for entry in _MODEL_CATALOG]


# ---------------------------------------------------------------------------
# a) detect_ollama
# ---------------------------------------------------------------------------


def detect_ollama() -> dict[str, Any]:
    """Check if Ollama is running and return available models.

    Returns a dict with keys:
        ``status``  — ``"running"``, ``"not_running"``, or ``"not_installed"``
        ``models``  — List of model name strings (empty if not running)
        ``url``     — The Ollama endpoint URL if running, else empty string
        ``error``   — Error message if detection failed, else None

    Detection logic:
        1. Ping ``http://localhost:11434`` — if unreachable, Ollama is either
           not installed or not running.  We check for the ``ollama`` binary
           on PATH to distinguish the two cases.
        2. If reachable, query ``/api/tags`` to enumerate loaded models.
    """
    result: dict[str, Any] = {
        "status": "not_installed",
        "models": [],
        "url": "",
        "error": None,
    }

    # Step 1: Is the Ollama server reachable?
    if not _probe_url(_OLLAMA_URL, timeout=_PROBE_TIMEOUT):
        # Server not reachable — is the binary on PATH?
        import shutil

        if shutil.which("ollama") is not None:
            result["status"] = "not_running"
            result["error"] = (
                "Ollama is installed but not running. "
                "Start it with: ollama serve"
            )
        else:
            result["status"] = "not_installed"
            result["error"] = (
                "Ollama is not installed. "
                "Get it at https://ollama.com/download"
            )
        return result

    # Step 2: Server is reachable — enumerate models
    result["url"] = _OLLAMA_URL
    result["status"] = "running"

    try:
        resp = _http_json("GET", f"{_OLLAMA_URL}/api/tags", timeout=_PROBE_TIMEOUT)
        models = [
            m.get("name", "") for m in resp.get("models", []) if m.get("name")
        ]
        result["models"] = models
    except RuntimeError as exc:
        # Server responded to root but /api/tags failed — still running, no models
        log.warning("Ollama reachable but /api/tags failed: %s", exc)
        result["error"] = f"Could not list models: {exc}"

    return result


# ---------------------------------------------------------------------------
# b) get_recommended_models
# ---------------------------------------------------------------------------


def get_recommended_models(
    available_models: list[str],
    max_recommendations: int = 3,
) -> list[dict[str, str]]:
    """Pick the best 2-3 models from the available Ollama models.

    Selection priority: llama3 > mistral > phi3 > gemma > codellama > any.

    Args:
        available_models: List of model name strings from Ollama (e.g.
                          ``["llama3:latest", "mistral:7b", "nomic-embed-text"]``).
        max_recommendations: Maximum number of models to recommend (default 3).

    Returns:
        List of dicts with ``name`` (the exact Ollama model name) and
        ``description`` (human-readable blurb).  Ordered by priority.
    """
    if not available_models:
        return []

    recommended: list[dict[str, str]] = []
    used_families: set[str] = set()

    # First pass: match models to known families in priority order
    for family in _MODEL_PRIORITY:
        if len(recommended) >= max_recommendations:
            break
        for model_name in available_models:
            # Match family name anywhere in the model string
            # e.g. "llama3:latest" matches "llama3", "codellama:7b" matches "codellama"
            base = model_name.split(":")[0].lower()
            if family in base and family not in used_families:
                catalog_entry = next(
                    (e for e in _MODEL_CATALOG if e["family"] == family), None
                )
                desc = catalog_entry["description"] if catalog_entry else f"{model_name} — local model."
                recommended.append({
                    "name": model_name,
                    "description": desc,
                })
                used_families.add(family)
                break

    # Second pass: fill remaining slots with any unmatched models
    for model_name in available_models:
        if len(recommended) >= max_recommendations:
            break
        if model_name not in [r["name"] for r in recommended]:
            recommended.append({
                "name": model_name,
                "description": f"{model_name} — local model available on your machine.",
            })

    return recommended


# ---------------------------------------------------------------------------
# c) spawn_welcome_agents
# ---------------------------------------------------------------------------


def spawn_welcome_agents(
    user_id: str,
    models: list[dict[str, str]],
) -> dict[str, Any]:
    """Spawn local agents for a new user and post welcome messages.

    Creates a ``#welcome-{user_id}`` channel, spawns agents using
    ``spawner.spawn_agent``, and posts staggered welcome messages so the
    user sees a natural conversation when they first open the app.

    Args:
        user_id: Unique user identifier (used in channel name and agent names).
        models:  List of model dicts from ``get_recommended_models()``.
                 Must have at least 1 entry with a ``"name"`` key.

    Returns:
        Dict with ``channel``, ``agents`` (list of spawn results), and
        ``messages_posted`` count.

    Raises:
        ValueError: If no models are provided.
    """
    if not models:
        raise ValueError("At least one model is required to spawn welcome agents.")

    channel = f"welcome-{user_id}"
    spawned: list[dict] = []
    messages_posted = 0

    # Spawn up to 2 agents (first two recommended models)
    agent_models = models[:2]

    for i, model_info in enumerate(agent_models):
        model_name = model_info["name"]
        # Derive a friendly agent name from the model
        agent_name = f"welcome-{user_id}-{model_name.split(':')[0]}"

        try:
            result = spawn_agent(
                name=agent_name,
                backend="ollama",
                model=model_name,
                channel=channel,
            )
            spawned.append(result)
        except RuntimeError as exc:
            log.warning(
                "Failed to spawn welcome agent '%s': %s", agent_name, exc
            )
            spawned.append({
                "name": agent_name,
                "pid": 0,
                "status": "failed",
                "error": str(exc),
            })

    # Post staggered welcome messages
    # These are posted directly to the channel file so they appear immediately
    # without waiting for the agent loop to produce them.
    running_agents = [a for a in spawned if a.get("status") == "running"]

    if len(running_agents) >= 1:
        agent1_name = running_agents[0]["name"]
        model1_display = agent_models[0]["name"].split(":")[0]
        _post_to_channel(
            channel,
            (
                f"Hey! I'm {model1_display}, running right on your machine. "
                f"No API keys, no cloud \u2014 just your hardware."
            ),
            msg_type="info",
        )
        messages_posted += 1

    if len(running_agents) >= 2:
        model2_display = agent_models[1]["name"].split(":")[0]
        _post_to_channel(
            channel,
            (
                f"I'm {model2_display}. Together we handle analysis, writing, code, "
                f"and research. All local."
            ),
            msg_type="info",
        )
        messages_posted += 1

    if len(running_agents) >= 1:
        _post_to_channel(
            channel,
            (
                "Want more power? You can connect Claude or Gemini with your API key. "
                "Just go to Settings and I'll help you set it up."
            ),
            msg_type="info",
        )
        messages_posted += 1

    log.info(
        "Welcome agents spawned for user '%s': %d agents, %d messages on #%s",
        user_id,
        len(running_agents),
        messages_posted,
        channel,
    )

    return {
        "channel": channel,
        "agents": spawned,
        "messages_posted": messages_posted,
    }


# ---------------------------------------------------------------------------
# d) check_user_setup_status
# ---------------------------------------------------------------------------


def check_user_setup_status(user_id: str) -> dict[str, Any]:
    """Return the user's onboarding / setup state.

    Checks:
        - Is Ollama running?
        - What local models are available?
        - Are any cloud API keys configured?
        - Have welcome agents been spawned for this user?
        - Does the user still need onboarding?

    Args:
        user_id: The user identifier to check.

    Returns:
        Dict with keys:
            ``ollama_running`` (bool),
            ``local_models``   (list[str]),
            ``cloud_keys``     (dict — which cloud backends have API keys set),
            ``agents_spawned`` (list[dict] — running agents for this user),
            ``needs_onboarding`` (bool — True if no agents and no cloud keys).
    """
    # Ollama detection
    ollama_info = detect_ollama()
    ollama_running = ollama_info["status"] == "running"
    local_models = ollama_info.get("models", [])

    # Cloud API key detection
    cloud_keys: dict[str, bool] = {
        "anthropic": bool(os.environ.get("ANTHROPIC_API_KEY", "").strip()),
        "gemini": bool(os.environ.get("GEMINI_API_KEY", "").strip()),
    }

    # Check for agents belonging to this user
    all_agents = list_agents()
    user_agents = [
        a for a in all_agents
        if a.get("name", "").startswith(f"welcome-{user_id}")
        and a.get("status") == "running"
    ]

    # User needs onboarding if they have no running agents and no cloud keys
    has_cloud = any(cloud_keys.values())
    has_agents = len(user_agents) > 0
    needs_onboarding = not has_agents and not has_cloud

    return {
        "ollama_running": ollama_running,
        "local_models": local_models,
        "cloud_keys": cloud_keys,
        "agents_spawned": user_agents,
        "needs_onboarding": needs_onboarding,
    }
