#!/bin/bash

if [ -z "${FD_APTLY_URI}" ];
then
    echo 'Missing variable FD_APTLY_URI' >&2
    exit 1
fi

FD_PROJECT_NAME="${FD_PROJECT_NAME:-"${CI_PROJECT_NAME}"}"

if [ -z "${FD_PROJECT_NAME}" ];
then
    echo 'Missing variable FD_PROJECT_NAME' >&2
    exit 1
fi

if [ -z "${FD_DISTRIB_CODENAME}" ];
then
    echo 'Missing variable FD_DISTRIB_CODENAME' >&2
    exit 1
fi

FD_APTLY_REPO_NAME="${FD_APTLY_REPO_NAME:-"${FD_DISTRIB_CODENAME}-fd"}"
FD_APTLY_PUBLISH_REPO_NAME="${FD_APTLY_PUBLISH_REPO_NAME:-"${FD_APTLY_REPO_NAME}"}"
FD_APTLY_FILES_PATH="${FD_APTLY_FILES_PATH:-"files/${FD_PROJECT_NAME}_${FD_APTLY_REPO_NAME}"}"
FD_APTLY_FILE_PATH="${FD_APTLY_FILE_PATH:-"file/${FD_PROJECT_NAME}_${FD_APTLY_REPO_NAME}"}"
FD_APTLY_PARALLEL_UPLOAD="${FD_APTLY_PARALLEL_UPLOAD:-2}"
FD_APTLY_UPLOAD_PIDS=()
FD_APTLY_REPO_PATH="${FD_APTLY_REPO_PATH:-"repos/${FD_APTLY_REPO_NAME}"}"
FD_APTLY_IMPORT_SOURCE="${FD_APTLY_IMPORT_SOURCE:-"false"}"
FD_BUILD_SRC_DIR="${FD_BUILD_SRC_DIR:-"../"}"
FD_BUILD_DEST_DIR="${FD_BUILD_DEST_DIR:-"build/${FD_DISTRIB_CODENAME}"}"
FD_APTLY_VERIFY_URI="${FD_APTLY_VERIFY_URI:-""}"
FD_APTLY_VERIFY_COMPONENT="${FD_APTLY_VERIFY_COMPONENT:-"main"}"
FD_APTLY_VERIFY_ARCH="${FD_APTLY_VERIFY_ARCH:-"amd64"}"
FD_APTLY_VERIFY_RETRIES="${FD_APTLY_VERIFY_RETRIES:-40}"
FD_APTLY_VERIFY_SLEEP="${FD_APTLY_VERIFY_SLEEP:-15}"
FD_APTLY_VERIFY_SKIP="${FD_APTLY_VERIFY_SKIP:-"false"}"

function fd_aptly_build_deb()
{
    echo '[fd-aptly] building debian package'

    apt-get update || true
    mk-build-deps -i -r --tool "apt-get -y --force-yes -o Debug::pkgProblemResolver=yes --no-install-recommends"
    local rc=${?}
    [ ${rc} -ne 0 ] && return ${rc}

    if [ -z "${FD_RELEASE_VERSION}" ];
    then
        dch --force-distribution \
            -D "${FD_DISTRIB_CODENAME}" \
            -l "+${FD_DISTRIB_CODENAME}" "[RELEASE] ${FD_DISTRIB_CODENAME}"
    else
        dch --force-distribution \
            -D "${FD_DISTRIB_CODENAME}" \
            -v "${FD_RELEASE_VERSION}+${FD_DISTRIB_CODENAME}" "[RELEASE] ${FD_DISTRIB_CODENAME}"
    fi

    local rc=${?}
    [ ${rc} -ne 0 ] && return ${rc}

    if [ ! -f 'debian/source/format' ];
    then
        mkdir -p debian/source
        echo '3.0 (native)' > debian/source/format
    fi

    if [ ! -f 'debian/source/options' ];
    then
        mkdir -p debian/source
        echo 'compression = "gzip"' > debian/source/options
    fi

    dpkg-buildpackage
    local rc=${?}
    [ ${rc} -ne 0 ] && return ${rc}

    if [ ! -d "${FD_BUILD_DEST_DIR}" ];
    then
        mkdir -p "${FD_BUILD_DEST_DIR}"
    fi

    find "${FD_BUILD_SRC_DIR}" -name "*.deb" -mindepth 1 -maxdepth 1 -type f -exec mv '{}' "${FD_BUILD_DEST_DIR}" \;
    find "${FD_BUILD_SRC_DIR}" -name "*.udeb" -mindepth 1 -maxdepth 1 -type f -exec mv '{}' "${FD_BUILD_DEST_DIR}" \;

    if [ "${FD_APTLY_IMPORT_SOURCE}" = "true" ];
    then
        find "${FD_BUILD_SRC_DIR}" -name "*.changes" -mindepth 1 -maxdepth 1 -type f -exec mv '{}' "${FD_BUILD_DEST_DIR}" \;
        find "${FD_BUILD_SRC_DIR}" -name "*.dsc" -mindepth 1 -maxdepth 1 -type f -exec mv '{}' "${FD_BUILD_DEST_DIR}" \;
        find "${FD_BUILD_SRC_DIR}" -name "*.tar.*" -mindepth 1 -maxdepth 1 -type f -exec mv '{}' "${FD_BUILD_DEST_DIR}" \;
    fi

    return 0
}

function fd_aptly_wait_pids() {
    local r=0
    local rc=0

    for pid in ${1};
    do
        wait ${pid}
        local r=${?}
        [ ${r} -ne 0 ] && local rc=${r};
    done

    return ${rc};
}

function fd_aptly_upload_files()
{
    local curl_user_opt=""
    local rc=0

    echo '[fd-aptly] upload files'

    if [ ! -z "${FD_APTLY_AUTH_USER}" ];
    then
        local curl_user_opt="--user ${FD_APTLY_AUTH_USER}:${FD_APTLY_AUTH_PASSWD}"
    fi

    local find_args=""

    if [ "${FD_APTLY_IMPORT_SOURCE}" != "true" ];
    then
        local find_args='-regex .*.[u]?deb'
    fi

    while read file;
    do
        (curl -f -s ${curl_user_opt} \
                 -X POST -F file="@${file}" "${FD_APTLY_URI}/api/${FD_APTLY_FILES_PATH}" \
                 -H "X-Fd-Aptly-Repo-Name: ${FD_APTLY_REPO_NAME}" \
         && echo ' uploaded') &
        FD_APTLY_UPLOAD_PIDS[${#FD_APTLY_UPLOAD_PIDS[@]}]=$!

        if [ ${#FD_APTLY_UPLOAD_PIDS[@]} -ge ${FD_APTLY_PARALLEL_UPLOAD} ];
        then
            fd_aptly_wait_pids "${FD_APTLY_UPLOAD_PIDS[*]}"
            local rc=${?}
            FD_APTLY_UPLOAD_PIDS=()
            [ ${rc} -ne 0 ] && return ${rc}
        fi
    done < <(find "${FD_BUILD_DEST_DIR}" -type f -print ${find_args});

    fd_aptly_wait_pids "${FD_APTLY_UPLOAD_PIDS[*]}"

    return ${?}
}

function fd_aptly_delete_files()
{
    local curl_user_opt=""

    echo '[fd-aptly] delete files'

    if [ ! -z "${FD_APTLY_AUTH_USER}" ];
    then
        local curl_user_opt="--user ${FD_APTLY_AUTH_USER}:${FD_APTLY_AUTH_PASSWD}"
    fi

    curl -s ${curl_user_opt} \
            -X DELETE "${FD_APTLY_URI}/api/${FD_APTLY_FILES_PATH}" \
            -H "X-Fd-Aptly-Repo-Name: ${FD_APTLY_REPO_NAME}" \
    && echo ''

    return ${?}
}

function fd_aptly_repo_add_package()
{
    local curl_user_opt=""

    echo '[fd-aptly] adding package to repository'

    if [ ! -z "${FD_APTLY_AUTH_USER}" ];
    then
        curl_user_opt="--user ${FD_APTLY_AUTH_USER}:${FD_APTLY_AUTH_PASSWD}"
    fi

    local res="$(curl -f -s ${curl_user_opt} \
                         -X POST "${FD_APTLY_URI}/api/${FD_APTLY_REPO_PATH}/${FD_APTLY_FILE_PATH}?forceReplace=1" \
                         -H "X-Fd-Aptly-Repo-Name: ${FD_APTLY_REPO_NAME}")"
    local rc=${?}

    [ ${rc} -ne 0 ] && return ${rc}

    local failed_files="$(echo -n "${res}"|jq '.FailedFiles[]')"

    if [ ! -z "${failed_files}" ];
    then
        echo "${failed_files}" >&2
        return 2
    fi

    local added_files="$(echo -n "${res}"|jq '.Report.Added[]')"

    [ -z "${added_files}" ] && return 1

    return 0
}

function fd_aptly_publish_repo()
{
    local curl_user_opt=""

    echo '[fd-aptly] publishing repository'

    if [ -z "${FD_APTLY_GPGKEY}" ];
    then
        echo 'Missing variable FD_APTLY_GPGKEY' >&2
        return 255
    fi

    if [ ! -z "${FD_APTLY_AUTH_USER}" ];
    then
        local curl_user_opt="--user ${FD_APTLY_AUTH_USER}:${FD_APTLY_AUTH_PASSWD}"
    fi

    curl -f -s ${curl_user_opt} \
               -X PUT "${FD_APTLY_URI}/api/publish/:./${FD_APTLY_PUBLISH_REPO_NAME}" \
               -H 'Content-Type: application/json' \
               -H "X-Fd-Aptly-Repo-Name: ${FD_APTLY_REPO_NAME}" \
               --data '{"ForceOverwrite": true, "SkipContents": true, "Signing": {"GpgKey": "'"${FD_APTLY_GPGKEY}"'"}}' \
    && echo ''

    return ${?}
}

function fd_aptly_get_verify_uri()
{
    local verify_uri="${FD_APTLY_VERIFY_URI}"

    if [ -z "${verify_uri}" ];
    then
        local uri="${FD_APTLY_URI%/}"
        local scheme="${uri%%://*}"
        local rest="${uri#*://}"
        local host_port="${rest%%/*}"
        local host="${host_port%%:*}"
        local path=""

        if [ "${rest}" != "${host_port}" ];
        then
            path="/${rest#*/}"
        fi

        verify_uri="${scheme}://${host}${path}"
    fi

    echo "${verify_uri%/}"
}

function fd_aptly_find_package_filename()
{
    local packages_file="${1}"
    local package="${2}"
    local version="${3}"
    local deb_filename="${4}"

    awk -v package="${package}" \
        -v version="${version}" \
        -v deb_filename="${deb_filename}" '
        BEGIN {
            RS = "";
            FS = "\n";
            status = 1;
        }
        {
            pkg = "";
            ver = "";
            filename = "";

            for (i = 1; i <= NF; i++) {
                if ($i ~ /^Package: /) {
                    pkg = substr($i, 10);
                } else if ($i ~ /^Version: /) {
                    ver = substr($i, 10);
                } else if ($i ~ /^Filename: /) {
                    filename = substr($i, 11);
                }
            }

            if (pkg == package && ver == version) {
                if (filename == "") {
                    status = 2;
                    exit;
                }

                basename = filename;
                sub(/^.*\//, "", basename);

                if (basename == deb_filename) {
                    print filename;
                    status = 0;
                } else {
                    status = 3;
                }

                exit;
            }
        }
        END {
            exit status;
        }
    ' "${packages_file}"
}

function fd_aptly_verify_deb_package()
{
    local verify_uri="${1}"
    local file="${2}"
    local package="$(dpkg-deb -f "${file}" Package)"
    local version="$(dpkg-deb -f "${file}" Version)"
    local arch="$(dpkg-deb -f "${file}" Architecture)"
    local deb_filename="$(basename "${file}")"
    local verify_arch="${arch}"

    if [ -z "${package}" ] || [ -z "${version}" ] || [ -z "${arch}" ];
    then
        echo "[fd-aptly] unable to read debian metadata from ${file}" >&2
        return 1
    fi

    if [ "${arch}" = "all" ];
    then
        verify_arch="${FD_APTLY_VERIFY_ARCH}"
    fi

    local packages_url="${verify_uri}/dists/${FD_APTLY_PUBLISH_REPO_NAME}/${FD_APTLY_VERIFY_COMPONENT}/binary-${verify_arch}/Packages.gz"
    local packages_gz="$(mktemp)"
    local packages_file="$(mktemp)"
    local filename=""
    local rc=0

    echo "[fd-aptly] verifying package ${package} ${version} (${deb_filename})"
    echo "[fd-aptly] using package index ${packages_url}"

    if ! curl -f -s -S "${packages_url}" -o "${packages_gz}";
    then
        rm -f "${packages_gz}" "${packages_file}"
        return 1
    fi

    if ! gzip -dc "${packages_gz}" > "${packages_file}";
    then
        rm -f "${packages_gz}" "${packages_file}"
        echo "[fd-aptly] unable to decompress ${packages_url}" >&2
        return 1
    fi

    filename="$(fd_aptly_find_package_filename "${packages_file}" "${package}" "${version}" "${deb_filename}")"
    rc=${?}

    rm -f "${packages_gz}" "${packages_file}"

    if [ ${rc} -ne 0 ];
    then
        case ${rc} in
            2)
                echo "[fd-aptly] package ${package} ${version} is present but has no Filename field" >&2
            ;;
            3)
                echo "[fd-aptly] package ${package} ${version} is present but does not point to ${deb_filename}" >&2
            ;;
            *)
                echo "[fd-aptly] package ${package} ${version} is not present in ${packages_url}" >&2
            ;;
        esac

        return 1
    fi

    if ! curl -f -s -S -I "${verify_uri}/${filename}" >/dev/null;
    then
        echo "[fd-aptly] package file is listed but not reachable: ${verify_uri}/${filename}" >&2
        return 1
    fi

    echo "[fd-aptly] verified ${package} ${version}: ${filename}"
    return 0
}

function fd_aptly_verify_publish()
{
    local verify_uri="$(fd_aptly_get_verify_uri)"
    local files=()
    local file=""
    local attempt=1
    local rc=1

    case "${FD_APTLY_VERIFY_SKIP}" in
        true|yes|1)
            echo '[fd-aptly] skipping publish verification'
            return 0
        ;;
    esac

    if [ -z "${verify_uri}" ];
    then
        echo 'Missing variable FD_APTLY_VERIFY_URI' >&2
        return 1
    fi

    if [ ! -d "${FD_BUILD_DEST_DIR}" ];
    then
        echo "[fd-aptly] build directory does not exist: ${FD_BUILD_DEST_DIR}" >&2
        return 1
    fi

    while read file;
    do
        files[${#files[@]}]="${file}"
    done < <(find "${FD_BUILD_DEST_DIR}" -type f -name "*.deb" -print)

    if [ ${#files[@]} -eq 0 ];
    then
        echo "[fd-aptly] no debian package found in ${FD_BUILD_DEST_DIR}" >&2
        return 1
    fi

    echo "[fd-aptly] verifying published repository ${verify_uri}"

    while [ ${attempt} -le ${FD_APTLY_VERIFY_RETRIES} ];
    do
        rc=0

        for file in "${files[@]}";
        do
            fd_aptly_verify_deb_package "${verify_uri}" "${file}"
            local r=${?}
            [ ${r} -ne 0 ] && rc=${r}
        done

        if [ ${rc} -eq 0 ];
        then
            echo '[fd-aptly] publish verification succeeded'
            return 0
        fi

        if [ ${attempt} -lt ${FD_APTLY_VERIFY_RETRIES} ];
        then
            echo "[fd-aptly] publish verification failed, retry ${attempt}/${FD_APTLY_VERIFY_RETRIES} in ${FD_APTLY_VERIFY_SLEEP}s"
            sleep "${FD_APTLY_VERIFY_SLEEP}"
        fi

        attempt=$((attempt + 1))
    done

    echo "[fd-aptly] publish verification failed after ${FD_APTLY_VERIFY_RETRIES} attempts" >&2
    return ${rc}
}

case "${1}" in
    build-deb)
        fd_aptly_build_deb
    ;;
    upload-files)
        fd_aptly_upload_files
    ;;
    delete-files)
        fd_aptly_delete_files
    ;;
    repo-add-package)
        fd_aptly_repo_add_package
        rc=${?}
        fd_aptly_delete_files || true
        exit ${rc}
    ;;
    publish-repo)
        fd_aptly_publish_repo
    ;;
    verify-publish)
        fd_aptly_verify_publish
    ;;
    publish)
        fd_aptly_upload_files || exit ${?}
        fd_aptly_repo_add_package
        rc=${?}
        fd_aptly_delete_files || true
        [ ${rc} -ne 0 ] && exit ${rc}
        fd_aptly_publish_repo || exit ${?}
        fd_aptly_verify_publish || exit ${?}
    ;;
    *)
        echo 'Invalid action [build-deb|upload-files|delete-files|repo-add-package|publish-repo|verify-publish|publish]' >&2
        exit 1
    ;;
esac

exit ${?}
