#!/usr/bin/env python3

"""
Builds integration branches. Something similar to
  $ git checkout -b branch-name
  $ for b in $(get-branches-from-github) ; do
  >   git pull b
  > done

Requires either `~/.github_token` containing ONLY the token
OR adding an entry like the following to `~/.netrc`:
  ```
  machine github.com
  password ghp_E7ln0tAR34LtoK3nIsw34RyTve2moM3BvK
  ```
"""

import argparse
import json
import os
import requests
import sys
import time
import netrc

from subprocess import call, check_output
from urllib.parse import urljoin

TIME_FORMAT = '%Y-%m-%d-%H%M'
CODENAMES = 'mimic nautilus octopus pacific quincy reef squid tentacle'
REPO = "ceph/ceph"


def get_postfix():
    postfix = "-" + time.strftime(TIME_FORMAT, time.localtime())
    current_branch = (
        check_output('git rev-parse --abbrev-ref HEAD', shell=True)
        .strip()
        .decode()
    )
    if current_branch in CODENAMES.split():
        postfix += '-' + current_branch
        print(f"Adding current branch name '-{current_branch}' as a postfix")
    return postfix


def parse_args():
    parser = argparse.ArgumentParser(usage=__doc__)
    parser.add_argument(
        "--no-date",
        "--no-postfix",
        action="store_true",
        help="Don't add `{postfix}` to the branch name.",
    )
    parser.add_argument(
        "--trailer",
        action="append",
        dest='trailers',
        help="Allow user to set arbitrary git trailers on final commit.",
    )
    parser.add_argument(
        '--ceph-build-job',
        action="append",
        dest='trailers',
        type=lambda v: f'CEPH-BUILD-JOB:{v}',
        help="Set CEPH-BUILD-JOB trailer on final commit.",
    )
    parser.add_argument(
        '--distros',
        action="append",
        dest='trailers',
        type=lambda v: f'DISTROS:{v}',
        help="Set DISTROS trailer on final commit.",
    )
    parser.add_argument(
        '--archs',
        action="append",
        dest='trailers',
        type=lambda v: f'ARCHS:{v}',
        help="Set DISTROS trailer on final commit.",
    )
    parser.add_argument(
        "--repo",
        default=REPO,
        help="GitHub repository (in `<org>/<name>` form)",
    )
    parser.add_argument(
        "label",
        help="GitHub label to search for",
    )
    return parser.parse_args()


def main():
    cli = parse_args()
    if cli.no_date:
        branch = cli.label
    else:
        branch = cli.label + get_postfix()
    label = cli.label
    repo = cli.repo

    token = ''
    try:
        nrc = netrc.netrc()
        nrauth = nrc.authenticators("api.github.com")
        if nrauth:
            token = nrauth[2]
        if not token:
            nrauth = nrc.authenticators("github.com")
            if nrauth:
                token = nrauth[2]
    except FileNotFoundError:
        pass
    if not token:
        try:
            with open(os.path.expanduser('~/.github_token')) as myfile:
                token = myfile.readline().strip()
        except FileNotFoundError:
            pass
    if not token:
        print('No github api access token found')
        print('  Add a token to .netrc for [api.]github.com')
        print('  OR add a token to $HOME/.github_token')

    # get prs
    baseurl = urljoin('https://api.github.com',
                      ('repos/{repo}/issues?labels={label}'
                       '&sort=created'
                       '&direction=asc'))
    url = baseurl.format(label=label,
                         repo=repo)
    r = requests.get(url,
                     headers={'Authorization': 'token %s' % token})
    if not r.ok:
        print("Failed to access github api")
        print("(Do you have a valid, unexpired github api token?)")
        sys.exit(1)

    j = json.loads(r.text or r.content)
    print("--- found %d issues tagged with %s" % (len(j), label))

    prs = []
    prtext = []
    for issue in j:
        if 'pull_request' not in issue:
            continue
        r = requests.get(issue['pull_request']['url'],
                         headers={'Authorization': 'token %s' % token})
        pr = json.loads(r.text or r.content)
        prs.append(pr)
        prtext.append(pr['html_url'] + ' - ' + pr['title'])
    print("--- queried %s prs" % len(prs))

    print("branch %s" % branch)

    # assemble
    print('--- creating branch %s' % branch)
    r = call(['git', 'branch', '-D', branch])
    r = call(['git', 'checkout', '-b', branch])
    assert not r
    for pr in prs:
        pr_number = pr['number']
        pr_url = pr['head']['repo']['clone_url']
        pr_ref = pr['head']['ref']
        print(f'--- pr {pr_number} --- pulling {pr_url} branch {pr_ref}')
        while True:
            r = call(['git', 'pull', '--no-ff', '--no-edit', pr_url, pr_ref])
            if r == 0:
                break
            elif r == 1:
                print(f'Unable to access {pr_url}, retrying..')
            elif r == 128:
                message = f'Unable to resolve conflict when merging PR#{pr_number}'
                raise Exception(message)
            else:
                message = ('Exiting due to an unknown failure when pulling '
                           f'PR#{pr_number}')
                raise Exception(message)
    if cli.trailers:
        cmd = ['git', 'commit', '--am', '--no-edit']
        cmd.extend(f'--trailer={t}' for t in cli.trailers)
        if call(cmd) != 0:
            print('Failed to set git trailers!')
            sys.exit(1)

    print('--- done. these PRs were included:')
    print('\n'.join(prtext).encode('ascii', errors='ignore').decode())
    print('--- perhaps you want to: ./run-make-check.sh && git push ci %s' % branch)


if __name__ == '__main__':
    main()
