#! /usr/bin/env python3

import argparse
import itertools
import os
import sys

from agithub.GitHub import GitHub
import git
import yaml


DEFAULT_CONF_PATH = "./github-backup.yml"

DEFAULT_CONF_BODY = """
general:
{token}
  repopath: "{repopath}"
# Number of unknown repos required to prompt a warning in cron mode. (Default: 5, set to 0 to disable)
# unknown_repo_warning: 5
# Only look for repos owned by the authed user (Default: true)
# only_personal: false
repos: {{}}
exclude: []"""


def gen_default_conf(conf_path, token):
    repopath = os.getcwd()
    if token == "":
        body = DEFAULT_CONF_BODY.format(repopath=repopath,
                                        token='# token: "XXXXXXXX"')
    else:
        body = DEFAULT_CONF_BODY.format(repopath=repopath,
                                        token='  token: "{}"'.format(token))
    with open(conf_path, "w") as conf_file:
        conf_file.write(body)


def paginate(path, per_page=30, **kwargs):
    for page in itertools.count(1):
        status, rsp = path(page=page, per_page=per_page, **kwargs)
        rsps = len(rsp)
        if rsps == 0:
            break  # Empty page means we are done
        else:
            for elm in rsp:
                yield elm
            if rsps < per_page:
                break  # Non-full page must be last


def load_repos(ghub, only_personal, exclude):
    if only_personal:
        repos = paginate(ghub.user.repos.get, affiliation="owner")
    else:
        repos = paginate(ghub.user.repos.get)
    return {repo['name']: repo for repo in repos if repo['name'] not in exclude}


def conf_load(conf, *args, default=None):
    cur = conf
    for step in args:
        if step in cur:
            cur = cur[step]
        else:
            return default
    return cur


def update_repos(repos, repopath):
    for name, repo in repos.items():
        print("Updating {}...".format(name))
        g_repo = git.Repo(os.path.join(repopath, name))
        if repo['clone_url'] != g_repo.remotes.origin.url:
            print("Repo url is incorrect, altering.")
            g_repo.remotes.origin.set_url(repo['clone_url'], g_repo.remotes.origin.url)
        g_repo.remotes.origin.pull()


def clone_repo(repo, repopath):
    git.Repo.clone_from(repo['clone_url'], os.path.join(repopath, repo['name']), mirror=True)


def gather_args():
    parser = argparse.ArgumentParser(description="A github mirroring tool.")
    parser.add_argument("--conf", default=DEFAULT_CONF_PATH)
    parser.add_argument("--token", default="")
    parser.add_argument("--cron", action="store_true")
    parser.add_argument("--gen-conf", action="store_true")
    return parser.parse_args()


def main():
    args = gather_args()

    if args.gen_conf:
        gen_default_conf(args.conf, args.token)
    else:
        with open(args.conf, "r") as conf_file:
            conf = yaml.safe_load(conf_file)

        if args.token != "":
            auth = args.token
        else:
            auth = conf_load(conf, 'general', 'token')
            if auth is None:
                print("Error: Must either specify auth token on command line or in config.")
                sys.exit(1)
        ghub = GitHub(token=auth)

        repopath = conf_load(conf, 'general', 'repopath')
        if repopath is None:
            print("Error: Config must specify a repopath in the general section.")
            sys.exit(1)

        only_personal = conf_load(conf, 'general', 'only_personal', default=True)
        unknown_repo_warning = conf_load(conf, 'general', 'unknown_repo_warning', default=5)
        exclude = conf_load(conf, 'exclude', default=[])
        conf_repos = conf_load(conf, 'repos', default=[])
        ghub_repos = load_repos(ghub, only_personal, exclude)

        unknown = [repo for name, repo in ghub_repos.items() if name not in conf_repos]

        if not args.cron:
            for repo in unknown:
                print("Unknown Repo\n"
                      "Name: {}\n"
                      "Fork: {}".format(repo['name'], "Yes" if repo['fork'] else "No"))
                while True:
                    choice = input("(A)dd/(S)kip/(E)xclude? ").upper()
                    if choice in ["A", "S", "E"]:
                        break
                if choice == "A":
                    print("Adding {} to the list of repos.".format(repo['name']))
                    conf_repos[repo['name']] = repo
                    clone_repo(repo, repopath)
                elif choice == "E":
                    print("Adding {} to the exclusion list.".format(repo['name']))
                    exclude.append(repo['name'])
                else:
                    print("Skipping {} for this run.".format(repo['name']))
            conf['repos'] = conf_repos
            conf['exclude'] = exclude
            with open(args.conf, "w") as conf_file:
                yaml.safe_dump(conf, conf_file)

        update_repos(conf_repos, repopath)

        if args.cron:
            if 0 < unknown_repo_warning <= len(unknown):
                print("Error: There are {} unknown repos on github. "
                      "This is more than your limit of {}.".format(unknown, unknown_repo_warning))
                sys.exit(2)


if __name__ == "__main__":
    main()

@@ -1947,24 +1947,28 @@
 popath, name
+, ''
 ))%0A        i
@@ -2193,12 +2193,13 @@
 gin.
-pull
+fetch
 ()%0A%0A
@@ -4403,20 +4403,48 @@
 me'%5D%5D = 
-repo
+%7B'clone_url': repo%5B'clone_url'%5D%7D
 %0A       

Fix some awkward bits in the git handling