#!/usr/bin/env python3
"""Clone and prepare py_heptapod for CI build of the Rails applications.

Expected to be run from a clone of the Rails application, and assumes
that `config/gitlab.yml` has already been initialized.

In the prebuild phase, the relevant that it does is to prepare the
clones. The installation in the interpreter will be lost and redone in
the actual testing phase. (TODO add a flag to skip it, although it's
probably negligible, given than most deps are in the base image)
"""
import argparse
import os
import re
import shutil
import subprocess
import sys

parser = argparse.ArgumentParser()
parser.add_argument('--prebuild', action='store_true',
                    help="Run for the heptapod-prebuild job, hence not "
                    "pip-installing (cannot be cached nor sent as artifact) "
                    )
parser.add_argument('--hg-bin', default='/usr/local/bin/hg',
                    help="Path to the Mercurial executable used for "
                    "cloning and to derive the Python interpreter. "
                    "default value is suitable for RSpec runs. ")

cl_args = parser.parse_args()


HG_BIN = cl_args.hg_bin
INTERPRETER, HG_PKG = subprocess.check_output(
    (HG_BIN, 'debuginstall', '-T', '{pythonexe} {hgmodules}')
).strip().decode('ascii').split()
SITE_PACKAGES = os.path.realpath(os.path.dirname(HG_PKG))

RELEASE_BRANCH_RX = re.compile(r'heptapod-\d+-\d+')

CWD = os.getcwd()
BASE_DIR = os.path.join(CWD, 'tmp', 'tests', 'hgitaly')
# lazy, but I'm tired of reimplenting `mkdir -p` over and over
subprocess.check_call(['mkdir', '-p', BASE_DIR])

COMPONENT_DIR_FMT = '{slug}-branch-{branch}'
COMPONENT_TARBALL_URL_FMT = (
    "https://foss.heptapod.net/heptapod/{slug}/"
    "-/archive/branch/{branch}/{component_dir}.tar.bz2"
)
RHGITALY_DOWNLOAD_BASE_URL = 'https://upload.heptapod.net/rhgitaly'
RHGITALY_TEST_BIN = os.path.join(CWD, 'tmp', 'tests', 'rhgitaly')


def write_config(req_hgrc_path):
    local_hgrc_path = os.path.join(CWD, 'config', 'tests.hgrc')
    with open(local_hgrc_path, 'w') as local_hgrcf:
        local_hgrcf.write("\n".join((
            "[heptapod]",
            "# we won't really send the pre- and post-receive hooks, ",
            "# but `heptapod.hooks.Hook` is currently still instantiated and ",
            "# that requires the configuration.",
            "gitlab-internal-api-secret-file = " + os.path.join(
                CWD, '.gitlab_shell_secret'),
            "repositories-root = " + os.path.join(CWD, 'tmp', 'tests', 'repositories'),
            "")))

    yml_format = 'hgrc:\n      - %s\n      - %s\n    bin_path: %s'

    with open('config/gitlab.yml', 'r+') as conf:
        out_lines = [line.replace('# hgrc:',
                                  yml_format % (local_hgrc_path,
                                                req_hgrc_path,
                                                HG_BIN))
                     for line in conf.readlines()]
        conf.seek(0)
        conf.writelines(out_lines)


def lookup_required_hgrc():
    """This can succeed only after all Python installations have been made.

    Currently the `required.hgrc` is inside the heptapod package, which
    gets installed by the HGitaly Python project.
    """
    hepta_mod = os.fsdecode(
        subprocess.check_output(
            (INTERPRETER, '-c', 'import heptapod; print(heptapod.__file__)'),
            cwd='/')
    )
    hgrc = os.path.join(os.path.dirname(hepta_mod), 'required.hgrc')
    if not os.path.exists(hgrc):
        raise LookupError("Expected HGRC file at %r not found" % hgrc)
    return hgrc


def install_rhgitaly(src_path):
    rust_path = os.path.join(src_path, 'rust')
    manifest_path = os.path.join(rust_path, 'Cargo.toml')
    print("Running cargo build")
    subprocess.check_call(('cargo', 'build',
                           '--locked',
                           '--features', 'rust-embed/debug-embed',
                           '--manifest-path', manifest_path))
    install_path = os.path.join(CWD, 'tmp', 'tests', 'rhgitaly')
    print("Installing rhgitaly at %r" % install_path)
    os.rename(os.path.join(rust_path, 'target', 'debug', 'rhgitaly'),
              install_path)
    # no need to pollute caches and artifacts with sources and intermediate
    # objects
    shutil.rmtree(rust_path)


def download_fixed_rhgitaly_sources():
    res = subprocess.run(('scripts/rhgitaly-download', '--unpack'),
                         check=True, capture_output=True)
    return res.stdout.strip().decode()


def download_fixed_rhgitaly_bin():
    with open(os.path.join(CWD, 'RHGITALY_SERVER_VERSION')) as vf:
        version = vf.read().strip()
    print(f"Downloading precompiled RHGitaly {version} from upload site")
    subprocess.run(('curl', '--no-progress-meter',
                    '-o', RHGITALY_TEST_BIN,
                    os.path.join(RHGITALY_DOWNLOAD_BASE_URL,
                                 f'rhgitaly-{version}_linux_amd64'),
                    ), check=True)
    os.chmod(RHGITALY_TEST_BIN, 0o755)


def ensure_rhgitaly_bin():
    if not os.path.exists(RHGITALY_TEST_BIN):
        download_fixed_rhgitaly_bin()
    print("Running 'rhgitaly --version'")
    subprocess.run((RHGITALY_TEST_BIN, '--version'), check=True)


CI_TAG = os.environ.get('CI_COMMIT_TAG')
# probably not set for tag pipelines:
CI_BRANCH = os.environ.get('CI_COMMIT_HG_BRANCH', '')
if CI_TAG or RELEASE_BRANCH_RX.match(CI_BRANCH):
    print("*** ATTENTION Pipeline running for a tag or a release branch ***")
    rhgitaly_src_path = download_fixed_rhgitaly_sources()
    if cl_args.prebuild:
        print("*** ==> installing the fixed RHGitaly "
              "version                ***")
        install_rhgitaly(rhgitaly_src_path)
        sys.exit(0)

    print("*** ==> installing the fixed requirements "
          "bundled with RHGitaly  ***")
    subprocess.check_call(
        (INTERPRETER, '-m', 'pip', 'install', '--upgrade',
         '-r', os.path.join(rhgitaly_src_path, 'python.req'))
    )

    write_config(lookup_required_hgrc())
    ensure_rhgitaly_bin()
    sys.exit(0)
elif CI_BRANCH == 'heptapod-stable':
    PYDEPS_BRANCH = 'stable'
elif CI_BRANCH == 'heptapod-oldstable':
    PYDEPS_BRANCH = 'oldstable'
elif CI_BRANCH == 'heptapod-future':
    PYDEPS_BRANCH = 'future'
else:
    PYDEPS_BRANCH = 'default'

HG_CMD = (HG_BIN,
          '--config', 'extensions.evolve=',
          '--config', 'extensions.topic=',
          )

# py-heptapod will now be installed from PyPI, as a dependency
# of HGitaly
PYTHON_COMPONENTS = ['hgitaly']
RS_ENRY_MIRROR_URL = 'https://foss.heptapod.net/heptapod/mirrors/rs-enry'
GO_ENRY_MIRROR_URL = 'https://foss.heptapod.net/heptapod/mirrors/go-enry'

source_paths = {}

for slug in PYTHON_COMPONENTS:
    component_dir = COMPONENT_DIR_FMT.format(slug=slug, branch=PYDEPS_BRANCH)
    url = COMPONENT_TARBALL_URL_FMT.format(slug=slug, branch=PYDEPS_BRANCH,
                                           component_dir=component_dir)
    print(f"Downloading {slug} tarball from {url}")
    subprocess.run(('curl', '-OL', '--no-progress-meter', url),
                   cwd=BASE_DIR,
                   check=True)
    archive = component_dir + '.tar.bz2'
    # We need to extract the archive at least for the case when RHGitaly
    # has to be compiled from branch head, so let's do it all the time instead
    # on making a special case for when all we need is pip to do it for us
    print(f"Extracting archive {archive}")
    subprocess.run(('tar', 'xjf', archive), cwd=BASE_DIR, check=True)
    # avoid keeping it uselessly in CI cache:
    os.unlink(os.path.join(BASE_DIR, archive))

    source_paths[slug] = os.path.join(BASE_DIR, component_dir)

HGITALY_CLONE_PATH = source_paths['hgitaly']

pip_cmd = [INTERPRETER, '-m', 'pip', 'install', '--upgrade']
for target in source_paths.values():
    pip_cmd.extend(('-e', target))

if cl_args.prebuild:
    subprocess.check_call(('make',
                           '-C', os.path.join(HGITALY_CLONE_PATH, 'rust'),
                           'dev-dependencies',
                           'RS_ENRY_SOURCE_REMOTE=' + RS_ENRY_MIRROR_URL,
                           'GO_ENRY_SOURCE_REMOTE=' + GO_ENRY_MIRROR_URL,
                           ))
    install_rhgitaly(HGITALY_CLONE_PATH)
else:
    print("Installing Python Heptapod components: %r" % pip_cmd)
    subprocess.check_call(pip_cmd, stderr=sys.stdout)

    write_config(lookup_required_hgrc())
    ensure_rhgitaly_bin()
