#!python

import os
import sys
import json
import argparse
import urllib
import gitlab
import subprocess


class Config:
    DEFAULTS = {
        'token': None
    }

    def __init__(self, *args, **kwargs):
        cnf = Config.DEFAULTS
        base = os.environ['HOME']
        if not base:
            base = '/tmp'
        try:
            with open(os.path.join(base, '.gitgud.json'), 'r') as f:
                cnf = json.load(f)
        except Exception as e:
            pass
        self.base = base
        self.cnf = cnf

    def __getitem__(self, key):
        return self.cnf[key]

    def __setitem__(self, key, value):
        self.cnf[key] = value
        with open(os.path.join(self.base, '.gitgud.json'), 'w') as f:
            json.dump(self.cnf, f)


class LazyGl():
    def __init__(self, token):
        self.gl = None
        self.token = token
        self.project_name = os.popen(
            "git remote get-url origin | cut -d ':' -f2 | sed \"s/\\.git//\"").read().strip()
        self.project_url = urllib.parse.quote(self.project_name, safe='')

    def __call__(self):
        if self.gl is None:
            self.gl = gitlab.Gitlab(
                'https://gitlab.com', private_token=self.token)
            self.gl.auth()
        return self.gl


def cmd():
    parser = argparse.ArgumentParser(prog='git gud')
    subparsers = parser.add_subparsers(dest='parser_cmd')
    parser_init = subparsers.add_parser(
        'init', help='Initialize a new git repo with support for the branching model.')
    parser_feature = subparsers.add_parser(
        'feature', help='Manage your feature branches.')
    # parser_bugfix = subparsers.add_parser('bugfix', help='Manage your bugfix branches.')
    parser_release = subparsers.add_parser(
        'release', help='Manage your release branches.')
    # parser_hotfix = subparsers.add_parser('hotfix', help='Manage your hotfix branches.')
    # parser_support = subparsers.add_parser('support', help='Manage your support branches.')
    # parser_log = subparsers.add_parser('log', help='Show log deviating from base branch.')

    subparsers_feature = parser_feature.add_subparsers(
        dest='feature_cmd', help='Feature branch management')
    subparsers_release = parser_release.add_subparsers(
        dest='release_cmd', help='Release branch management')

    parser_feature_start = subparsers_feature.add_parser('start')
    parser_feature_finish = subparsers_feature.add_parser('finish')
    parser_feature_delete = subparsers_feature.add_parser('delete')

    parser_release_start = subparsers_release.add_parser('start')
    parser_release_finish = subparsers_release.add_parser('finish')
    parser_release_delete = subparsers_release.add_parser('delete')

    # FEATURE START
    parser_feature_start.add_argument('name', type=str)
    parser_feature_start.add_argument(
        'base', type=str, nargs='?', default='develop')

    # FEATURE FINISH
    parser_feature_finish.add_argument('name', type=str)
    parser_feature_finish.add_argument(
        '--merge', action='store_true', help='try to trigger Gitlab merge')

    # FEATURE DELETE
    parser_feature_delete.add_argument('name', type=str)

    # RELEASE START
    parser_release_start.add_argument('version', type=str)
    parser_release_start.add_argument(
        'base', type=str, nargs='?', default='develop')

    # RELEASE FINISH
    parser_release_finish.add_argument('version', type=str)

    # RELEASE DELETE
    parser_release_delete.add_argument('version', type=str)

    return parser


class ReleaseDelete():
    COMMAND = 'delete'

    def __call__(self, parser, args):
        pass


class ReleaseStart():
    COMMAND = 'start'

    def __call__(self, parser, args):
        subprocess.run(
            "git config --local gitflow.branch.release/{}.base {}".format(
                args.version, args.base
            ).split()
        )
        subprocess.run(
            "git checkout -b release/{} {}".format(
                args.version, args.base).split()
        )
        subprocess.run('git fetch -q origin'.split())
        subprocess.run(
            'git push -u origin release/{}:release/{}'.format(args.version, args.version).split())
        subprocess.run(
            'git fetch -q origin release/{}'.format(args.version).split())
        project = args.gl().projects.get(args.gl.project_url)
        project.mergerequests.create({
            'source_branch': 'release/{}'.format(args.version),
            'target_branch': 'master',
            'title': 'Release {}'.format(args.version),
            'labels': ['release'],
            'remove_source_branch': True,
            'allow_collaboration': True,
            'squash': True,
        })


class ReleaseFinish():
    COMMAND = 'finish'

    def __call__(self, parser, args):
        project = args.gl().projects.get(args.gl.project_url)
        mr = list(filter(
            lambda x: x.source_branch == 'release/{}'.format(args.version),
            project.mergerequests.list()
        ))
        if not len(mr):
            print(
                'Could not find Gitlab merge request for branch "release/{}", exiting.'.format(args.version))
            return
        mr = mr[0]

        print('Accept the release MR')
        mr.merge()

        subprocess.run(
            "git fetch -q origin".format(
                args.version
            ).split()
        )

        print('Tag the merge commit {}'.format(mr.merge_commit_sha))
        subprocess.run(
            "git tag -m {} -a {} {}".format(
                args.version, args.version, mr.merge_commit_sha).split()
        )
        print('propagate tag')
        subprocess.run(
            "git push --tags origin".split()
        )

        print('Realign develop')
        develop_mr = project.mergerequests.create({
            'source_branch': 'master',
            'target_branch': 'develop',
            'title': 'Release {}'.format(args.version),
            'labels': ['release'],
            'remove_source_branch': False,
            'allow_collaboration': True,
            'squash': False,
        })

        print('Accept the release MR')
        develop_mr.merge()

        subprocess.run(
            "git fetch -q origin".split()
        )

        subprocess.run(
            "git checkout develop".split()
        )

        subprocess.run(
            "git pull origin develop".split()
        )


class FeatureDelete():
    COMMAND = 'delete'

    def __call__(self, parser, args):
        project = args.gl().projects.get(args.gl.project_url)
        mr = list(filter(
            lambda x: x.source_branch == 'feature/{}'.format(args.name),
            project.mergerequests.list()
        ))
        if not len(mr):
            print(
                'WARN: Could not find Gitlab merge request for branch "feature/{}".'.format(args.name))
        else:
            mr = mr[0]
            mr.delete()
            print('Merge request deleted')

        subprocess.run(
            "git checkout develop".format(
                args.name
            ).split()
        )

        subprocess.run(
            "git branch -d feature/{}".format(
                args.name
            ).split()
        )
        subprocess.run(
            "git push --delete origin feature/{}".format(
                args.name
            ).split()
        )
        subprocess.run(
            "git fetch -q origin".format(
                args.name
            ).split()
        )


class FeatureFinish():
    COMMAND = 'finish'

    def __call__(self, parser, args):
        project = args.gl().projects.get(args.gl.project_url)
        mr = list(filter(
            lambda x: x.source_branch == 'feature/{}'.format(args.name),
            project.mergerequests.list()
        ))
        if not len(mr):
            print(
                'Could not find Gitlab merge request for branch "feature/{}", exiting.'.format(args.name))
            return
        mr = mr[0]
        print('Changing labels')
        mr.labels = list(
            filter(lambda x: x not in ['Doing', 'WIP'], mr.labels)) + ['Done']
        print('Remove WIP tag in title')
        mr.title = mr.title.replace('WIP:', '').lstrip()

        print('Save merge request')
        mr.save()

        print('Merge request URL:', mr.web_url)

        subprocess.run("git checkout develop".split())
        subprocess.run("git branch -d feature/{}".format(args.name).split())

        if args.merge:
            mr.merge()

        subprocess.run("git fetch -q origin".split())
        subprocess.run("git pull origin develop".split())


class FeatureStart():
    COMMAND = 'start'

    def __call__(self, parser, args):
        subprocess.run(
            "git config --local gitflow.branch.feature/{}.base {}".format(
                args.name, args.base
            ).split()
        )
        subprocess.run(
            "git checkout -b feature/{} {}".format(
                args.name, args.base).split()
        )
        subprocess.run('git fetch -q origin'.split())
        subprocess.run(
            'git push -u origin feature/{}:feature/{}'.format(args.name, args.name).split())
        subprocess.run(
            'git fetch -q origin feature/{}'.format(args.name).split())
        subprocess.run('git checkout feature/{}'.format(args.name).split())
        project = args.gl().projects.get(args.gl.project_url)
        project.mergerequests.create({
            'source_branch': 'feature/{}'.format(args.name),
            'target_branch': args.base,
            'title': 'WIP: feature {}'.format(args.name),
            'labels': ['WIP', 'Doing'],
            'remove_source_branch': True,
            'allow_collaboration': True,
            'squash': True,
        })


class Release():
    COMMAND = 'release'
    FTAB = {
        ReleaseStart.COMMAND:  ReleaseStart,
        ReleaseFinish.COMMAND: ReleaseFinish,
        ReleaseDelete.COMMAND: ReleaseDelete,
    }

    def __call__(self, parser, args):
        if args.release_cmd is None:
            parser.print_help()
            sys.exit(1)
        elif args.release_cmd in Release.FTAB.keys():
            Release.FTAB[args.release_cmd]()(parser, args)
        else:
            print('Release command not implemented', args.release_cmd)
            sys.exit(1)


class Feature():
    COMMAND = 'feature'
    FTAB = {
        FeatureStart.COMMAND: FeatureStart,
        FeatureFinish.COMMAND: FeatureFinish,
        FeatureDelete.COMMAND: FeatureDelete,
    }

    def __call__(self, parser, args):
        if args.feature_cmd is None:
            parser.print_help()
            sys.exit(1)
        elif args.feature_cmd in Feature.FTAB.keys():
            Feature.FTAB[args.feature_cmd]()(parser, args)
        else:
            print('Feature command not implemented', args.feature_cmd)
            sys.exit(1)


COMMAND_FTAB = {
    Feature.COMMAND: Feature,
    Release.COMMAND: Release,
}

if __name__ == '__main__':
    parser = cmd()
    args = parser.parse_args()
    args.file_config = Config()

    if not args.file_config['token']:
        print('Personal access token not found.')
        print('You can create one here : https://gitlab.com/profile/personal_access_tokens')
        print(' scope: api')
        print('')
        print('Token:')
        print('> ', end='')
        sys.stdout.flush()
        token = sys.stdin.readline().strip("\n")
        gl = gitlab.Gitlab('https://gitlab.com', private_token=token)
        gl.auth()
        args.file_config['token'] = token

    args.gl = LazyGl(args.file_config['token'])

    if args.parser_cmd is None:
        parser.print_help()
        sys.exit(1)
    elif args.parser_cmd in COMMAND_FTAB.keys():
        COMMAND_FTAB[args.parser_cmd]()(parser, args)
    else:
        print('Command not implemented', args.parser_cmd)
        sys.exit(1)
