#!/usr/bin/env python3

"""
Deploys the package to PyPI and uploads docs. This script is called in circle.yml.
"""
import os
import shlex
import subprocess
import sys

CIRCLECI_ENV_VAR = 'CIRCLECI'


def _shell(cmd, check=True, stdin=None, stdout=None, stderr=None):  # pragma: no cover
    """Runs a subprocess shell with check=True by default"""
    return subprocess.run(cmd, shell=True, check=check, stdin=stdin, stdout=stdout, stderr=stderr)


def _pypi_push(dist):
    """Push created package to PyPI.

    Requires the following defined environment variables:
        - TWINE_USERNAME: The PyPI username to upload this package under
        - TWINE_PASSWORD: The password to the user's account

    Args:
        dist (str):
            The distribution to push. Must be a valid directory; shell globs are
            NOT allowed.
    """
    # Register all distributions and wheels with PyPI. We have to list the dist
    # directory and register each file individually because `twine` doesn't
    # handle globs.
    for filename in os.listdir(dist):
        full_path = os.path.join(dist, filename)
        if os.path.isfile(full_path):
            # This will fail if the project has never been uploaded, so use check=false
            _shell('twine register ' + shlex.quote(full_path), check=False)

    _shell('twine upload ' + shlex.quote(dist + '/*'))


def deploy(target):
    """Deploys the package and documentation.

    Proceeds in the following steps:

    1. Ensures proper environment variables are set and checks that we are on Circle CI
    2. Tags the repository with the new version
    3. Creates a standard distribution and a wheel
    4. Updates version.py to have the proper version
    5. Commits the ChangeLog, AUTHORS, and version.py file
    6. Pushes to PyPI
    7. Pushes the tags and newly committed files

    Raises:
        `EnvironmentError`:
            - Not running on CircleCI
            - `*_PYPI_USERNAME` and/or `*_PYPI_PASSWORD` environment variables
               are missing
            - Attempting to deploy to production from a branch that isn't master
    """
    # Ensure proper environment
    if not os.getenv(CIRCLECI_ENV_VAR):     # pragma: no cover
        raise EnvironmentError('Must be on CircleCI to run this script')

    current_branch = os.getenv('CIRCLE_BRANCH')
    if (target == 'PROD') and (current_branch != 'master'):
        raise EnvironmentError((
            'Refusing to deploy to production from branch {current_branch!r}. '
            'Production deploys can only be made from master.'
        ).format(current_branch=current_branch))

    if target in ('PROD', 'TEST'):
        pypi_username = os.getenv('{target}_PYPI_USERNAME'.format(target=target))
        pypi_password = os.getenv('{target}_PYPI_PASSWORD'.format(target=target))
    else:
        raise ValueError(
            "Deploy target must be 'PROD' or 'TEST', got {target!r}.".format(target=target))

    if not (pypi_username and pypi_password):  # pragma: no cover
        raise EnvironmentError((
            "Missing '{target}_PYPI_USERNAME' and/or '{target}_PYPI_PASSWORD' "
            "environment variables. These are required to push to PyPI."
        ).format(target=target))

    # Twine requires these environment variables to be set. Subprocesses will
    # inherit these when we invoke them, so no need to pass them on the command
    # line. We want to avoid that in case something's logging each command run.
    os.environ['TWINE_USERNAME'] = pypi_username
    os.environ['TWINE_PASSWORD'] = pypi_password

    # Set up git on circle to push to the current branch
    _shell('git config --global user.email "oss@cloverhealth.com"')
    _shell('git config --global user.name "Circle CI"')
    _shell('git config push.default current')

    # Obtain the version to deploy
    ret = _shell('make version', stdout=subprocess.PIPE)
    version = ret.stdout.decode('utf-8').strip()

    print('Deploying version {version!r}...'.format(version=version))

    # Tag the version
    _shell('git tag -f -a {version} -m "Version {version}"'.format(version=version))

    # Update the version
    _shell(
        'sed -i.bak "s/^__version__ = .*/__version__ = {version!r}/" */version.py'.format(
            version=version))

    # Create a standard distribution and a wheel
    _shell('python setup.py sdist bdist_wheel')

    # Add the updated ChangeLog and AUTHORS
    _shell('git add ChangeLog AUTHORS */version.py')

    # Start the commit message with "Merge" so that PBR will ignore it in the
    # ChangeLog. Use [skip ci] to ensure CircleCI doesn't recursively deploy.
    _shell('git commit --no-verify -m "Merge autogenerated files [skip ci]"')

    # Push the distributions to PyPI.
    _pypi_push('dist')

    # Push the tag and AUTHORS / ChangeLog after successful PyPI deploy
    _shell('git push --follow-tags')

    print('Deployment complete. Latest version is {version}.'.format(version=version))


if __name__ == '__main__':  # pragma: no cover
    if len(sys.argv) != 2:
        raise RuntimeError('Require one argument indicating deploy target: `prod` or `test`.')

    deploy(sys.argv[1].upper())
