# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import glob
import logging
import math
import os

import pmb.chroot
import pmb.config.pmaports
import pmb.config.workdir
import pmb.helpers.pmaports
import pmb.helpers.run
import pmb.parse.apkindex


def zap(args, confirm=True, dry=False, pkgs_local=False, http=False,
        pkgs_local_mismatch=False, pkgs_online_mismatch=False, distfiles=False,
        rust=False, netboot=False):
    """
    Shutdown everything inside the chroots (e.g. distccd, adb), umount
    everything and then safely remove folders from the work-directory.

    :param dry: Only show what would be deleted, do not delete for real
    :param pkgs_local: Remove *all* self-compiled packages (!)
    :param http: Clear the http cache (used e.g. for the initial apk download)
    :param pkgs_local_mismatch: Remove the packages that have
        a different version compared to what is in the aports folder.
    :param pkgs_online_mismatch: Clean out outdated binary packages
        downloaded from mirrors (e.g. from Alpine)
    :param distfiles: Clear the downloaded files cache
    :param rust: Remove rust related caches
    :param netboot: Remove images for netboot

    NOTE: This function gets called in pmb/config/init.py, with only args.work
    and args.device set!
    """
    # Get current work folder size
    if not dry:
        pmb.chroot.shutdown(args)
        logging.debug("Calculate work folder size")
        size_old = pmb.helpers.other.folder_size(args, args.work)

    # Delete packages with a different version compared to aports,
    # then re-index
    if pkgs_local_mismatch:
        zap_pkgs_local_mismatch(args, confirm, dry)

    # Delete outdated binary packages
    if pkgs_online_mismatch:
        zap_pkgs_online_mismatch(args, confirm, dry)

    pmb.chroot.shutdown(args)

    # Deletion patterns for folders inside args.work
    patterns = [
        "chroot_native",
        "chroot_buildroot_*",
        "chroot_installer_*",
        "chroot_rootfs_*",
    ]
    if pkgs_local:
        patterns += ["packages"]
    if http:
        patterns += ["cache_http"]
    if distfiles:
        patterns += ["cache_distfiles"]
    if rust:
        patterns += ["cache_rust"]
    if netboot:
        patterns += ["images_netboot"]

    # Delete everything matching the patterns
    for pattern in patterns:
        pattern = os.path.realpath(f"{args.work}/{pattern}")
        matches = glob.glob(pattern)
        for match in matches:
            if (not confirm or
                    pmb.helpers.cli.confirm(args, f"Remove {match}?")):
                logging.info(f"% rm -rf {match}")
                if not dry:
                    pmb.helpers.run.root(args, ["rm", "-rf", match])

    # Remove config init dates for deleted chroots
    pmb.config.workdir.clean(args)

    # Chroots were zapped, so no repo lists exist anymore
    pmb.helpers.other.cache["apk_repository_list_updated"].clear()

    # Print amount of cleaned up space
    if dry:
        logging.info("Dry run: nothing has been deleted")
    else:
        size_new = pmb.helpers.other.folder_size(args, args.work)
        mb = (size_old - size_new) / 1024
        logging.info(f"Cleared up ~{math.ceil(mb)} MB of space")


def zap_pkgs_local_mismatch(args, confirm=True, dry=False):
    channel = pmb.config.pmaports.read_config(args)["channel"]
    if not os.path.exists(f"{args.work}/packages/{channel}"):
        return

    question = "Remove binary packages that are newer than the corresponding" \
               f" pmaports (channel '{channel}')?"
    if confirm and not pmb.helpers.cli.confirm(args, question):
        return

    reindex = False
    pattern = f"{args.work}/packages/{channel}/*/APKINDEX.tar.gz"
    for apkindex_path in glob.glob(pattern):
        # Delete packages without same version in aports
        blocks = pmb.parse.apkindex.parse_blocks(apkindex_path)
        for block in blocks:
            pkgname = block["pkgname"]
            origin = block["origin"]
            version = block["version"]
            arch = block["arch"]

            # Apk path
            apk_path_short = f"{arch}/{pkgname}-{version}.apk"
            apk_path = f"{args.work}/packages/{channel}/{apk_path_short}"
            if not os.path.exists(apk_path):
                logging.info("WARNING: Package mentioned in index not"
                             f" found: {apk_path_short}")
                continue

            # Aport path
            aport_path = pmb.helpers.pmaports.find(args, origin, False)
            if not aport_path:
                logging.info(f"% rm {apk_path_short}"
                             f" ({origin} aport not found)")
                if not dry:
                    pmb.helpers.run.root(args, ["rm", apk_path])
                    reindex = True
                continue

            # Clear out any binary apks that do not match what is in aports
            apkbuild = pmb.parse.apkbuild(f"{aport_path}/APKBUILD")
            version_aport = f"{apkbuild['pkgver']}-r{apkbuild['pkgrel']}"
            if version != version_aport:
                logging.info(f"% rm {apk_path_short}"
                             f" ({origin} aport: {version_aport})")
                if not dry:
                    pmb.helpers.run.root(args, ["rm", apk_path])
                    reindex = True

    if reindex:
        pmb.build.other.index_repo(args)


def zap_pkgs_online_mismatch(args, confirm=True, dry=False):
    # Check whether we need to do anything
    paths = glob.glob(f"{args.work}/cache_apk_*")
    if not len(paths):
        return
    if (confirm and not pmb.helpers.cli.confirm(args,
                                                "Remove outdated"
                                                " binary packages?")):
        return

    # Iterate over existing apk caches
    for path in paths:
        arch = os.path.basename(path).split("_", 2)[2]
        suffix = f"buildroot_{arch}"
        if arch == pmb.config.arch_native:
            suffix = "native"

        # Clean the cache with apk
        logging.info(f"({suffix}) apk -v cache clean")
        if not dry:
            pmb.chroot.root(args, ["apk", "-v", "cache", "clean"], suffix)
