import argparse
import os
import re
from pathlib import Path
from typing import Any, List, Optional, Pattern

from .approve import approve_command
from .comments import show_comments
from .merge import merge_command
from .post_result import post_result_command
from .pr import pr_command
from .rev import rev_command
from .wip import wip_command


def regex_type(s: str) -> Pattern[str]:
    try:
        return re.compile(s)
    except re.error as e:
        raise argparse.ArgumentTypeError(f"'{s}' is not a valid regex: {e}")


def pr_flags(subparsers: argparse._SubParsersAction) -> argparse.ArgumentParser:
    pr_parser = subparsers.add_parser("pr", help="review a pull request on nixpkgs")
    pr_parser.add_argument(
        "--eval",
        default="ofborg",
        choices=["ofborg", "local"],
        help="Whether to use ofborg's evaluation result",
    )
    checkout_help = (
        "What to source checkout when building: "
        + "`merge` will merge the pull request into the target branch, "
        + "while `commit` will checkout pull request as the user has committed it"
    )

    pr_parser.add_argument(
        "-c",
        "--checkout",
        default="merge",
        choices=["merge", "commit"],
        help=checkout_help,
    )
    pr_parser.add_argument(
        "number",
        nargs="+",
        help="one or more nixpkgs pull request numbers (ranges are also supported)",
    )
    pr_parser.add_argument(
        "--post-result",
        action="store_true",
        help="Post the nixpkgs-review results as a PR comment",
    )
    pr_parser.set_defaults(func=pr_command)
    return pr_parser


def rev_flags(subparsers: argparse._SubParsersAction) -> argparse.ArgumentParser:
    rev_parser = subparsers.add_parser(
        "rev", help="review a change in the local pull request repository"
    )
    rev_parser.add_argument(
        "-b", "--branch", default="master", help="branch to compare against with"
    )
    rev_parser.add_argument(
        "commit", help="commit/tag/ref/branch in your local git repository"
    )
    rev_parser.add_argument(
        "-r",
        "--remote",
        default="https://github.com/NixOS/nixpkgs",
        help="Name of the nixpkgs repo to review",
    )
    rev_parser.set_defaults(func=rev_command)
    return rev_parser


def wip_flags(subparsers: argparse._SubParsersAction) -> argparse.ArgumentParser:
    wip_parser = subparsers.add_parser(
        "wip", help="review the uncommited changes in the working tree"
    )

    wip_parser.add_argument(
        "-b", "--branch", default="master", help="branch to compare against with"
    )
    wip_parser.add_argument(
        "-s",
        "--staged",
        action="store_true",
        default=False,
        help="Whether to build staged changes",
    )
    wip_parser.add_argument(
        "-r",
        "--remote",
        default="https://github.com/NixOS/nixpkgs",
        help="Name of the nixpkgs repo to review",
    )

    wip_parser.set_defaults(func=wip_command)

    return wip_parser


class CommonFlag:
    def __init__(self, *args: Any, **kwargs: Any) -> None:
        self.args = args
        self.kwargs = kwargs


def read_github_token() -> Optional[str]:
    # for backwards compatibility we also accept GITHUB_OAUTH_TOKEN.
    token = os.environ.get("GITHUB_OAUTH_TOKEN", os.environ.get("GITHUB_TOKEN"))
    if token:
        return token
    raw_hub_path = os.environ.get("HUB_CONFIG", None)
    if raw_hub_path:
        hub_path = Path(raw_hub_path)
    else:
        raw_config_home = os.environ.get("XDG_CONFIG_HOME", None)
        if raw_config_home is None:
            home = os.environ.get("HOME", None)
            if home is None:
                return None
            config_home = Path(home).joinpath(".config")
        else:
            config_home = Path(raw_config_home)
        hub_path = config_home.joinpath("hub")
    try:
        with open(hub_path) as f:
            for line in f:
                token_match = re.match(r"\s*oauth_token:\s+([a-f0-9]+)", line)
                if token_match:
                    return token_match.group(1)
    except OSError:
        pass
    return None


def common_flags() -> List[CommonFlag]:
    return [
        CommonFlag(
            "--build-args", default="", help="arguments passed to nix when building"
        ),
        CommonFlag(
            "-p",
            "--package",
            action="append",
            default=[],
            help="Package to build (can be passed multiple times)",
        ),
        CommonFlag(
            "--package-regex",
            action="append",
            default=[],
            type=regex_type,
            help="Regular expression that package attributes have to match (can be passed multiple times)",
        ),
        CommonFlag(
            "--no-shell",
            action="store_true",
            help="Only evaluate and build without executing nix-shell",
        ),
        CommonFlag(
            "--token",
            type=str,
            default=read_github_token(),
            help="Github access token (optional if request limit exceeds)",
        ),
    ]


def parse_args(command: str, args: List[str]) -> argparse.Namespace:
    main_parser = argparse.ArgumentParser(
        prog=command, formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )
    subparsers = main_parser.add_subparsers(
        dest="subcommand",
        title="subcommands",
        description="valid subcommands",
        help="use --help on the additional subcommands",
    )
    subparsers.required = True

    post_result_parser = subparsers.add_parser(
        "post-result", help="post PR comments with results"
    )
    post_result_parser.set_defaults(func=post_result_command)

    approve_parser = subparsers.add_parser("approve", help="approve PR")
    approve_parser.set_defaults(func=approve_command)

    comments_parser = subparsers.add_parser("comments", help="show comments")
    comments_parser.set_defaults(func=show_comments)

    merge_parser = subparsers.add_parser("merge", help="merge PR")
    merge_parser.set_defaults(func=merge_command)

    parsers = [
        approve_parser,
        comments_parser,
        merge_parser,
        post_result_parser,
        pr_flags(subparsers),
        rev_flags(subparsers),
        wip_flags(subparsers),
    ]

    common = common_flags()
    for parser in parsers:
        for flag in common:
            parser.add_argument(*flag.args, **flag.kwargs)

    return main_parser.parse_args(args)


def main(command: str, raw_args: List[str]) -> None:
    args = parse_args(command, raw_args)
    args.func(args)