"""CLI main entry point."""

import os
import shutil
import sys
import warnings
from pathlib import Path

# =============================================================================
# Global warning suppression for torch_npu
# =============================================================================
# Suppress torch_npu's "owner does not match" warnings at the earliest point.
# This happens when CANN toolkit is installed by root but used by non-root user.
# Must be set BEFORE any torch_npu import anywhere in the codebase.
warnings.filterwarnings(
    "ignore",
    message=".*owner does not match.*",
    category=UserWarning,
    module="torch_npu.*",
)


def _maybe_reexec_with_ascend_env() -> None:
    """Re-exec CLI via Ascend env wrapper when needed.

    On some Ascend systems, updating ``LD_LIBRARY_PATH`` inside the current
    process is not enough for ``torch_npu`` dynamic dependencies to resolve.
    Re-execing once with pre-populated env ensures stable one-click behavior.
    """
    if os.environ.get("SAGELLM_ASCEND_ENV_READY") == "1":
        return
    if shutil.which("npu-smi") is None:
        return

    # Only for commands that are likely to initialize torch/torch_npu.
    command: str | None = None
    for arg in sys.argv[1:]:
        if not arg.startswith("-"):
            command = arg
            break
    if command not in {"hello", "run", "chat", "serve", "setup", "profile"}:
        return

    wrapper = Path(__file__).resolve().parents[3] / "scripts" / "sagellm_with_ascend_env.sh"
    if not wrapper.is_file():
        return

    env = os.environ.copy()
    env["SAGELLM_ASCEND_ENV_READY"] = "1"
    cli_executable = sys.argv[0] if sys.argv and sys.argv[0] else "sagellm"
    os.execvpe(
        str(wrapper),
        [str(wrapper), cli_executable, *sys.argv[1:]],
        env,
    )


_maybe_reexec_with_ascend_env()

import click

# =============================================================================
# Initialize software environment at the very start
# =============================================================================
from .utils.env_detector import EnvInitializer

# Initialize all software-level environment variables
# This happens BEFORE any command execution
EnvInitializer.init_all(verbose=False)

from .utils.display import show_welcome_message

# Version from package
try:
    from sagellm import __version__
except ImportError:
    __version__ = "0.1.0"


CANONICAL_CLI_NAME = "sagellm"
SETUP_HINT_MARKER = Path.home() / ".sagellm" / ".setup_hint_shown"

# ---------------------------------------------------------------------------
# Command registry: maps command-name → (module_path, attr_in_module)
# (multi-name entries like "run,chat" share one module)
# ---------------------------------------------------------------------------
_COMMAND_REGISTRY: dict[str, tuple[str, str]] = {
    # user-facing commands (shown in help)
    "hello": (".commands.hello", "hello"),
    "run": (".commands.run", "run"),
    "chat": (".commands.run", "chat"),
    "serve": (".commands.serve", "serve"),
    "demo": (".commands.demo", "demo"),
    "setup": (".commands.setup", "setup"),
    "config": (".commands.config", "config"),
    "info": (".commands.info", "info"),
    "gateway": (".commands.gateway", "gateway"),
    "install": (".commands.install", "install"),
    "profile": (".commands.profile", "profile"),
    "check": (".commands.check", "check"),
    "arch": (".commands.arch", "arch"),
    "env": (".commands.env", "env"),
    "sync": (".commands.sync", "sync"),
    "ollama": (".commands.ollama", "ollama"),
    "embedding": (".commands.embedding", "embedding"),
    "ascend": (".commands.ascend", "ascend"),
    # dev-only commands (hidden; still need to be loadable by name)
    "version": (".commands.info", "version"),
}

# Commands hidden from the main help listing (dev / internal use only)
_HIDDEN_COMMANDS: frozenset[str] = frozenset(
    {
        "version",
        "info",
        "arch",
        "check",
        "env",
        "install",
        "profile",
        "sync",
        "gateway",
        "embedding",
    }
)


class _LazyGroup(click.Group):
    """Click Group that defers command-module imports until the command is invoked.

    This keeps startup time fast (< 0.1 s) even when individual commands
    transitively import heavy libraries such as ``torch`` or ``transformers``.
    """

    def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None:
        """Lazily import and return the named command."""
        if cmd_name not in _COMMAND_REGISTRY:
            return None
        module_path, attr = _COMMAND_REGISTRY[cmd_name]
        try:
            import importlib

            mod = importlib.import_module(module_path, package="sagellm.cli")
            return getattr(mod, attr)
        except (ImportError, AttributeError) as exc:
            import logging

            logging.debug("Failed to load command %r: %s", cmd_name, exc)
            return None

    def list_commands(self, ctx: click.Context) -> list[str]:
        """Return visible commands in stable order (no module imports needed)."""
        visible = [name for name in _COMMAND_REGISTRY if name not in _HIDDEN_COMMANDS]
        # Canonical ordering mirrors the old registration order
        order = [
            "hello",
            "run",
            "chat",
            "serve",
            "demo",
            "setup",
            "config",
        ]
        ordered = [c for c in order if c in visible]
        rest = sorted(c for c in visible if c not in order)
        return ordered + rest


def _maybe_show_setup_hint(invoked_subcommand: str | None) -> None:
    """Show one-time onboarding hint for interactive users."""
    if invoked_subcommand == "setup":
        return
    if os.getenv("SAGELLM_DISABLE_SETUP_HINT", "0").strip() == "1":
        return
    if not sys.stdout.isatty():
        return
    if SETUP_HINT_MARKER.exists():
        return

    SETUP_HINT_MARKER.parent.mkdir(parents=True, exist_ok=True)
    click.secho(
        "💡 首次使用建议先运行: sagellm setup  (或 sagellm-setup)",
        fg="yellow",
        err=True,
    )
    click.secho("   该向导会检测硬件并推荐可运行模型。", fg="yellow", err=True)
    SETUP_HINT_MARKER.write_text("shown\n", encoding="utf-8")


def _invoked_cli_name() -> str:
    """Return the command name used by user."""
    cli_name = os.path.basename(sys.argv[0])
    if cli_name == CANONICAL_CLI_NAME:
        return cli_name
    return CANONICAL_CLI_NAME


@click.command(cls=_LazyGroup, invoke_without_command=True)
@click.version_option(version=__version__, prog_name=CANONICAL_CLI_NAME)
@click.pass_context
def main(ctx: click.Context) -> None:
    """sageLLM: 面向国产算力的模块化大模型推理引擎

    为中国硬件生态提供类似 Ollama 的使用体验。

    \b
    快速开始:
      sagellm hello              # 🚀 一键推理测试
      sagellm info               # 查看系统信息
      sagellm run -p "Hello!"   # 运行推理
    """
    _maybe_show_setup_hint(ctx.invoked_subcommand)

    if ctx.invoked_subcommand is None:
        # No subcommand - show welcome message
        show_welcome_message(cli_name=CANONICAL_CLI_NAME)


if __name__ == "__main__":
    main()
