"""Command line interface for qpip."""

from __future__ import annotations

import sys
from dataclasses import dataclass
from pathlib import Path
from textwrap import dedent
from typing import Mapping, Optional, Sequence, Tuple

from .core import (
    DEFAULT_MIRROR,
    format_mirrors,
    get_default_mirror,
    is_mirror_name,
    normalize_mirror_name,
    run_pip_with_mirror,
)
from .project_init import run_init_command
from .project_run import run_project_command
from .project_venv import (
    VenvUsageError,
    run_activate_command,
    run_deactivate_command,
    run_venv_command,
)


HELP_TEXT = dedent(
    f"""\
    qpip - 使用更快的软件包镜像运行 pip

    用法:
      qpip install requests
      qpip i requests
      qpip update requests
      qpip up requests
      qpip uninstall requests
      qpip un requests
      qpip pip install numpy
      qpip tsinghua install numpy
      qpip --mirror douban install pandas
      qpip run
      qpip run test
      qpip run test -- -k api
      qpip init
      qpip init -y
      qpip venv
      qpip active
      qpip deactivate
      qpip --list-mirrors
      qpip --help

    说明:
      - qpip 默认使用 '{DEFAULT_MIRROR}' 镜像。
      - 可通过设置环境变量 QPIP_DEFAULT_MIRROR 修改默认镜像。
      - qpip 自身的参数必须放在 pip 参数之前。
      - 如果你要把前导选项传给 pip 自身，请使用 '--'。
      - `qpip update` / `qpip up` 等价于 `pip install --upgrade ...`。
      - `qpip uninstall` / `qpip un` 等价于 `pip uninstall ...`。
      - `qpip init` 会在当前目录生成 pyproject.toml 和默认 scripts。
      - `qpip run` 优先读取 `[tool.qpip.scripts]`，并兼容常见项目的已有 scripts 配置。
      - `qpip venv` 会在当前目录创建 `.venv`。
      - `qpip active` / `qpip deactivate` 会输出当前 shell 可直接执行的虚拟环境命令。
    """
).strip()


class CLIUsageError(ValueError):
    """Raised when qpip-specific CLI usage is invalid."""


@dataclass(frozen=True)
class CLIOptions:
    """Parsed qpip command line options."""

    mirror: str
    pip_args: Tuple[str, ...]
    dry_run: bool = False
    list_mirrors: bool = False
    help_requested: bool = False


def parse_cli_args(
    argv: Sequence[str],
    env: Optional[Mapping[str, str]] = None,
) -> Tuple[CLIOptions, Optional[str]]:
    """Parse qpip flags while leaving the remainder for pip."""
    args = list(argv)
    mirror, warning = get_default_mirror(env)
    positional_mirror = None

    if args and is_mirror_name(args[0]):
        positional_mirror = normalize_mirror_name(args.pop(0))
        mirror = positional_mirror

    dry_run = False
    list_mirrors = False
    help_requested = False
    pip_args: Tuple[str, ...] = ()

    index = 0
    while index < len(args):
        arg = args[index]

        if arg == "--":
            pip_args = tuple(args[index + 1 :])
            break

        if arg in {"-h", "--help"}:
            help_requested = True
            index += 1
            continue

        if arg == "--list-mirrors":
            list_mirrors = True
            index += 1
            continue

        if arg == "--dry-run":
            dry_run = True
            index += 1
            continue

        if arg in {"-m", "--mirror"}:
            if index + 1 >= len(args):
                raise CLIUsageError(f"{arg} 需要指定镜像名称")

            explicit_mirror = normalize_mirror_name(args[index + 1])
            if positional_mirror and explicit_mirror != positional_mirror:
                raise CLIUsageError("镜像被重复指定且值不一致")

            mirror = explicit_mirror
            index += 2
            continue

        if is_mirror_name(arg):
            explicit_mirror = normalize_mirror_name(arg)
            if positional_mirror and explicit_mirror != positional_mirror:
                raise CLIUsageError("镜像被重复指定且值不一致")

            positional_mirror = explicit_mirror
            mirror = explicit_mirror
            index += 1
            continue

        pip_args = tuple(args[index:])
        break
    else:
        pip_args = ()

    return (
        CLIOptions(
            mirror=mirror,
            pip_args=pip_args,
            dry_run=dry_run,
            list_mirrors=list_mirrors,
            help_requested=help_requested,
        ),
        warning,
    )


def main(argv: Optional[Sequence[str]] = None) -> int:
    """Console script entry point."""
    raw_args = sys.argv[1:] if argv is None else list(argv)

    try:
        options, warning = parse_cli_args(raw_args)
    except ValueError as exc:
        print(f"错误: {exc}", file=sys.stderr)
        print(HELP_TEXT, file=sys.stderr)
        return 2

    if warning:
        print(f"[qpip] {warning}", file=sys.stderr)

    if options.help_requested:
        print(HELP_TEXT)
        return 0

    if options.list_mirrors:
        print(format_mirrors())
        return 0

    if not options.pip_args:
        print(HELP_TEXT, file=sys.stderr)
        return 2

    if options.pip_args[0] == "init":
        try:
            return run_init_command(
                options.pip_args,
                cwd=Path.cwd(),
                dry_run=options.dry_run,
            )
        except ValueError as exc:
            print(f"错误: {exc}", file=sys.stderr)
            return 2

    if options.pip_args[0] == "run":
        try:
            return run_project_command(
                options.pip_args,
                cwd=Path.cwd(),
                dry_run=options.dry_run,
            )
        except ValueError as exc:
            print(f"错误: {exc}", file=sys.stderr)
            return 2

    if options.pip_args[0] == "venv":
        try:
            return run_venv_command(
                options.pip_args,
                cwd=Path.cwd(),
                dry_run=options.dry_run,
            )
        except VenvUsageError as exc:
            print(f"错误: {exc}", file=sys.stderr)
            return 2

    if options.pip_args[0] == "active":
        try:
            return run_activate_command(
                options.pip_args,
                cwd=Path.cwd(),
            )
        except VenvUsageError as exc:
            print(f"错误: {exc}", file=sys.stderr)
            return 2

    if options.pip_args[0] == "deactivate":
        try:
            return run_deactivate_command(
                options.pip_args,
                cwd=Path.cwd(),
            )
        except VenvUsageError as exc:
            print(f"错误: {exc}", file=sys.stderr)
            return 2

    try:
        return run_pip_with_mirror(
            options.pip_args,
            mirror=options.mirror,
            dry_run=options.dry_run,
        )
    except ValueError as exc:
        print(f"错误: {exc}", file=sys.stderr)
        return 2
