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

from __future__ import annotations

import sys
from dataclasses import dataclass
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,
)


HELP_TEXT = dedent(
    f"""\
    qpip - run pip with a faster package mirror

    Usage:
      qpip install requests
      qpip tsinghua install numpy
      qpip --mirror douban install pandas
      qpip --dry-run install fastapi
      qpip --list-mirrors
      qpip -- help

    Notes:
      - qpip defaults to the '{DEFAULT_MIRROR}' mirror.
      - Set QPIP_DEFAULT_MIRROR to change the default mirror.
      - qpip flags must appear before pip arguments.
      - Use '--' when you want to pass a leading option to pip itself.
    """
).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} requires a mirror name")

            explicit_mirror = normalize_mirror_name(args[index + 1])
            if positional_mirror and explicit_mirror != positional_mirror:
                raise CLIUsageError("mirror specified twice with different values")

            mirror = explicit_mirror
            index += 2
            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"Error: {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

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