#!/usr/bin/python

import os, sys, argparse, re, git, http.client, subprocess
import urlgrabber.progress, tarfile, shutil, gitdb, time, fnmatch
from datetime import datetime
from termcolor import colored
from urllib.parse import urlparse

# on Tuxfamily
ssh_path = '/var/www/galette.eu/download/plugins/'
ssh_host = 'galette.eu'
galette_dl_repo = 'https://galette.eu/download/plugins/'
is_local = False

local_dl_repo = os.path.join(
    os.path.dirname(
        os.path.dirname(os.path.abspath(__file__))
    ),
    'dist'
)
verbose = False
tagrefs = None
force = False
commit = None
extra = None
sign = True
assume_yes = False
nightly = False
ssh_key = False
tag_commit = None


def print_err(msg):
    """
    Display colored error message
    """
    print(colored(msg, 'red', attrs=['bold']))


def get_numeric_version(ver):
    """
    Returns all numeric version
    """
    return re.findall(r'\d+', ver)


def valid_version(ver):
    """
    Check if provided version is valid.

    Takes all digits in passed version, then reassemble them with dots
    to check if it is the same as original given one.
    """
    return '.'.join(get_numeric_version(ver)) == ver


def incr_version(ver):
    """
    Increment version number
    """
    version = get_numeric_version(ver)
    version[-1] = str(int(version[-1]) + 1)
    return version


def propose_version():
    """
    Propose new minor and major versions,
    according to existing git tags
    """
    last_major = '0'
    last_minor = '0'

    for tagref in tagrefs:
        tag = tagref.tag
        if valid_version(tag.tag):
            # last minor version is always the last one :)
            if tag.tag > last_minor:
                last_minor = tag.tag

            # last major version
            if len(tag.tag) == 5 and tag.tag > last_major:
                last_major = tag.tag

    if verbose:
        print('last minor: %s | last major %s' % (last_minor, last_major))

    # no version provided. propose one
    new_minor = None
    new_major = None

    if len(last_minor) == 5:
        # if the latest is a major version
        new_minor = last_minor + '.1'
    else:
        new_minor = '.'.join(incr_version(last_minor))

    new_major = '.'.join(incr_version(last_major))

    print("""Proposed versions:
    minor: %s
    major: %s
    """ % (new_minor, new_major))


def get_latest_version():
    """
    Look for latest version
    """
    global tag_commit

    last = None
    for tagref in tagrefs:
        tag = tagref.name
        if tag is not None and valid_version(tag):
            # last minor version is always the last one :)
            if last is None or tag > last:
                last = tag
                tag_commit = tagref.commit

    return tag


def is_existing_version(ver):
    """
    Look specified version exists
    """
    for tagref in tagrefs:
        tag = tagref.tag
        if valid_version(tag.tag):
            if tag.tag == ver:
                return True
    return False

def ask_user_confirm(msg):
    """
    Ask user his confirmation
    """
    if assume_yes:
        return True
    else:
        while True:
            sys.stdout.write(msg)
            choice = input().lower()
            if choice == 'y' or choice == 'yes':
                return True
            elif choice == 'n' or choice == 'no':
                return False
            else:
                print_err(
                    "Invalid input. Please enter 'yes' or 'no' (or 'y' or 'n')."
                )


def get_rel_name(buildver):
    """
    Build archive name from command line parameters
    That would be used for git archiving prefix and archive name
    """
    archive_name = None

    if commit and extra:
        now = datetime.now()
        archive_name = 'galette-plugin-oauth2-%s-%s-%s-%s' % (
            buildver,
            extra,
            now.strftime('%Y%m%d'),
            commit
        )
    elif nightly:
        archive_name = 'galette-plugin-oauth2-dev'
    else:
        archive_name = 'galette-plugin-oauth2-%s' % buildver

    return archive_name


def _do_build(ver):
    """
    Proceed build
    """
    global is_local

    exists = False
    ascexists = False
    rel_name = get_rel_name(ver)
    archive_name = rel_name + '.tar.bz2'
    galette_archive = os.path.join(
        local_dl_repo,
        archive_name
    )

    if not force:
        # first check if a version
        local = False
        ascLocal = False

        url = galette_dl_repo + '/' + archive_name
        urlasc = '%s.asc' % url

        if is_local:
            exists = os.path.isfile(url)
        else:
            parsed = urlparse(url)

            connection = http.client.HTTPConnection(parsed[1], 80)
            connection.request('HEAD', parsed[2])
            response = connection.getresponse()
            exists = response.status == 200

        if not exists:
            # also check from local repo
            exists = os.path.exists(galette_archive)
            if exists:
                local = True

        if is_local:
            ascexists = os.path.isfile(urlasc)
        else:
            ascparsed = urlparse(urlasc)
            connection = http.client.HTTPConnection(ascparsed[1], 80)
            connection.request('HEAD', ascparsed[2])
            response = connection.getresponse()
            ascexists = response.status == 200

        if not ascexists:
            # also check from local repo
            ascexists = os.path.exists(
                os.path.join(
                    local_dl_repo,
                    archive_name + '.asc'
                )
            )
            if ascexists:
                ascLocal = True

    if exists or ascexists:
        msg = None
        if exists:
            loctxt = ''
            if local:
                loctxt = 'locally '
            msg = 'Release %s already %sexists' % (rel_name, loctxt)

        if ascexists:
            loctxt = ''
            if ascLocal:
                loctxt = ' locally'
            if msg is not None:
                msg += ' and has been %ssigned!' % loctxt
            else:
                msg += 'Release has been %ssigned!' % loctxt

        msg += '\n\nYou will *NOT* build another one :)'
        print_err(msg)
    else:
        print('Building %s...' % rel_name)

        archive_cmd_pattern = 'git archive --prefix=%s/ %s | bzip2 > %s'
        if commit and extra or nightly:
            archive_cmd = archive_cmd_pattern % (
                rel_name,
                commit,
                galette_archive
            )
        else:
            archive_cmd = archive_cmd_pattern % (
                rel_name,
                ver,
                galette_archive
            )

        if verbose:
            typestr = 'Tag'
            typever = ver

            if commit and extra:
                typestr = 'Commit'
                typever = commit

            print('Release name: %s, %s: %s, Dest: %s' % (
                rel_name,
                typestr,
                typever,
                galette_archive
            ))
            print('Archive command: %s' % archive_cmd)

        if commit and extra:
            print('Archiving GIT commit %s' % commit)
        else:
            print('Archiving GIT tag %s' % ver)

        p1 = subprocess.Popen(archive_cmd, shell=True)
        p1.communicate()

        print('Adding vendor libraries')
        add_libs(rel_name, galette_archive)

        if sign:
            do_sign(galette_archive)

        upload = ask_user_confirm(
            'Do you want to upload archive %s? [yes/No] ' % galette_archive
        )

        if upload:
            do_upload(galette_archive)


def do_sign(archive):
    sign_cmd = 'gpg --detach-sign --armor %s' % archive
    p1 = subprocess.Popen(sign_cmd, shell=True)
    p1.communicate()


def do_upload(galette_archive):
    """
    proceed file upload
    :param galette_archive:
    :return:
    """
    global is_local

    if is_local:
        do_cp(galette_archive)
    else:
        do_scp(galette_archive)


def do_scp(archive):
    global ssh_key, ssh_host, ssh_path

    path = ssh_path
    if extra:
        path += 'dev/'

    if ssh_key:
        scp_cmd = 'scp -i %s %s* %s:%s' % (ssh_key, archive, ssh_host, path)
    else:
        scp_cmd = 'scp -r %s* %s:%s' % (archive, ssh_host, path)
    print(scp_cmd)
    p1 = subprocess.Popen(scp_cmd, shell=True)
    p1.communicate()


def do_cp(archive):
    global galette_dl_repo

    path = galette_dl_repo
    if extra:
        path = os.path.join(path, 'dev')

    shutil.copyfile(
        archive,
        os.path.join(path, os.path.basename(archive))
    )


def add_libs(rel_name, galette_archive):
    """
    Add external libraries to the archive
    """
    galette = tarfile.open(galette_archive, 'r|bz2', format=tarfile.GNU_FORMAT)
    src_dir = os.path.join(local_dl_repo, 'src')
    if not os.path.exists(src_dir):
        os.makedirs(src_dir)
    galette.extractall(path=src_dir)
    galette.close()

    composer_dir = os.path.join(src_dir, rel_name)
    has_composer = os.path.exists(
        os.path.join(
            composer_dir,
            'composer.json'
        )
    )

    if has_composer:
        composer_cmd = 'composer install --no-dev'
        p1 = subprocess.Popen(composer_cmd, shell=True, cwd=composer_dir)
        p1.wait()

        #cleanup vendors
        for root, dirnames, filenames in os.walk(os.path.join(composer_dir, 'vendor')):
            #remove git directories
            for dirname in fnmatch.filter(dirnames, '.git*'):
                remove_dir = os.path.join(composer_dir, root, dirname)
                shutil.rmtree(remove_dir)
            #remove test directories
            for dirname in fnmatch.filter(dirnames, 'test?'):
                remove_dir = os.path.join(composer_dir, root, dirname)
                shutil.rmtree(remove_dir)
            #remove examples directories
            for dirname in fnmatch.filter(dirnames, 'example?'):
                remove_dir = os.path.join(composer_dir, root, dirname)
                shutil.rmtree(remove_dir)
            #remove doc directories
            for dirname in fnmatch.filter(dirnames, 'doc?'):
                remove_dir = os.path.join(composer_dir, root, dirname)
                shutil.rmtree(remove_dir)
            #remove composer stuff
            for filename in fnmatch.filter(filenames, 'composer*'):
                remove_file = os.path.join(composer_dir, root, filename)
                os.remove(remove_file)

            for dirname in dirnames:
                #remove Faker useless languages
                if root.endswith('src/Faker/Provider'):
                    if dirname not in ['en_US', 'fr_FR', 'de_DE']:
                        shutil.rmtree(os.path.join(root, dirname))
                #begin to remove tcpdf not used fonts
                if root.endswith('tcpdf/fonts'):
                    if dirname != 'dejavu-fonts-ttf-2.34':
                        shutil.rmtree(os.path.join(root, dirname))

            for filename in filenames:
                if os.path.islink(os.path.join(root, filename)):
                    os.remove(os.path.join(root, filename))
                #remove tcpdf not used fonts
                if root.endswith('tcpdf/fonts'):
                    if filename not in [
                            'dejavusansbi.ctg.z',
                            'dejavusansbi.z',
                            'dejavusansb.z',
                            'dejavusansi.ctg.z',
                            'dejavusansi.z',
                            'dejavusans.z',
                            'zapfdingbats.php',
                            'dejavusansb.ctg.z',
                            'dejavusansbi.php',
                            'dejavusansb.php',
                            'dejavusans.ctg.z',
                            'dejavusansi.php',
                            'dejavusans.php',
                            'helvetica.php'
                    ]:
                        os.remove(os.path.join(root, filename))

    galette = tarfile.open(galette_archive, 'w|bz2', format=tarfile.GNU_FORMAT)

    for i in os.listdir(src_dir):
        galette.add(
            os.path.join(src_dir, i),
            arcname=rel_name
        )

    galette.close()
    shutil.rmtree(src_dir)


def valid_commit(repo, c):
    """
    Validate commit existence in repository
    """
    global commit

    try:
        dformat = '%a, %d %b %Y %H:%M'
        repo_commit = repo.commit(c)

        commit = repo_commit.hexsha[:10]
        print(colored("""Commit information:
        Hash:          %s
        Author:        %s
        Authored date: %s
        Commiter:      %s
        Commit date:   %s
        Message:       %s""" % (
            commit,
            repo_commit.author,
            time.strftime(dformat, time.gmtime(repo_commit.authored_date)),
            repo_commit.committer,
            time.strftime(dformat, time.gmtime(repo_commit.committed_date)),
            repo_commit.message
        ), None, 'on_grey', attrs=['bold']))
        return True
    except gitdb.exc.BadObject:
        return False

def main():
    """
    Main method
    """
    global verbose, tagrefs, force, extra, assume_yes, nightly, sign, ssh_key, repo, ssh_host, ssh_path, galette_dl_repo, is_local

    parser = argparse.ArgumentParser(description='Release Galette OAuth2 Plugin')
    group = parser.add_mutually_exclusive_group()
    group.add_argument(
        '-v',
        '--version',
        help='Version to release'
    )
    group.add_argument(
        '-p',
        '--propose',
        help='Calculate and propose next possible versions',
        action='store_true'
    )
    parser.add_argument(
        '-c',
        '--commit',
        help='Specify commit to archive (-v required)'
    )
    parser.add_argument(
        '-e',
        '--extra',
        help='Extra version information (-c required)'
    )
    parser.add_argument(
        '-Y',
        '--assume-yes',
        help='Assume YES to all questions. Be sure to understand what you are doing!',
        action='store_true'
    )
    parser.add_argument(
        '-V',
        '--verbose',
        help='Be more verbose',
        action="store_true"
    )
    parser.add_argument(
        '-n',
        '--nightly',
        help='Build nightly',
        action="store_true"
    )
    parser.add_argument(
        '-l',
        '--local',
        help='Use local copy (defined from galette_dl_repo) rather than SSH',
        action='store_true'
    )
    parser.add_argument(
        '-k',
        '--ssh-key',
        help='SSH key to be used for uploading',
    )
    parser.add_argument(
        '-H',
        '--ssh-host',
        help='SSH host to upload to (default %s)' % ssh_host
    )
    parser.add_argument(
        '-P',
        '--ssh-path',
        help='Path on SSH host (default %s)' % ssh_path
    )
    parser.add_argument(
        '-d',
        '--download-url',
        help='Download URL (default %s)' % galette_dl_repo
    )
    parser.add_argument('-f', action='store_true')
    args = parser.parse_args()

    verbose = args.verbose

    if verbose:
        print(args)

    galette_repo = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    repo = git.Repo(galette_repo)
    tagrefs = repo.tags

    if args.f == True:
        force = ask_user_confirm(
            'Are you *REALLY* sure you mean -f when you typed -f? [yes/No] '
        )
    assume_yes = args.assume_yes

    if args.local:
        if not args.download_url:
            print_err('download_url is mandatory for local builds!')
            sys.exit(1)
        is_local = args.local

    else:
        if args.ssh_key:
            ssh_key = args.ssh_key

        if args.ssh_host:
            ssh_host = args.ssh_host

        if args.ssh_path:
            ssh_path = args.ssh_path

    if args.download_url:
        galette_dl_repo = args.download_url

    build = False
    buildver = None
    if args.nightly:
        nightly = True
        buildver = 'dev'
        args.commit = repo.commit('develop')
        if valid_commit(repo, args.commit):
            force = True
            build = True
            sign = False
            assume_yes = True
        else:
            print_err('Invalid commit ref %s' % args.commit)
    elif (args.extra or args.commit) and (not args.extra or not args.commit or not args.version):
        print_err('You have to specify --version --commit and --extra all together')
        sys.exit(1)
    elif args.commit and args.version and args.extra:
        if valid_commit(repo, args.commit):
            if verbose:
                print('Commit is valid')
            build = True
            buildver = args.version
            extra = args.extra
        else:
            print_err('Invalid commit ref %s' % args.commit)
    elif args.version:
        if not valid_version(args.version):
            print_err('%s is not a valid version number!' % args.version)
            sys.exit(1)
        else:
            # check if specified version exists
            if not is_existing_version(args.version):
                print_err('%s does not exist!' % args.version)
            else:
                build = True
                buildver = args.version
    elif args.propose:
        propose_version()
    else:
        buildver = get_latest_version()
        if force:
            build = True
        else:
            build = ask_user_confirm(
                'Do you want to build Galette OAUth2 Plugin version %s? [Yes/no] ' % buildver
            )

    if build:
        _do_build(buildver)


if __name__ == "__main__":
    main()
