#!/usr/bin/env python3
import os
import re
import subprocess

import click


ROOT_DIR = os.path.dirname(
    os.path.dirname(os.path.abspath(__file__)),
)


@click.group()
def cli():
    """Command line tool for managing releases.

    To do a chalice release, run these commands::

        \b
        $ NEXT_VERSION=$(jmeslog query next-version)
        $ scripts/release bump-version --version-number ${NEXT_VERSION}
        $ git add -A .
        $ git commit -m "Bumping version to $NEXT_VERSION"
        $ scripts/release tag-release
        $ scripts/release build-release
        $ git push upstream master --tags
        $ twine upload dist/chalice-*

    """
    pass


@cli.command('bump-version')
@click.option('--version-number')
def bump_version(version_number):
    """Update necessary files with next version number."""
    print(f"Bumping version to: {version_number}")
    _create_new_changelog_release()
    for filename, replacer in get_files_to_change().items():
        print("Bumping version in %s" % filename)
        with open(filename, 'r') as f:
            contents = f.read()
            if callable(replacer):
                new_contents = replacer(version_number, contents)
            else:
                new_contents = _regex_based_version_bump(
                    version_number,
                    replacer,
                    contents)
            with open(filename, 'w') as f:
                f.write(new_contents)


def _create_new_changelog_release():
    # This takes everything from .changes/next-release/ and creates
    # a new release entry for them.
    subprocess.check_call(['jmeslog', 'new-release'])


@cli.command('build-release')
def build_release():
    """Build sdist/whl files."""
    original = os.getcwd()
    os.chdir(ROOT_DIR)
    try:
        subprocess.check_call(
            ['python', 'setup.py', 'sdist', 'bdist_wheel']
        )
    finally:
        os.chdir(original)


@cli.command('tag-release')
def tag_release():
    """Create a git tag based on the current version number."""
    # We're assuming that setup.py has already been updated
    # manually or using scripts/release/bump-version so the
    # current version in setup.py is the version number we should tag.
    version_number = get_current_version_number()
    click.echo("Tagging %s release" % version_number)
    subprocess.check_call(
        ['git', 'tag', '-a', version_number,
         '-m', 'Tagging %s release' % version_number],
    )


@cli.command('get-version')
def get_version():
    """Print the current version number in setup.py."""
    click.echo(get_current_version_number())


def get_files_to_change():
    # A mapping of all files that require version bumps.
    # You can either specify:
    # * Tuple[str, str] - regex to search, replacement string
    # * Callable[[str, str], str] - function to handle custom logic
    files_with_version_numbers = {
        'chalice/app.py': ("__version__ = '.*'", "__version__ = '{version}'"),
        'CHANGELOG.rst': update_changelog,
        'docs/source/conf.py': update_doc_conf,
        'setup.py': ("version='(.*)'", "version='{version}'"),
    }
    return files_with_version_numbers


def _regex_based_version_bump(next_version_number, replacer, contents):
    regex = replacer[0]
    replacement = replacer[1].format(version=next_version_number)
    new_contents = re.sub(regex, replacement, contents)
    return new_contents


def update_changelog(next_version_number, contents):
    output = subprocess.check_output(['jmeslog', 'render'])
    return output.decode('utf-8')


def update_doc_conf(next_version_number, contents):
    # For the docs the 'version' is only X.Y
    # and the release is X.Y.Z
    version = '.'.join(next_version_number.split('.')[:2])
    release = next_version_number
    new_contents = []
    for line in contents.splitlines():
        if line.startswith('version ='):
            new_contents.append("version = u'%s'" % version)
        elif line.startswith('release = '):
            new_contents.append("release = u'%s'" % release)
        else:
            new_contents.append(line)
    # Ensure the file ends with a newline.
    new_contents.append('')
    return '\n'.join(new_contents)


def get_next_version_number(release_type):
    # Returns a string like '1.0.0'.
    current = get_current_version_number()
    # Convert to a list of ints: [1, 0, 0].
    version_parts = list(int(i) for i in current.split('.'))
    # We've already validated that release_type is from a fixed
    # list of choices so we know it's going to be one of these.
    # We only support integer version parts, which shouldn't be
    # a problem now that we're post 1.0.
    if release_type == 'patch':
        version_parts[-1] += 1
    elif release_type == 'minor':
        version_parts[1] += 1
        version_parts[-1] = 0
    return '.'.join(str(i) for i in version_parts)


def get_current_version_number():
    # We can avoid executing setup.py because we know
    # specifically how the version is hardcoded in the setup.py file.
    # This won't work for the general case.
    regex = re.compile("version='(.*)',")
    with open(os.path.join(ROOT_DIR, 'setup.py')) as f:
        for line in f:
            match = regex.search(line)
            if match is not None:
                return match.groups()[0]
    raise RuntimeError("Could not find version number from setup.py")


def main():
    return cli()


if __name__ == '__main__':
    main()
