#! /usr/bin/python3

"""
Migrate existing build results for a given project and all of its CoprDirs
from one storage (Copr backend) to another (Pulp).
"""

import os
import sys
import argparse
import logging
from copr_common.log import setup_script_logger
from copr_backend.helpers import BackendConfigReader
from copr_backend.storage import PulpStorage
from copr_backend.frontend import FrontendClient
from copr_backend.exceptions import FrontendClientException


STORAGES = ["backend", "pulp"]

log = logging.getLogger(__name__)
setup_script_logger(log, "/var/log/copr-backend/change-storage.log")


def get_arg_parser():
    """
    CLI argument parser
    """
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--src",
        required=True,
        choices=STORAGES,
        help="The source storage",
    )
    parser.add_argument(
        "--dst",
        required=True,
        choices=STORAGES,
        help="The destination storage",
    )
    parser.add_argument(
        "--project",
        required=True,
        help="Full name of the project that is to be migrated",
    )
    parser.add_argument(
        "--delete",
        action="store_true",
        default=False,
        help="After migrating the data, remove it from the old storage",
    )
    return parser


def is_valid_build_directory(name):
    """
    See the `copr-backend-resultdir-cleaner`. We may want to share the code
    between them.
    """
    if name in ["repodata", "devel"]:
        return False

    if name.startswith("repodata.old") or name.startswith(".repodata."):
        return False

    if name in ["tmp", "cache", "appdata"]:
        return False

    parts = name.split("-")
    if len(parts) <= 1:
        return False

    number = parts[0]
    if len(number) != 8 or any(not c.isdigit() for c in number):
        return False

    return True


def change_on_frontend(client, owner, project, storage):
    """
    Request copr-frontend to change storage for this project in database
    """
    data = {
        "owner": owner,
        "project": project,
        "storage": storage,
    }
    client.post("change-storage", data)


def add_redirect(fullname):
    """
    Create a HTTP redirect for this project
    See https://pagure.io/fedora-infra/ansible/blob/main/f/roles/copr/backend/templates/lighttpd/pulp-redirect.lua.j2
    """
    path = "/var/lib/copr/pulp-redirect.txt"
    with open(path, "a", encoding="utf-8") as fp:
        print(fullname, file=fp)


def main():
    """
    The main function
    """
    # pylint: disable=too-many-locals
    # pylint: disable=too-many-statements
    parser = get_arg_parser()
    args = parser.parse_args()

    if args.src == args.dst:
        log.info("The source and destination storage is the same, nothing to do.")
        return

    if args.src == "pulp":
        log.error("Migration from pulp to somewhere else is not supported")
        sys.exit(1)

    if args.delete:
        log.error("Data removal is not supported yet")
        sys.exit(1)

    config = BackendConfigReader("/etc/copr/copr-be.conf").read()
    owner, project = args.project.split("/")
    ownerdir = os.path.join(config.destdir, owner)
    ok = True

    for subproject_entry in os.scandir(ownerdir):
        subproject = subproject_entry.name
        if not (subproject == project or subproject.startswith(project + ":")):
            continue

        coprdir = os.path.join(ownerdir, subproject)
        for chroot_entry in os.scandir(coprdir):
            chroot = chroot_entry.name
            if chroot == "srpm-builds":
                continue

            if not chroot_entry.is_dir():
                continue

            chrootdir = os.path.join(coprdir, chroot)
            appstream = None
            devel = None
            storage = PulpStorage(
                owner, subproject, appstream, devel, config, log)

            for builddir_entry in os.scandir(chrootdir):
                if not builddir_entry.is_dir():
                    continue
                builddir = builddir_entry.name
                resultdir = os.path.join(chrootdir, builddir)


                if not is_valid_build_directory(builddir):
                    log.info("Skipping: %s", resultdir)
                    continue

                # TODO Fault-tolerance and data consistency
                # Errors when creating things in Pulp will likely happen
                # (networking issues, unforseen Pulp validation, etc). We
                # should figure out how to ensure that all RPMs were
                # successfully uploaded, and if not, we know about it.
                #
                # We also need to make sure that no builds, actions, or cron,
                # are currently writing into the results directory. Otherwise
                # we can end up with incosystent data in Pulp.

                result = storage.init_project(subproject, chroot)
                if not result:
                    log.error("Failed to initialize project: %s", resultdir)
                    ok = False
                    break

                # We cannot check return code here
                storage.upload_build_results(chroot, resultdir, None)

                result = storage.publish_repository(chroot)
                if not result:
                    log.error("Failed to publish a repository: %s", resultdir)
                    ok = False
                    break

                log.info("OK: %s", resultdir)

    # Not everything was migrated successfully. Play it safe and fail.
    if not ok:
        log.error(
            "Failure during '%s' migration, not switching on frontend",
            args.project,
        )
        sys.exit(1)

    # Change storage in the frontend database
    frontend_client = FrontendClient(config, try_indefinitely=False, logger=log)
    try:
        change_on_frontend(frontend_client, owner, project, args.dst)
    except FrontendClientException as ex:
        log.error("Failed to change storage on frontend for %s because: %s",
                  args.project, str(ex))
        sys.exit(1)

    # At this point all data is successfully migrated and frontend thinks the
    # project is in Pulp, so we can safely add the HTTP redirect
    try:
        add_redirect(args.project)
    except OSError as ex:
        log.error("Failed to add a redirect for %s because: %s",
                  args.project, str(ex))

    log.info("Project %s successfully migrated", args.project)


if __name__ == "__main__":
    main()
