#! /usr/bin/python3

"""
Take the JSON provided by Mock, download corresponding RPMs, and put them into
an RPM repository.
"""

# pylint: disable=invalid-name

import argparse
import concurrent.futures
import json
import logging
import os
import shutil
import subprocess
import sys

from urllib.parse import quote
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

import requests

logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger(__name__)


def request_with_retry(retries=5, backoff_factor=0.3,
                       status_forcelist=(500, 502, 504, 408, 429), session=None):
    """
    Wrapper for requests Session with default retries

    Converted from koji:
    https://pagure.io/koji/blob/fccf4fa3f990ca454a411c8a699e693f4e5b0ac8/f/koji/__init__.py#_2004
    originally stolen from
    https://www.peterbe.com/plog/best-practice-with-retries-with-requests
    """
    session = session or requests.Session()
    retry = Retry(total=retries, read=retries, connect=retries,
                  backoff_factor=backoff_factor,
                  status_forcelist=status_forcelist)
    adapter = HTTPAdapter(max_retries=retry)
    session.mount('http://', adapter)
    session.mount('https://', adapter)
    return session


def download_file(url, outputdir):
    """
    Download a single file (pool worker)
    """
    basename = os.path.basename(url)
    file_name = os.path.join(outputdir, basename)
    dirname = os.path.dirname(url)

    # basename often contains '+' character, e.g., g++ package, url-encode it
    url = dirname + "/" + quote(basename)
    log.info("Downloading %s", url)

    try:
        with request_with_retry().get(url, stream=True, timeout=60) as response:
            if response.status_code != 200:
                return False
            with open(file_name, "wb") as fd:
                shutil.copyfileobj(response.raw, fd)
            return True
    except:  # noqa: E722
        log.exception("Exception raised for %s", url)
        raise


def _argparser():
    parser = argparse.ArgumentParser(
        prog='mock-hermetic-repo',
        description=(
            "Prepare a repository for a `mock --hermetic-build` build. "
            "Given a Mock buildroot \"lockfile\"\n\n"
            "  a) create an output repo directory,\n"
            "  b) download and place all the necessary RPM files there,\n"
            "  c) create a local RPM repository there (run createrepo), and\n"
            "  d) dump there also the previously used bootstrap image as "
            "a tarball.\n\n"
            "Lockfile is a buildroot_lock.json file from Mock's "
            "result directory; it is a JSON file generated by the "
            "--calculate-build-dependencies option/buildroot_lock "
            "plugin."),
        formatter_class=argparse.RawTextHelpFormatter,
    )
    parser.add_argument("--lockfile", required=True,
                        help=(
                            "Select buildroot_lock.json filename on your system, "
                            "typically located in the Mock's result directory "
                            "upon the --calculate-build-dependencies mode "
                            "execution."))
    parser.add_argument("--output-repo", required=True,
                        help=(
                            "Download RPMs into this directory, and then run "
                            "/bin/createrepo_c utility there to populate the "
                            "RPM repo metadata."))
    return parser


def prepare_image(image_specification, outputdir):
    """
    Store the tarball into the same directory where the RPMs are
    """
    subprocess.check_output(["podman", "pull", image_specification])
    subprocess.check_output(["podman", "save", "--format=oci-archive", "--quiet",
                             "-o", os.path.join(outputdir, "bootstrap.tar"),
                             image_specification])


def _main():
    options = _argparser().parse_args()

    with open(options.lockfile, "r", encoding="utf-8") as fd:
        data = json.load(fd)

    try:
        os.makedirs(options.output_repo)
    except FileExistsError:
        pass

    failed = False
    urls = [i["url"] for i in data["buildroot"]["rpms"]]
    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
        for i, out in zip(urls, executor.map(download_file, urls,
                                             [options.output_repo for _ in urls])):
            if out is False:
                log.error("Download failed: %s", i)
                failed = True
    if failed:
        log.error("RPM deps downloading failed")
        sys.exit(1)

    subprocess.check_call(["createrepo_c", options.output_repo])

    prepare_image(data["config"]["bootstrap_image"], options.output_repo)


if __name__ == "__main__":
    _main()
