#!/usr/bin/env python3

import argparse
import glob
import os
import shutil
import tempfile
from contextlib import contextmanager

import crayons
import setuptools.sandbox
import twine.commands.upload

REPO_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


@contextmanager
def cd(newdir):
    prevdir = os.getcwd()
    os.chdir(os.path.expanduser(newdir))
    try:
        yield
    finally:
        os.chdir(prevdir)


def _generate_dist(dist_type, setup_file, package_name, setup_args):
    message = "Generating {dist_type} for {package_name}.".format(
        dist_type=dist_type,
        package_name=package_name,
    )
    print(crayons.white(message, bold=True))

    setup_dir = os.path.dirname(setup_file)
    with cd(setup_dir):
        setuptools.sandbox.run_setup(setup_file, setup_args)

    message = "{dist_type} for {package_name} generated under {dir}.\n".format(
        dist_type=dist_type,
        package_name=package_name,
        dir=setup_dir,
    )
    print(crayons.green(message, bold=True))


def generate_bdist_wheel(setup_file, package_name, universal=False):
    if universal:
        _generate_dist("bdist_wheel", setup_file, package_name, ["bdist_wheel", "--universal"])
    else:
        _generate_dist("bdist_wheel", setup_file, package_name, ["bdist_wheel"])


def twine_upload(dist_dirs):
    message = "Uploading distributions under the following directories:"
    print(crayons.green(message, bold=True))
    for dist_dir in dist_dirs:
        print(crayons.yellow(dist_dir))
    twine.commands.upload.main(dist_dirs)


def cleanup(package_dir):
    build_dir = os.path.join(package_dir, "build")
    temp_dir = os.path.join(package_dir, "temp")
    dist_dir = os.path.join(package_dir, "dist")
    egg_info = os.path.join(package_dir, f"{os.path.basename(package_dir)}.egg-info")

    def _rm_if_it_exists(directory):
        if os.path.isdir(directory):
            print(crayons.green(f"Removing {directory}/*", bold=True))
            shutil.rmtree(directory)

    _rm_if_it_exists(build_dir)
    _rm_if_it_exists(temp_dir)
    _rm_if_it_exists(dist_dir)
    _rm_if_it_exists(egg_info)


def set_variable(fp, variable, value):
    fh, temp_abs_path = tempfile.mkstemp()
    with os.fdopen(fh, "w") as new_file, open(fp) as old_file:
        for line in old_file:
            if line.startswith(variable):
                if isinstance(value, bool):
                    template = "{variable} = {value}\n"
                else:
                    template = '{variable} = "{value}"\n'
                new_file.write(template.format(variable=variable, value=value))
            else:
                new_file.write(line)

    os.remove(fp)
    shutil.move(temp_abs_path, fp)

    message = f"Set {variable} in {fp} to {value}."
    print(crayons.white(message, bold=True))


def update_requirements_in_zulip_repo(zulip_repo_dir, version, hash_or_tag):
    common = os.path.join(zulip_repo_dir, "requirements", "common.in")
    prod = os.path.join(zulip_repo_dir, "requirements", "prod.txt")
    dev = os.path.join(zulip_repo_dir, "requirements", "dev.txt")

    def _edit_reqs_file(reqs, zulip_bots_line, zulip_line):
        fh, temp_abs_path = tempfile.mkstemp()
        with os.fdopen(fh, "w") as new_file, open(reqs) as old_file:
            for line in old_file:
                if "python-zulip-api" in line and "zulip==" in line:
                    new_file.write(zulip_line)
                elif "python-zulip-api" in line and "zulip_bots" in line:
                    new_file.write(zulip_bots_line)
                else:
                    new_file.write(line)

        os.remove(reqs)
        shutil.move(temp_abs_path, reqs)

    url_zulip = "git+https://github.com/zulip/python-zulip-api.git@{tag}#egg={name}=={version}_git&subdirectory={name}\n"
    url_zulip_bots = "git+https://github.com/zulip/python-zulip-api.git@{tag}#egg={name}=={version}+git&subdirectory={name}\n"
    zulip_bots_line = url_zulip_bots.format(tag=hash_or_tag, name="zulip_bots", version=version)
    zulip_line = url_zulip.format(tag=hash_or_tag, name="zulip", version=version)

    _edit_reqs_file(prod, zulip_bots_line, zulip_line)
    _edit_reqs_file(dev, zulip_bots_line, zulip_line)

    editable_zulip = f'-e "{url_zulip.rstrip()}"\n'
    editable_zulip_bots = f'-e "{url_zulip_bots.rstrip()}"\n'

    _edit_reqs_file(
        common,
        editable_zulip_bots.format(tag=hash_or_tag, name="zulip_bots", version=version),
        editable_zulip.format(tag=hash_or_tag, name="zulip", version=version),
    )

    message = "Updated zulip API package requirements in the main repo."
    print(crayons.white(message, bold=True))


def parse_args():
    usage = """
Script to automate the PyPA release of the zulip, zulip_bots and
zulip_botserver packages.

For example, to make a release for version 0.4.0, execute the
following steps in order:

1. Run ./tools/provision

2. Activate the virtualenv created by tools/provision

3. To make a release for version 0.4.0, run the
   following command:

     ./tools/release-packages --build 0.4.0 --release

4. After Step 3, commit the outstanding changes in your python-zulip-api
   repo.

5. Create a release tag "0.4.0".

6. Push the commit and the release tag.

7. To update the zulip/requirements/* in the main zulip repo, run (this
   will update the requirements to install the packages off of the release
   tag "0.4.0"):

     ./tools/release-packages update-main-repo PATH_TO_ZULIP_DIR 0.4.0

   You can also update the requirements to install the packages from a
   specific commit, like so:

     ./tools/release-packages update-main-repo PATH_TO_ZULIP_DIR 0.4.0 --hash abcedef

8. Commit and push the outstanding changes in your zulip/ repo.

And you're done! Congrats!
"""
    parser = argparse.ArgumentParser(usage=usage)

    parser.add_argument(
        "--cleanup",
        "-c",
        action="store_true",
        default=False,
        help="Remove build directories (dist/, build/, egg-info/, etc).",
    )

    parser.add_argument(
        "--build",
        "-b",
        metavar="VERSION_NUM",
        help=(
            "Build sdists and wheels for all packages with the"
            "specified version number."
            " sdists and wheels are stored in <package_name>/dist/*."
        ),
    )

    parser.add_argument(
        "--release",
        "-r",
        action="store_true",
        default=False,
        help="Upload the packages to PyPA using twine.",
    )

    subparsers = parser.add_subparsers(dest="subcommand")
    parser_main_repo = subparsers.add_parser(
        "update-main-repo", help="Update the zulip/requirements/* in the main zulip repo."
    )
    parser_main_repo.add_argument("repo", metavar="PATH_TO_ZULIP_DIR")
    parser_main_repo.add_argument("version", metavar="version number of the packages")
    parser_main_repo.add_argument("--hash", metavar="COMMIT_HASH")

    return parser.parse_args()


def main():
    options = parse_args()

    glob_pattern = os.path.join(REPO_DIR, "*", "setup.py")
    setup_py_files = glob.glob(glob_pattern)

    if options.cleanup:
        package_dirs = map(os.path.dirname, setup_py_files)
        for package_dir in package_dirs:
            cleanup(package_dir)

    if options.build:
        package_dirs = map(os.path.dirname, setup_py_files)
        for package_dir in package_dirs:
            cleanup(package_dir)

        zulip_init = os.path.join(REPO_DIR, "zulip", "zulip", "__init__.py")
        set_variable(zulip_init, "__version__", options.build)
        bots_setup = os.path.join(REPO_DIR, "zulip_bots", "setup.py")
        set_variable(bots_setup, "ZULIP_BOTS_VERSION", options.build)
        set_variable(bots_setup, "IS_PYPA_PACKAGE", True)
        botserver_setup = os.path.join(REPO_DIR, "zulip_botserver", "setup.py")
        set_variable(botserver_setup, "ZULIP_BOTSERVER_VERSION", options.build)

        for setup_file in setup_py_files:
            package_name = os.path.basename(os.path.dirname(setup_file))
            generate_bdist_wheel(setup_file, package_name)

        set_variable(bots_setup, "IS_PYPA_PACKAGE", False)

    if options.release:
        dist_dirs = glob.glob(os.path.join(REPO_DIR, "*", "dist", "*"))
        twine_upload(dist_dirs)

    if options.subcommand == "update-main-repo":
        if options.hash:
            update_requirements_in_zulip_repo(options.repo, options.version, options.hash)
        else:
            update_requirements_in_zulip_repo(options.repo, options.version, options.version)


if __name__ == "__main__":
    main()