#!/usr/bin/env python
"""
Use URL to EUPS candidate tag file to git tag repos with official version
"""

# Technical Debt
# --------------
# - sort out the certificate so we don't have to supress warnings
# - completely hide eups-specifics from this file
# - skips non-github repos - can add repos.yaml knowhow to address this
# - worth doing the smart thing for externals? (yes for Sims)
# - deal with authentication version

# Known Bugs
# ----------
# Yeah, the candidate logic is broken, will fix


from __future__ import print_function

import logging
import os
import sys
import argparse
import textwrap
import string
from datetime import datetime
from getpass import getuser
import urllib3
from .. import codetools


def parse_args():
    """Parse command-line arguments"""
    user = getuser()

    parser = argparse.ArgumentParser(
        prog='github-tag-version',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description=textwrap.dedent("""

        Tag all repositories in a GitHub org using a team-based scheme

        Examples:
        github-tag-version --org lsst --team 'Data Management' w.2015.33 b1630

        github-tag-version --org lsst --team 'Data Management' \
            --team 'External' --candidate v11_0_rc2 11.0.rc2 b1679

        Note that the access token must have access to these oauth scopes:
            * read:org
            * repo

        The token generated by `github-auth --user` should have sufficient
        permissions.
        """),
        epilog='Part of codekit: https://github.com/lsst-sqre/sqre-codekit'
    )

    # for safety, default to dummy org <user>-shadow
    # will fail for most people but see github_fork_repos in this module
    # on how to get your own

    parser.add_argument('tag')
    parser.add_argument('manifest')
    parser.add_argument(
        '--org',
        default=user + '-shadow')
    parser.add_argument(
        '--team',
        action='append',
        required=True,
        help="team whose repos may be tagged (can specify several times")
    parser.add_argument('--candidate')
    parser.add_argument('--dry-run', action='store_true')
    parser.add_argument(
        '--tagger',
        help='Name of person making the tag - defaults to gitconfig value')
    parser.add_argument(
        '--email',
        help='Email address of tagger - defaults to gitconfig value')
    parser.add_argument(
        '--token-path',
        default='~/.sq_github_token_delete',
        help='Use a token (made with github-auth) in a non-standard location')
    parser.add_argument(
        '--token',
        default=None,
        help='Literal github personal access token string')
    parser.add_argument(
        '-d', '--debug',
        action='store_true',
        default=os.getenv('DM_SQUARE_DEBUG'),
        help='Debug mode')
    parser.add_argument('-v', '--version',
                        action='version', version='%(prog)s 0.5')
    return parser.parse_args()


def main():
    """Create the tag"""
    # pylint: disable=too-many-locals,too-many-nested-blocks,too-many-branches
    # pylint: disable=too-many-statements
    # Although maybe that is a hint that we should break this up...
    args = parse_args()

    orgname = args.org
    version = args.tag

    # if email not specified, try getting it from the gitconfig
    email = args.email
    if email is None:
        email = codetools.gituseremail()
        if email is None:
            sys.exit("Specify --email option")
    if args.debug:
        print("email is " + email)
    # ditto for the name of the tagger
    tagger = args.tagger
    if tagger is None:
        tagger = codetools.gitusername()
        if tagger is None:
            sys.exit("Specify --tagger option")
    if args.debug:
        print("tagger name is " + tagger)

    # The candidate is assumed to be the requested EUPS tag unless
    # otherwise specified with the --candidate option The reason to
    # currently do this is that for weeklies and other internal builds,
    # it's okay to eups publish the weekly and git tag post-facto. However
    # for official releases, we don't want to publish until the git tag
    # goes down, because we want to eups publish the build that has the
    # official versions in the eups ref.

    if args.candidate:
        candidate = args.candidate
    else:
        candidate = args.tag

    eupsbuild = args.manifest  # sadly we need to "just" know this
    message_template = 'Version {v} release from {c}/{b}'
    message = message_template.format(v=version, c=candidate, b=eupsbuild)
    eupspkg_site = 'https://sw.lsstcorp.org/eupspkg/'

    # generate timestamp for github API
    now = datetime.utcnow()
    timestamp = now.isoformat()[0:19] + 'Z'
    if args.debug:
        print(timestamp)

    tagstuff = dict(name=tagger,
                    email=email,
                    date=timestamp)

    if args.debug:
        print(tagstuff)

    ghb = codetools.login_github(token_path=args.token_path, token=args.token)
    if args.debug:
        print(type(ghb))

    if args.debug:
        print("Tagging repos in ", orgname)

    # generate eups-style version
    # eups no likey semantic versioning markup, wants underscores

    # Python 2/3 compatibility
    try:
        # Python 2
        cmap = string.maketrans('.-', '__')
    except AttributeError:
        # Python 3
        cmap = str.maketrans('.-', '__')  # pylint: disable=no-member

    # eups_version = version.translate(map)
    eups_candidate = candidate.translate(cmap)

    # construct url
    eupspkg_taglist = '/'.join((eupspkg_site, 'tags',
                                eups_candidate + '.list'))
    if args.debug:
        print(eupspkg_taglist)

    http = urllib3.poolmanager.PoolManager()

    # supress the certificate warning - technical debt
    urllib3.disable_warnings()  # NOQA
    if args.debug:
        # pylint: disable=fixme
        # FIXME what's going on here? assigning a logger to a package?
        logging.getLogger('requests.packages.urllib3')  # NOQA
        stream_handler = logging.StreamHandler()
        logger = logging.getLogger('github3')
        logger.addHandler(stream_handler)
        logger.setLevel(logging.DEBUG)

    manifest = http.request('GET', eupspkg_taglist)

    if manifest.status >= 300:
        sys.exit("Failed GET")

    entries = manifest.data.splitlines()

    for entry in entries:
        # Python 2/3 accomodation
        if not isinstance(entry, str):
            entry = str(entry, 'utf-8')
        # skip commented out and blank lines
        if entry.startswith('#'):
            continue
        if entry.startswith('EUPS'):
            continue
        if entry == '':
            continue

        # extract the repo and eups tag from the entry
        (upstream, _, eups_tag) = entry.split()
        if args.debug:
            print(upstream, eups_tag)

        # okay so we still have the data dirs on gitolite
        # for now, just skip them and record them.
        # question is should they be on different tagging scheme anyway?
        # at this point these are: afwdata, astrometry_net_data qserv_testdata

        repo = ghb.repository(orgname, upstream)

        # if the repo is not in github skip it for now
        # see TD
        if not hasattr(repo, 'name'):
            print('!!! SKIPPING', upstream, (60 - len(upstream)) * '-')
            continue

        if not sum(1 for _ in repo.teams()):
            print('!!! repo has NO teams -- SKIPPING', upstream,
                  (45 - len(upstream)) * '-')
            continue

        for team in repo.teams():
            if team.name in args.team:
                if args.debug or args.dry_run:
                    print(repo.name.ljust(40), 'found in', team.name)
                sha = codetools.eups2git_ref(eups_ref=eups_tag,
                                             repo=repo.name,
                                             eupsbuild=eupsbuild,
                                             debug=args.debug)
                if args.debug or args.dry_run:
                    print('Will tag sha: {sha} as {v} (was {t})'.format(
                        sha=sha, v=version, t=eups_tag))

                if not args.dry_run:
                    try:
                        # create_tag() returns a Tag object on success or None
                        # on failure
                        tag = repo.create_tag(tag=version,
                                              message=message,
                                              sha=sha,
                                              obj_type='commit',
                                              tagger=tagstuff,
                                              lightweight=False)
                        if tag is None:
                            raise RuntimeError('failed to create git tag')

                    except Exception as exc:  # pylint: disable=broad-except
                        print('OOPS: -------------------')
                        print(str(exc))
                        print('OOPS: -------------------')

            else:
                if args.debug:
                    print('No action for', repo.name,
                          'belonging to', team.name)


if __name__ == '__main__':
    main()
