#!/bin/bash

# SPDX-License-Identifier: GPL-2.0

set -eou pipefail

PROGNAME="${BASH_SOURCE[0]##*/}"

usage() {
    cat <<- _EOF_
		Usage: ${PROGNAME} [OPTIONS] URL
		
		Rebuilds packages from a todo list. If there is a failing build it will
		drop into a subshell that allows fixing the PKGBUILD.
		
		OPTIONS
		    -m, --message   Sets the commitpkg message. Default is todo list title
		    -i, --ignore    Give one or more pkgbases to ignore
		    -f, --filter    Filter for one or more maintainers (orphan for orphan packages)
		    -e, --edit      Edit PKGBUILD before building. Default when todo type is "Task"
		    -o, --offload   Use offloaded builds
		    -h, --help      Show this help text
		    -d, --dry-run   Show the offload-build and commitpkg being ran
		    --import-keys   Import PGP keys for packages source verification into the user's keyring
		    --no-build      Don't build PKGBUILD
		    --no-publish    Don't run commitpkg after building
		    --testing       Publish towards testing
		    --staging       Publish towards staging
		    --skip-broken   Skip broken packages

		Repository Filters (default: $filter)
		    --core         Rebuild [core] packages
		    --extra        Rebuild [extra] packages
		    --multilib     Rebuild [multilib] packages

		Examples:
		    Rebuilds all packages in [extra] towards [extra] ignoring "archlinux-contrib"
		    $  ${PROGNAME} -i "archlinux-contrib" "https://archlinux.org/todo/some-todo-list/"

		    Rebuilds all packages in [extra] towards [extra] using offloaded builds
		    $  ${PROGNAME} -o "https://archlinux.org/todo/some-todo-list/"

		    Rebuilds all packages from [extra] towards [extra-testing]
		    $  ${PROGNAME} --extra --testing "https://archlinux.org/todo/some-todo-list/"

		    Rebuilds all packages from [core] towards [core-staging]
		    $  ${PROGNAME} --core --staging -m "binutils rebuild" "https://archlinux.org/todo/some-todo-list/"

		    Rebuilds all packages from stdin (from [core]) towards [staging]
		    $ arch-rebuild-order glibc --repos core | ${PROGNAME} --staging -m "glibc rebuild" -
_EOF_
}

DRY=0
STDIN=0
NO_BUMP=0
NO_BUILD=0
PACKAGES=0
IMPORT_KEYS=0
NO_PUBLISH=0
EDIT_PKGBUILD=0
CONTINUE=0
SKIP_BROKEN=0
URL=""
OFFLOAD=""
REPO=""
message=""
filter=("extra")
maintainers=()
packages=()
ignore=()
skipped_packages=()
rebuilt_packages=()
released_packages=()

if ! ((${#})); then
    usage
    exit 0
fi

while ((${#})); do
    key="${1}"
    case ${key} in
        -h|--help)
            usage
            exit 0
        ;;
        -m|--message)
            shift
            message="${1}"
        ;;
        -i|--ignore)
            shift
            ignore+=("${1}")
        ;;
        -f|--filter)
            shift
            maintainers+=("${1}")
        ;;
        -e|--edit)
            EDIT_PKGBUILD=1
        ;;
        -o|--offload)
            OFFLOAD="$key"
        ;;
        -d|--dry-run)
            DRY=1
        ;;
        --import-keys)
            IMPORT_KEYS=1
        ;;
        --testing|--staging)
            REPO="$key"
        ;;
        --no-build)
            NO_BUILD=1
        ;;
        --no-publish)
            NO_PUBLISH=1
        ;;
        --skip-broken)
            SKIP_BROKEN=1
        ;;
        --core)
            PACKAGES=1
            filter=("extra")
        ;;
        --extra)
            PACKAGES=1
            filter=("extra")
        ;;
        --multilib)
            PACKAGES=0
            filter=("multilib")
        ;;
        *)
            if [[ "${key}" == "-" ]]; then
                STDIN=1
                break
            fi
            if [[ ! "${key}" == https* ]]; then
                echo "Missing url!"
                exit 1
            fi
            URL="${key}"
            if [[ ! "$URL" == *json ]]; then
                URL+="json"
            fi
        ;;
        esac
        shift
done

TMPDIR=$(mktemp -d /var/tmp/contrib-rebuild.XXXX) || exit 1
trap "rm -rf ${TMPDIR}" EXIT

remove_from_rebuilt_packages_list() {
    local element=$1
    for i in "${!rebuilt_packages[@]}"; do
        if [[ "${rebuilt_packages[i]}" == "$element" ]]; then
            unset 'rebuilt_packages[i]'
        fi
    done
}

if [[ "$URL" != "" ]]; then
    echo -e "Parsing packages list...\n"
    while read -r json; do
        readarray -t packages < <(jq --slurpfile repo <(printf '"%s" ' "${filter[@]}") \
                                     --slurpfile maint <(printf '"%s" ' "${maintainers[@]}") \
                                     -r '.created as $created 
                                         | .packages[] 
                                         | select(.status_str == "Incomplete" ) 
                                         | select([.repo] | inside($repo)) 
                                         | select(($maint[0] == "") or (($maint[0] == "orphan") and .maintainers == []) or (select(.maintainers | any([.] | inside($maint)))))
                                         | "\(.pkgbase)"' \
                                     - <<< "$json" | sort -u)

        # This removes any elements we have ignored.... it's not pretty
        readarray -t packages < <(comm -1 -3 <(printf "%s\n" "${ignore[@]}" | sort) <(printf "%s\n" "${packages[@]}"| sort))

        # Default to include the list name in the commit message
        if [[ "$message" == "" ]]; then
            message="$(jq -r '.name' - <<< "$json")"
        fi

        # If we are doing a Task we probably want to edit the PKGBUILD
        if [[ "$(jq -r '.kind' - <<< "$json")" == "Task" ]]; then
            EDIT_PKGBUILD=1
        fi
    done <<< "$(curl -s "$URL")"
fi

if ((STDIN)); then
    readarray -t packages /dev/stdin
fi


if ((DRY)); then
    echo "Would rebuild the following packages:"
    printf '    %s\n' "${packages[@]}"
    echo "With:"
    echo "    pkgctl build --rebuild $REPO $OFFLOAD"
    echo "    pkgctl release --db-update $REPO -m \"$message\""
    exit 0
fi

if ! ((${#packages[@]})); then
    echo "No packages to rebuild!"
    exit 1
fi
echo "Rebuilding packages:"
printf '    %s\n' "${packages[@]}"
printf "Press enter to confirm "
read <&1

pkgctl repo clone "${packages[@]}"

if ((IMPORT_KEYS)); then
	echo "Importing PGP keys..."
	# Only add paths that actually have key(s) to import and ignore paths that don't, don't exit on error
	mapfile -d '' key_paths < <(find "${packages[@]/%//keys/pgp}" -type f -print0 2>/dev/null) || true

	if ((${#key_paths[@]})); then
		cat "${key_paths[@]}" | if [[ -z "$OFFLOAD" ]]; then
			gpg --import
		else
			ssh build.archlinux.org gpg --import
		fi
	else
		echo "No PGP key to import"
	fi
fi

for pkg in "${packages[@]}"; do
    pushd "$pkg" &>/dev/null

    # This should help us figure out if the package is already built
    readarray -t pkgs < <(makepkg --packagelist)
    if [[ -f ${pkgs[0]} ]]; then
        echo "${pkg[0]} has already been rebuilt!"
    else
        if ((EDIT_PKGBUILD)); then
            "$EDITOR" PKGBUILD
        fi
        if ! ((NO_BUILD)); then
            SKIP_BUILD=0
            while true; do
                if pkgctl build --rebuild $REPO $OFFLOAD; then
                    rebuilt_packages+=("$pkg")
                    break
                else
                    skipped_packages+=("$pkg")
                fi
                if ((SKIP_BROKEN)); then
                    SKIP_BUILD=1
                    break
                fi
                echo "We failed to build! You are in a subshell to fix the build. Exit the shell to build again."
                $SHELL || true
                read -p "Skip build? [N/y] " -n 1 -r
                if [[ $REPLY =~ ^[Yy]$ ]]; then
                    SKIP_BUILD=1
                    break
                fi
            done
            if ((SKIP_BUILD)); then
                popd &>/dev/null
                continue
            fi
        fi
        if ! ((NO_PUBLISH)); then
            if pkgctl release --db-update $REPO -m "$message"; then
                remove_from_rebuilt_packages_list "$pkg"
                released_packages+=("$pkg")
            fi
        fi
    fi
    popd &>/dev/null
done

if ((${#skipped_packages[@]})); then
    echo -e "\nSkipped packages (failed to build):"
    printf '    %s\n' "${skipped_packages[@]}"
fi

if ((${#rebuilt_packages[@]})); then
    echo -e "\nRebuilt packages (but not released):"
    printf '    %s\n' "${rebuilt_packages[@]}"
fi

if ((${#released_packages[@]})); then
    echo -e "\nReleased packages:"
    printf '    %s\n' "${released_packages[@]}"
fi
