"""
llmpm — LLM Package Manager
CLI entry point.
"""

from __future__ import annotations

import os
import sys
import traceback

# ── Fix: prevent namespace-package collision ───────────────────────────────────
# When a Homebrew pip-install and an editable dev install of llmpm both appear
# on sys.path, Python merges them into a namespace package (no __init__.py),
# making every sub-package import fail with "unknown location".
# Pin llmpm.__path__ to this file's parent directory so all sub-imports resolve
# to exactly one tree, before any llmpm submodule imports below are attempted.
_llmpm_dir = os.path.dirname(os.path.abspath(__file__))
if "llmpm" in sys.modules and hasattr(sys.modules["llmpm"], "__path__"):
    sys.modules["llmpm"].__path__ = [_llmpm_dir]

# ── Mandatory venv isolation ───────────────────────────────────────────────────
# Must run BEFORE any llmpm submodule imports so that, on first launch,
# we create ~/.llmpm/venv and replace this process with the venv Python.
# All subsequent imports (llmpm.commands.*, llmpm.core.*) then execute
# inside the clean, conflict-free venv environment.
from llmpm._venv import ensure as _ensure_venv  # noqa: E402
_ensure_venv()
# ─────────────────────────────────────────────────────────────────────────────

import click
from rich.console import Console
from rich.panel import Panel

from importlib.metadata import version as _pkg_version
__version__: str = _pkg_version("llmpm")
try:
    from llmpm.commands.clean_cmd import clean as _clean_cmd
except ImportError:  # older PyPI install that predates clean_cmd
    _clean_cmd = None  # type: ignore[assignment]
from llmpm.commands.benchmark_cmd import benchmark
from llmpm.commands.init_cmd import init
from llmpm.commands.install import install
from llmpm.commands.list_cmd import info, list_models, uninstall
from llmpm.commands.search_cmd import search
from llmpm.commands.push import push
from llmpm.commands.run_cmd import run_model
from llmpm.commands.serve_cmd import serve
from llmpm.commands.trending_cmd import trending

console = Console()

HELP_TEXT = """\
[bold green]llmpm[/] [dim]—[/] LLM Package Manager

  Download, run, and share AI models from the command line.
  Models are sourced from [bold]HuggingFace Hub[/].

[bold]Usage:[/]

  [bold cyan]llmpm init[/]               Create a llmpm.json (local project mode)
  [bold cyan]llmpm install[/]            Install all models from llmpm.json
  [bold cyan]llmpm install[/] [dim]<repo>[/]    Download and install a model
  [bold cyan]llmpm run[/]     [dim]<repo>[/]    Run a model (interactive chat)
  [bold cyan]llmpm serve[/]   [dim]<repo>[/]    Serve model as HTTP API
  [bold cyan]llmpm push[/]    [dim]<repo>[/]    Upload a model to HuggingFace
  [bold cyan]llmpm search[/]  [dim]<query>[/]   Search HuggingFace Hub for models
  [bold cyan]llmpm trending[/]           Show top trending models by likes
  [bold cyan]llmpm benchmark[/] [dim]<repo>[/]    Run evaluation benchmarks
  [bold cyan]llmpm push[/]      [dim]<repo>[/]    Upload a model to HuggingFace
  [bold cyan]llmpm list[/]               Show all installed models
  [bold cyan]llmpm info[/]    [dim]<repo>[/]    Show details about a model
  [bold cyan]llmpm uninstall[/] [dim]<repo>[/]  Uninstall a model
  [bold cyan]llmpm clean[/]              Remove the managed environment (~/.llmpm/venv)

[bold]Local vs global mode:[/]

  When [bold]llmpm.json[/] is present in the project directory (created by
  [bold cyan]llmpm init[/]), all commands operate on a local [dim].llmpm/[/] folder
  next to that file.  Without it, the global [dim]~/.llmpm/[/] is used.

  [dim]# Set up a project[/]
  [bold]llmpm init[/]
  [bold]llmpm install meta-llama/Llama-3.2-3B-Instruct --save[/]

  [dim]# Later, reproduce the same environment[/]
  [bold]llmpm install[/]

[bold]Examples:[/]

  [dim]# Install a GGUF model (interactive quantisation picker)[/]
  [bold]llmpm install bartowski/Llama-3.2-3B-Instruct-GGUF[/]

  [dim]# Install with a specific quantisation[/]
  [bold]llmpm install bartowski/Llama-3.2-3B-Instruct-GGUF --quant Q4_K_M[/]

  [dim]# Install a full Transformer model and save to llmpm.json[/]
  [bold]llmpm install meta-llama/Llama-3.2-3B-Instruct --save[/]

  [dim]# Run a model in interactive chat[/]
  [bold]llmpm run meta-llama/Llama-3.2-3B-Instruct[/]

  [dim]# Single-turn inference[/]
  [bold]llmpm run meta-llama/Llama-3.2-3B-Instruct --prompt "Hello!"[/]

  [dim]# Serve a model as an HTTP API[/]
  [bold]llmpm serve meta-llama/Llama-3.2-3B-Instruct[/]

  [dim]# Push a model to HuggingFace[/]
  [bold]llmpm push my-org/my-fine-tune --path ./my-model[/]

[bold]Backends:[/]

  [blue]GGUF[/] models  → [bold]llama.cpp[/]
    (requires: pip install llama-cpp-python)
  Other models  → [bold]Transformers[/]
    (requires: pip install transformers torch)

[bold]Docs:[/] https://www.llmpm.co/docs
"""


class LlpmGroup(click.Group):
    """Custom group that shows a branded help page."""

    def format_help(self, _ctx, _formatter):
        """Render branded help panel instead of default Click output."""
        console.print(Panel(
            HELP_TEXT,
            title=f"[bold green]llmpm[/] [dim]v{__version__}[/]",
            border_style="dim green",
            padding=(1, 3),
        ))

    def parse_args(self, ctx, args):
        """Allow bare `llmpm` (no args) to show help instead of erroring."""
        # Allow `llmpm` with no args to show help instead of error
        if not args:
            ctx.ensure_object(dict)
        return super().parse_args(ctx, args)


@click.group(cls=LlpmGroup, invoke_without_command=True)
@click.version_option(
    version=__version__,
    prog_name="llmpm",
    message="%(prog)s  %(version)s",
)
@click.pass_context
def main(ctx: click.Context) -> None:
    """llmpm — LLM Package Manager."""
    if ctx.invoked_subcommand is None:
        console.print(Panel(
            HELP_TEXT,
            title=f"[bold green]llmpm[/] [dim]v{__version__}[/]",
            border_style="dim green",
            padding=(1, 3),
        ))


# Register commands
main.add_command(init)
main.add_command(install)
main.add_command(run_model, name="run")
main.add_command(serve)
main.add_command(push)
main.add_command(list_models, name="list")
main.add_command(search)
main.add_command(info)
main.add_command(uninstall)
if _clean_cmd is not None:
    main.add_command(_clean_cmd, name="clean")
main.add_command(trending)
main.add_command(benchmark)


# ── Module entry point (python -m llmpm) ──────────────────────────────────────

def entry():
    """Wrapper that handles uncaught exceptions gracefully."""
    try:
        main(standalone_mode=False)  # pylint: disable=no-value-for-parameter
    except click.ClickException as exc:
        exc.show()
        sys.exit(exc.exit_code)
    except click.Abort:
        console.print("\n  [dim]Aborted.[/]\n")
        sys.exit(1)
    except SystemExit as exc:
        sys.exit(exc.code)
    except KeyboardInterrupt:
        console.print("\n  [dim]Interrupted.[/]\n")
        sys.exit(130)
    except Exception as exc:  # pylint: disable=broad-except
        console.print(f"\n  [bold red]error[/]  Unexpected error: {exc}\n")
        if "--debug" in sys.argv:
            traceback.print_exc()
        sys.exit(1)
