"""
llmpm search <query>   — search HuggingFace Hub for models
"""

from __future__ import annotations

import json
import urllib.error
import urllib.parse
import urllib.request
from typing import Any

import click
from rich.console import Console
from rich.markup import escape
from rich.panel import Panel
from rich.table import Table

from llmpm import display

_console = Console()

HF_API = "https://huggingface.co/api/models"
HF_INFO = "https://huggingface.co/api/models/{repo_id}"

PIPELINE_LABELS: dict[str, str] = {
    "text-generation": "text-gen",
    "text2text-generation": "text2text",
    "feature-extraction": "embedding",
    "fill-mask": "fill-mask",
    "question-answering": "QA",
    "summarization": "summary",
    "translation": "translation",
    "image-classification": "img-cls",
    "image-to-text": "img2text",
    "text-to-image": "text2img",
    "automatic-speech-recognition": "ASR",
    "text-to-speech": "TTS",
    "audio-classification": "audio-cls",
    "object-detection": "obj-det",
    "zero-shot-classification": "zero-shot",
}


def _fmt_downloads(n: int | None) -> str:
    if n is None:
        return "—"
    if n >= 1_000_000:
        return f"{n / 1_000_000:.1f}M"
    if n >= 1_000:
        return f"{n / 1_000:.1f}K"
    return str(n)


def _pipeline_label(tag: str) -> str:
    return PIPELINE_LABELS.get(tag, tag)


def _fetch_json(url: str, params: dict | None = None) -> Any:
    try:
        if params:
            url = url + "?" + urllib.parse.urlencode(params)
        req = urllib.request.Request(url, headers={"User-Agent": "llmpm-cli/1.0"})
        with urllib.request.urlopen(req, timeout=15) as resp:
            return json.loads(resp.read().decode())
    except urllib.error.HTTPError as exc:
        raise click.ClickException(
            f"HuggingFace API returned HTTP {exc.code}: {exc.reason}"
        ) from exc
    except urllib.error.URLError as exc:
        raise click.ClickException(f"Network error: {exc.reason}") from exc
    except Exception as exc:  # pylint: disable=broad-except
        raise click.ClickException(f"Unexpected error: {exc}") from exc


# ── search ────────────────────────────────────────────────────────────────────

@click.command("search")
@click.argument("query")
@click.option("--limit", "-n", default=20, show_default=True, help="Max results to show.")
@click.option("--task", "-t", default=None, help="Filter by pipeline task (e.g. text-generation).")
@click.option("--library", "-l", default=None, help="Filter by library (e.g. transformers, gguf).")
@click.option("--sort", "-s",
              type=click.Choice(["downloads", "likes", "lastModified", "trending"], case_sensitive=False),
              default="downloads", show_default=True,
              help="Sort results by field.")
@click.option("--info", "show_info", is_flag=True, help="Show detailed info for a chosen result.")
def search(query: str, limit: int, task: str | None, library: str | None,
           sort: str, show_info: bool) -> None:
    """Search HuggingFace Hub for models matching QUERY."""
    display.header(f"search  {query}")
    display.blank()

    params: dict[str, Any] = {
        "search": query,
        "limit": min(limit, 100),
        "sort": sort,
        "direction": -1,
        "full": "false",
    }
    if task:
        params["pipeline_tag"] = task
    if library:
        params["library"] = library

    with _console.status("[dim]Querying HuggingFace Hub…[/]"):
        results: list[dict] = _fetch_json(HF_API, params)

    if not results:
        display.warn("No models found. Try a different query or remove filters.")
        return

    _render_results_table(results)

    if show_info or click.confirm("\n  View details for a model?", default=False):
        _interactive_detail(results)


def _render_results_table(results: list[dict]) -> None:
    table = Table(show_header=True, header_style="bold dim", border_style="dim",
                  show_lines=False, padding=(0, 1))
    table.add_column("#", style="dim", width=4, justify="right")
    table.add_column("Model ID", min_width=30)
    table.add_column("Task", style="cyan", min_width=12)
    table.add_column("Downloads", justify="right", style="green", min_width=10)
    table.add_column("Likes", justify="right", style="yellow", min_width=6)
    table.add_column("Tags", style="dim", no_wrap=True, min_width=20)

    for idx, model in enumerate(results, 1):
        repo_id = model.get("modelId") or model.get("id") or "?"
        pipeline = model.get("pipeline_tag") or "—"
        task_label = _pipeline_label(pipeline) if pipeline != "—" else "—"
        downloads = _fmt_downloads(model.get("downloads"))
        likes = str(model.get("likes") or 0)
        tags = model.get("tags") or []
        # remove pipeline tag and library tags from display tags
        display_tags = [t for t in tags if t != pipeline and t not in
                        ("transformers", "pytorch", "safetensors", "arxiv")][:4]
        tags_str = "  ".join(display_tags) if display_tags else "—"

        table.add_row(
            str(idx),
            f"[bold]{escape(repo_id)}[/]",
            task_label,
            downloads,
            likes,
            tags_str,
        )

    _console.print(table)
    _console.print()


def _interactive_detail(results: list[dict]) -> None:
    """Prompt user for index and show full model details."""
    while True:
        raw = click.prompt(
            "  Enter model # (or 'q' to quit)",
            default="q",
            show_default=False,
        )
        if raw.strip().lower() in ("q", "quit", ""):
            break
        try:
            idx = int(raw.strip())
        except ValueError:
            _console.print("  [red]Please enter a number.[/]")
            continue

        if idx < 1 or idx > len(results):
            _console.print(f"  [red]Must be 1–{len(results)}.[/]")
            continue

        model = results[idx - 1]
        repo_id = model.get("modelId") or model.get("id") or "?"
        _show_model_detail(repo_id)
        break


def _show_model_detail(repo_id: str) -> None:
    """Fetch and render full model card info from HF API."""
    with _console.status(f"[dim]Fetching info for {repo_id}…[/]"):
        detail = _fetch_json(HF_INFO.format(repo_id=repo_id))

    grid = Table.grid(padding=(0, 2))
    grid.add_column(style="dim", justify="right", min_width=14)
    grid.add_column()

    def row(label: str, value: str) -> None:
        grid.add_row(label, value)

    row("model", f"[bold]{escape(repo_id)}[/]")

    pipeline = detail.get("pipeline_tag")
    if pipeline:
        row("task", f"[cyan]{_pipeline_label(pipeline)}[/]  [dim]({pipeline})[/]")

    author = detail.get("author")
    if author:
        row("author", escape(author))

    created = (detail.get("createdAt") or "")[:10]
    modified = (detail.get("lastModified") or "")[:10]
    if created:
        row("created", created)
    if modified:
        row("last modified", modified)

    downloads = detail.get("downloads")
    if downloads is not None:
        row("downloads", f"[green]{_fmt_downloads(downloads)}[/]")

    likes = detail.get("likes")
    if likes is not None:
        row("likes", f"[yellow]{likes}[/]")

    # library name
    library = detail.get("library_name")
    if library:
        row("library", library)

    # languages
    languages = detail.get("cardData", {}).get("language") or []
    if isinstance(languages, str):
        languages = [languages]
    if languages:
        row("languages", "  ".join(languages[:8]))

    # license
    license_val = detail.get("cardData", {}).get("license") or detail.get("license")
    if license_val:
        row("license", escape(str(license_val)))

    # siblings (file list)
    siblings = detail.get("siblings") or []
    if siblings:
        fnames = [s.get("rfilename", "") for s in siblings[:10]]
        row("files", "\n".join(f"[dim]  {escape(f)}[/]" for f in fnames))
        if len(siblings) > 10:
            grid.add_row("", f"[dim]  … and {len(siblings) - 10} more[/]")

    # tags
    tags = [t for t in (detail.get("tags") or []) if "/" not in t][:10]
    if tags:
        row("tags", "  ".join(f"[dim]{escape(t)}[/]" for t in tags))

    # model card excerpt
    card_text = (detail.get("cardData") or {}).get("description") or ""
    if not card_text:
        # try readme snippet from siblings
        card_text = ""
    if card_text:
        # trim to 3 lines
        excerpt = "\n".join(card_text.splitlines()[:3])
        row("description", f"[dim]{escape(excerpt)}[/]")

    model_url = f"https://llmpm.co/models/{repo_id}"
    row("url", f"[link={model_url}]{model_url}[/link]")

    _console.print()
    _console.print(Panel(grid, title=f"[bold green]{escape(repo_id)}[/]",
                         border_style="dim green", padding=(1, 2)))
    _console.print()
    _console.print(
        f"  [dim]Install:[/]  [bold cyan]llmpm install {escape(repo_id)}[/]\n"
    )
