"""Interactive model selector with fuzzy search for LLMHosts CLI.

Discovers models from Ollama backends and presents an interactive
picker with filtering, model metadata, and config persistence.
"""

from __future__ import annotations

import sys
from difflib import SequenceMatcher
from typing import TYPE_CHECKING

from rich.prompt import Prompt
from rich.table import Table

if TYPE_CHECKING:
    from rich.console import Console

    from llmhosts.discovery.models import OllamaModel


def is_interactive() -> bool:
    """Return True if stdin is a TTY (interactive terminal)."""
    return hasattr(sys.stdin, "isatty") and sys.stdin.isatty()


def _fuzzy_score(query: str, text: str) -> float:
    """Score how well *query* matches *text* (0.0-1.0).

    Uses substring containment first (fast path), then falls back
    to SequenceMatcher for typo-tolerant matching.
    """
    query_lower = query.lower()
    text_lower = text.lower()

    # Exact substring → highest score
    if query_lower in text_lower:
        return 1.0

    # Token match: any word in query appears in text
    query_tokens = query_lower.split()
    if query_tokens and all(tok in text_lower for tok in query_tokens):
        return 0.9

    return SequenceMatcher(None, query_lower, text_lower).ratio()


def _filter_models(models: list[OllamaModel], query: str) -> list[OllamaModel]:
    """Filter and rank models by fuzzy match against name, family, and quantization."""
    if not query.strip():
        return models

    scored: list[tuple[float, OllamaModel]] = []
    for model in models:
        # Match against name, family, parameter size, quantization
        searchable = " ".join(
            [
                model.name,
                model.details.family,
                model.details.parameter_size,
                model.details.quantization_level,
            ]
        )
        score = _fuzzy_score(query, searchable)
        if score >= 0.3:
            scored.append((score, model))

    scored.sort(key=lambda x: x[0], reverse=True)
    return [m for _, m in scored]


def _render_model_table(
    console: Console,
    models: list[OllamaModel],
    *,
    title: str = "Available Models",
    show_index: bool = True,
) -> None:
    """Render a Rich table of models with metadata."""
    table = Table(title=title, show_lines=False)

    if show_index:
        table.add_column("#", style="dim", justify="right", no_wrap=True)
    table.add_column("Model", style="bold cyan", no_wrap=True)
    table.add_column("Size", justify="right")
    table.add_column("Params", justify="right")
    table.add_column("Quantization", style="dim")
    table.add_column("Family", style="dim")

    for i, model in enumerate(models, 1):
        size_str = f"{model.size_gb:.1f} GB" if model.size_gb else "—"
        params_str = model.details.parameter_size or "—"
        quant_str = model.details.quantization_level or "—"
        family_str = model.details.family or "—"

        row = []
        if show_index:
            row.append(str(i))
        row.extend([model.name, size_str, params_str, quant_str, family_str])
        table.add_row(*row)

    console.print(table)


def select_models_interactive(
    console: Console,
    models: list[OllamaModel],
) -> list[OllamaModel]:
    """Interactive model selector with fuzzy search.

    Presents discovered models in a table, allows the user to filter
    by typing a search query, and select one or more models by number.

    Returns the list of selected models, or an empty list if the user
    cancels.
    """
    if not models:
        console.print("[yellow]No models found. Run llmhosts serve to auto-provision models.[/yellow]")
        return []

    current_models = models

    while True:
        console.print()
        _render_model_table(console, current_models)
        console.print()
        console.print(
            "[dim]Commands: numbers to select (e.g. 1,3,5) | 'all' | 'filter <query>' | 'reset' | 'q' to cancel[/dim]"
        )

        answer = Prompt.ask("[bold]Select models[/bold]", default="all")
        answer = answer.strip()

        if not answer or answer.lower() == "q":
            return []

        # Filter command
        if answer.lower().startswith("filter "):
            query = answer[7:].strip()
            if query:
                filtered = _filter_models(models, query)
                if filtered:
                    current_models = filtered
                    console.print(f"[dim]Showing {len(filtered)} matching model(s)[/dim]")
                else:
                    console.print("[yellow]No models match that filter. Try again.[/yellow]")
            continue

        # Reset filter
        if answer.lower() == "reset":
            current_models = models
            console.print("[dim]Filter cleared.[/dim]")
            continue

        # Select all
        if answer.lower() == "all":
            console.print(f"[green]✓[/green] Selected all {len(current_models)} model(s)")
            return list(current_models)

        # Parse comma-separated numbers
        selected: list[OllamaModel] = []
        try:
            indices = [int(x.strip()) for x in answer.split(",") if x.strip()]
        except ValueError:
            # Try as a filter query instead
            filtered = _filter_models(models, answer)
            if filtered:
                current_models = filtered
                console.print(f"[dim]Showing {len(filtered)} matching model(s)[/dim]")
            else:
                console.print("[yellow]Invalid input. Enter numbers, 'all', 'filter <query>', or 'q'.[/yellow]")
            continue

        for idx in indices:
            if 1 <= idx <= len(current_models):
                selected.append(current_models[idx - 1])
            else:
                console.print(f"[yellow]Skipping invalid index: {idx}[/yellow]")

        if selected:
            names = ", ".join(m.name for m in selected)
            console.print(f"[green]✓[/green] Selected {len(selected)} model(s): {names}")
            return selected

        console.print("[yellow]No valid models selected. Try again.[/yellow]")


async def discover_and_select(
    console: Console,
    ollama_host: str = "http://127.0.0.1:11434",
) -> list[OllamaModel]:
    """Discover Ollama models and run interactive selection.

    Returns the selected models, or an empty list if discovery fails
    or the user cancels.
    """
    from llmhosts.discovery.ollama import OllamaDiscovery

    console.print("[dim]Discovering local models...[/dim]")

    async with OllamaDiscovery(host=ollama_host) as ollama:
        if not await ollama.is_available():
            console.print("[yellow]Inference engine is not running.[/yellow]")
            console.print(f"[dim]Tried: {ollama_host}[/dim]")
            console.print("[dim]Run: llmhosts serve (auto-starts engine)[/dim]")
            return []

        models = await ollama.list_models()

    if not models:
        console.print("[yellow]Inference engine running but no models installed.[/yellow]")
        console.print("[dim]Run: llmhosts serve (auto-provisions models)[/dim]")
        return []

    console.print(f"[green]✓[/green] Found {len(models)} local model(s)")
    return select_models_interactive(console, models)
