#!/usr/bin/python3

updates_repo = "QubesOS/updates-status"

q_template="""query {{
    resource(url:"{repo}") {{
        ... on Repository {{
            issues({pagination} states:OPEN) {{
                edges {{
                    node {{
                        body
                        number
                        createdAt
                        title
                        id
                        labels(first:100) {{
                            nodes {{
                                name
                            }}
                        }}
                        reactions(first:100) {{
                            nodes {{
                                content
                            }}
                        }}
                    }}
                    cursor
                }}
                pageInfo {{
                    hasNextPage
                }}
            }}
        }}
    }}
}}"""

comment_mutation="""mutation {{
    addComment(input:{{subjectId:"{subjectid!s}",body:"{body!s}"}}) {{
        subject
    }}
}}"""

import requests
import json
import re
import argparse
import subprocess
import datetime

parser = argparse.ArgumentParser("updates-upload-repo")

parser.add_argument('--verbose', action='store_true')
parser.add_argument('--days', action='store', type=int, default=5, help='ensure package at least this time in testing (default: %(default)d)')
parser.add_argument('release_name')
parser.add_argument('repo_name', help='target repo name, like \'current\' or \'templates-itl\'; list will include components applicable to the repo')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--all', action='store_true', help='upload all matching components')
group.add_argument('--list', action='store_true', help='only list components, do not upload')
group.add_argument('--components', nargs='*', default=[])
group.add_argument('--dists', nargs='*', default=[], help='upload those dists only, applicable for uploading templates')

def graphql(query):
    r = requests.post("https://api.github.com/graphql",
            json={'query': query},
            auth=())
    if not r.ok:
        raise Exception('API call failed: %s' % r.text)
    return r.text

def sign_command(cmd):
    p = subprocess.Popen(['qubes-gpg-client', '--clearsign', '-u', 'commands'],
            stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    (out, _) = p.communicate(cmd.encode())
    return out.decode()

def comment_issue(issue_id, issue_no, body, repo=updates_repo):
    r = requests.post('https://api.github.com/repos/{}/issues/{}/comments'.format(
                repo, issue_no),
            json={'body': body}, auth=())
    if not r.ok:
        raise Exception('API call failed: %d %s' % (r.status_code, r.text))

def close_issue(issue_id, issue_no, repo=updates_repo):
    # Github API v4 lack closeIssue mutation
    # https://platform.github.community/t/add-closeissue-mutation/3250/3

    r = requests.patch('https://api.github.com/repos/{}/issues/{}'.format(
                repo, issue_no),
            json={'state': 'closed'}, auth=())
    if not r.ok:
        raise Exception('API call failed: %d %s' % (r.status_code, r.text))

def main():
    args = parser.parse_args()

    q = q_template.format(repo=updates_repo, pagination='first:100')

    data = graphql(q)
    data = json.loads(data)
    issues_and_commands = [e['node'] for e in data['data']['resource']['issues']['edges']]
    while data['data']['resource']['issues']['pageInfo']['hasNextPage']:
        cursor = data['data']['resource']['issues']['edges'][-1]['cursor']
        q = q_template.format(repo=updates_repo, pagination="first:100 after:\"{}\"".format(cursor))
        data = graphql(q)
        data = json.loads(data)
        issues_and_commands.extend(e['node'] for e in data['data']['resource']['issues']['edges'])

    for issue in issues_and_commands:
        issue_no = issue['number']
        labels = [l['name'] for l in issue['labels']['nodes']]
        reactions = [r['content'] for r in issue['reactions']['nodes']]
        match = re.search(r"`Upload [a-z0-9. -]* {}(?: vm-[a-z0-9+-]*)? repo`".format(args.repo_name), issue['body'])
        if not match:
            if args.verbose:
                print("Warning: issue {} has no cmd for {}".format(issue_no, args.repo_name))
            continue
        cmd = match.group(0).strip('`')
        _, component, commit_id, release_name, repo_name, repo = cmd.split(' ', 5)
        dist = 'all'
        if repo.startswith('vm-'):
            dist = repo.split(' ')[0]
        if release_name != args.release_name:
            continue
        days = (datetime.datetime.now() -
                datetime.datetime.strptime(issue['createdAt'] , '%Y-%m-%dT%H:%M:%SZ')).days
        if args.list:
            extra_info = ''
            thumb_up = len(list(r for r in reactions if r == 'THUMBS_UP'))
            thumb_down = len(list(r for r in reactions if r == 'THUMBS_DOWN'))
            if thumb_up:
                extra_info += '\033[32m {}\U0001F44D \033[m'.format(thumb_up)
            if thumb_down:
                extra_info += '\033[31m {}\U0001F44E \033[m'.format(thumb_down)
            if 'buggy' in labels:
                extra_info += '\033[31m buggy\033[m'
            if days >= args.days:
                color = '\033[32m'
            else:
                color = '\033[31m'
            print("issue no: {} {}{} ({} days ago)\033[m{} cmd: {}".format(issue_no, color, issue['title'], days, extra_info, cmd))
        else:
            if args.all or component in args.components or \
                    (component == 'linux-template-builder' and dist in args.dists):
                if args.dists and component != 'linux-template-builder':
                    print("ERROR: selective dists upload for non-templates not supported by this script")
                    sys.exit(1)
                if 'buggy' in labels:
                    print('WARNING: skipping buggy {} for {}'.format(component, dist))
                    continue
                if days < args.days and args.repo_name in ('current', 'templates-itl', 'templates-community'):
                    print('WARNING: {} for {} not long enough in testing'.format(component, dist))
                    continue
                print(cmd)
                signed_command = sign_command(cmd)
                comment_issue(issue['id'], issue['number'], signed_command)
                #if args.repo_name == 'current':
                #    close_issue(issue['id'], issue['number'])


if __name__ == '__main__':
    main()
