#!/usr/bin/python3
"""Perform a createrepo test run."""

import argparse
import pathlib
import subprocess
import sys
import tempfile

from typing import Dict, NamedTuple

import createrepo_c  # type: ignore  # pylint: disable=import-error


Config = NamedTuple(
    "Config",
    [
        ("rpmtree", pathlib.Path),
        ("repo", pathlib.Path),
        ("repomd", pathlib.Path),
        ("source", pathlib.Path),
        ("spec", pathlib.Path),
        ("tempd", pathlib.Path),
    ],
)


def parse_args(tempd: pathlib.Path) -> Config:
    """Parse the command-line arguments."""
    parser = argparse.ArgumentParser(prog="repotest")
    parser.add_argument(
        "--source",
        type=str,
        required=True,
        help="path to the source directory",
    )
    parser.add_argument(
        "--spec", type=str, required=True, help="path to the spec file"
    )

    args = parser.parse_args()

    # A sanity check
    source = pathlib.Path(args.source)
    if not source.is_dir():
        sys.exit("Not a directory: {source}".format(source=source))
    if not (source / "GNUmakefile").is_file():
        sys.exit("Not a file: {source}".format(source=source / "GNUmakefile"))

    spec = pathlib.Path(args.spec)
    if not spec.is_file():
        sys.exit("Not a file: {spec}".format(spec=spec))

    return Config(
        repo=tempd / "repo",
        repomd=tempd / "repo/repodata/repomd.xml",
        rpmtree=tempd / "rpmbuild",
        source=source,
        spec=spec,
        tempd=tempd,
    )


def build_tarball(cfg: Config) -> pathlib.Path:
    """Build the source tarball."""
    archive = cfg.tempd / (cfg.source.name + ".tar.gz")
    if archive.exists():
        sys.exit(f"Something already created {archive}")

    try:
        subprocess.check_call(
            ["tar", "-caf", archive, "--", cfg.source.name],
            shell=False,
            cwd=cfg.source.parent,
        )
    except subprocess.CalledProcessError as err:
        sys.exit(f"Could not create {archive}: {err}")
    if not archive.is_file():
        sys.exit(f"The tar tool did not generate {archive}")

    return archive


def build_rpmtree(cfg: Config, source: pathlib.Path) -> None:
    """Build the rpmbuild tree structure."""
    cfg.rpmtree.mkdir(mode=0o755)
    for subdir in ("BUILD", "BUILDROOT", "SOURCES"):
        (cfg.rpmtree / subdir).mkdir(mode=0o755)

    dest = cfg.rpmtree / "SOURCES" / source.name
    try:
        subprocess.check_call(
            ["install", "-m", "644", "--", source, dest], shell=False
        )
    except subprocess.CalledProcessError as err:
        sys.exit(f"Could not copy {source} to {dest}: {err}")


def create_empty_repo(cfg: Config) -> None:
    """Create an empty repository."""
    print(f"Creating an empty repository at {cfg.repo}")
    cfg.repo.mkdir(mode=0o755)
    subprocess.check_call(["createrepo_c", "--", cfg.repo], shell=False)

    if not cfg.repomd.is_file():
        sys.exit(f"createrepo_c did not create {cfg.repomd}")


def parse_repo(cfg: Config) -> Dict[str, pathlib.Path]:
    """Parse a repository using the Python bindings for libcreaterepo-c."""
    repomd = createrepo_c.Repomd(str(cfg.repomd))
    primary = next(rec for rec in repomd.records if rec.type == "primary")

    def add_package(
        res: Dict[str, pathlib.Path], pkg: createrepo_c.Package
    ) -> None:
        """Record information about a single package."""
        path = cfg.repo / pkg.location_href
        idx = f"{pkg.name}:{pkg.arch}"

        if idx in res:
            sys.exit(f"Duplicate package {idx}: first {res[idx]} now {path}")
        if not path.is_file():
            sys.exit(f"Not a file: {path}")

        res[idx] = path

    res: Dict[str, pathlib.Path] = {}
    createrepo_c.xml_parse_primary(
        str(cfg.repo / primary.location_href),
        pkgcb=lambda pkg: add_package(res, pkg),
    )

    return res


def copy_rpm_packages(cfg: Config) -> None:
    """Copy the packages built by rpmbuild to the repo."""
    print("Copying the source RPM package")
    srcs = list((cfg.rpmtree / "SRPMS").rglob("*"))
    if len(srcs) != 1:
        sys.exit(f"Expected a single source package, got {srcs}")
    if not srcs[0].is_file() or not srcs[0].name.endswith(".src.rpm"):
        sys.exit(f"Expected a single SRPM file, got {srcs}")

    srcdir = cfg.repo / "Sources"
    srcdir.mkdir(mode=0o755)
    print(f"- {srcs[0]} -> {srcdir}")
    subprocess.check_call(
        ["install", "-m", "644", "--", srcs[0], srcdir / srcs[0].name],
        shell=False,
    )

    print("Copying the binary RPM packages")
    rpms = list(
        path for path in (cfg.rpmtree / "RPMS").rglob("*") if path.is_file()
    )
    if len(rpms) != 2:
        sys.exit(f"Expected two RPM packages, got {rpms!r}")
    if not all(path.name.endswith(".rpm") for path in rpms):
        sys.exit(f"Expected two RPM packages, got {rpms!r}")
    architectures = set(path.suffixes[-2].split(".", 1)[1] for path in rpms)
    if len(architectures) != 2 or "noarch" not in architectures:
        sys.exit(f"Unexpected RPM architectures: {architectures!r}")

    for src in rpms:
        dstdir = cfg.repo / src.suffixes[-2].split(".", 1)[1]
        dstdir.mkdir(mode=0o755)
        print(f"- {src} -> {dstdir}")
        subprocess.check_call(
            ["install", "-m", "644", "--", src, dstdir / src.name], shell=False
        )

    print("Running createrepo_c again")
    subprocess.check_call(["createrepo_c", "--", cfg.repo], shell=False)


def main() -> None:
    """Main program: build the tarball, output the full path to it."""
    with tempfile.TemporaryDirectory(prefix="repotest.") as tempd:
        print(f"Using temporary directory {tempd}")
        cfg = parse_args(pathlib.Path(tempd).absolute())
        source = build_tarball(cfg)
        print(f"Source archive: {source}")

        build_rpmtree(cfg, source)
        print(f"RPM directory structure at {cfg.rpmtree}")
        subprocess.check_call(["find", "--", cfg.rpmtree, "-ls"], shell=False)

        try:
            subprocess.check_call(
                [
                    "rpmbuild",
                    "-ba",
                    f"-D_topdir {cfg.rpmtree}",
                    "--",
                    cfg.spec,
                ],
                shell=False,
            )
        except subprocess.CalledProcessError as err:
            sys.exit(f"rpmbuild failed: {err}")

        subprocess.check_call(["find", "--", cfg.rpmtree, "-ls"], shell=False)
        subprocess.check_call(
            ["tar", "-caf", "/tmp/rpmbuild.tar.gz", "--", cfg.rpmtree.name],
            shell=False,
            cwd=cfg.rpmtree.parent,
        )

        create_empty_repo(cfg)
        subprocess.check_call(["find", "--", cfg.repo, "-ls"], shell=False)

        files = parse_repo(cfg)
        if files:
            sys.exit(f"Files found in an empty repo: {files!r}")

        copy_rpm_packages(cfg)
        subprocess.check_call(["find", "--", cfg.repo, "-ls"], shell=False)

        files = parse_repo(cfg)
        print(repr(files))

        known = {"foo:src", "foo-common:noarch"}
        keys = set(files.keys())
        missing = known - keys
        if missing:
            sys.exit(f"Expected at least {known!r}, got {keys!r}")
        remaining = keys - known
        if len(remaining) != 1:
            sys.exit(f"Expected a single different key, got {remaining!r}")
        if list(remaining)[0].split(":", 1)[0] != "foo":
            sys.exit(f"Expected a arch:foo key, got {remaining!r}")

        print("Seems fine!")


if __name__ == "__main__":
    main()
