#!/usr/bin/env python3
import argparse
import traceback
from json import load
from os import makedirs
from sys import argv

from vang.bitbucket.get_clone_urls import get_clone_urls
from vang.bitbucket.has_branch import has_branch
from vang.core.core import is_included
from vang.pio.shell import run_commands


def should_be_cloned(project, repo, project_config, has_branch_map):
    return has_branch_map[(project, repo)] and is_included(
        repo,
        project_config["excludes"],
        project_config["includes"],
    )


def clone(commands, root_dir):
    makedirs(root_dir, exist_ok=True)
    yield from run_commands(
        [(cmd, root_dir) for cmd in commands], max_processes=25, timeout=60, check=False
    )


def get_projects_commands(projects, branch=None, flat=False):
    return (
        (clone_dir, project, repo, command)
        for clone_dir, project, repo, command in get_clone_urls(
            projects,
            True,
            branch,
            flat,
        )
    )


def get_repos_commands(repos, branch=None, flat=False):
    projects = {repo.split("/")[0] for repo in repos}

    commands = []
    clone_urls_map = {
        f"{project}/{repo}": (clone_dir, project, repo, command)
        for clone_dir, project, repo, command in get_clone_urls(
            projects,
            True,
            branch,
            flat,
        )
    }
    for repo_spec in repos:
        if repo_spec in clone_urls_map:
            commands.append(clone_urls_map[repo_spec])
        else:
            print(f"Warning! Non existing repo: {repo_spec}")

    return commands


def get_config_commands(config, branch=None, flat=False):
    clone_url_specs = tuple(
        get_clone_urls(
            config["projects"], True, branch if branch else config["branch"], flat
        )
    )
    has_branch_specs = has_branch(
        [(p, r) for d, p, r, c in clone_url_specs], config["branch"]
    )
    has_branch_map = {repo_spec: has for repo_spec, has in has_branch_specs}

    return (
        (clone_dir, project, repo, command)
        for clone_dir, project, repo, command in clone_url_specs
        if should_be_cloned(project, repo, config["projects"][project], has_branch_map)
    )


def main(root_dir, projects=None, repos=None, config=None, branch=None, flat=False):
    n = 1
    if projects:
        commands = get_projects_commands(projects, branch, flat)
    elif repos:
        commands = get_repos_commands(repos, branch, flat)
    else:
        with open(config, "rt", encoding="utf-8") as cfg:
            commands = get_config_commands(load(cfg), branch, flat)

    for process in clone(
        [command for clone_dir, project, repo, command in commands],
        root_dir,
    ):
        try:
            print(str(n).zfill(2), process.stdout.decode(), end="")
            n += 1
        except OSError:  # pragma: no cover
            print(traceback.format_exc())


def parse_args(args):
    parser = argparse.ArgumentParser(description="Clone Bitbucket repos")
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument(
        "-p", "--projects", nargs="+", help="Bitbucket projects, e.g key1 key2"
    )
    group.add_argument(
        "-r", "--repos", nargs="+", help="Repos, e.g. key1/repo1 key2/repo2"
    )
    group.add_argument(
        "-c", "--config", help="Configuration file, see clone_repos.json"
    )
    parser.add_argument(
        "-b",
        "--branch",
        help="The clone branch. Overrides branch in configuration file (-c)",
    )
    parser.add_argument(
        "-d", "--root_dir", default=".", help="The directory to clone into"
    )
    parser.add_argument(
        "-f", "--flat", help="Clone to flat structure", action="store_true"
    )
    return parser.parse_args(args)


if __name__ == "__main__":  # pragma: no cover
    main(**parse_args(argv[1:]).__dict__)
