#!/bin/bash
set -euo pipefail

OSBUILD_COMPOSER_TEST_DATA=/usr/share/tests/osbuild-composer/

# Get OS data.
source /usr/libexec/osbuild-composer-test/set-env-variables.sh
source /usr/libexec/tests/osbuild-composer/shared_lib.sh


if [ "${NIGHTLY:=false}" == "true" ]; then
    greenprint "INFO: Test not supported during nightly CI pipelines. Exiting ..."
    exit 1
fi

#
# Cloud provider / target names
#

CLOUD_PROVIDER_AWS="aws"
CLOUD_PROVIDER_GCP="gcp"
CLOUD_PROVIDER_AZURE="azure"

# define the default cloud provider to test for
CLOUD_PROVIDER="none"

#
# Test types
#
# Tests Koji compose via cloudapi
TEST_TYPE_CLOUDAPI="cloudapi"
# Tests Koji compose via cloudapi with upload to a cloud target
TEST_TYPE_CLOUD_UPLOAD="cloud-upload"

# test Koji compose via cloudapi without upload to cloud by default
TEST_TYPE="${1:-$TEST_TYPE_CLOUDAPI}"

# Koji hub URL to use for testing
KOJI_HUB_URL="http://localhost:8080/kojihub"

#
# Cloud upload - check environment and prepare it
#
if [[ "$TEST_TYPE" == "$TEST_TYPE_CLOUD_UPLOAD" ]]; then
    if [[ $# -ne 3 ]]; then
        echo "Usage: $0 cloud-upload TARGET IMAGE_TYPE"
        exit 1
    fi

    CLOUD_PROVIDER="${2}"
    IMAGE_TYPE="${3}"

    greenprint "Using Cloud Provider / Target ${CLOUD_PROVIDER} for Image Type ${IMAGE_TYPE}"

    # Load a correct test runner.
    # Each one must define following methods:
    # - checkEnv
    # - cleanup
    # - installClient
    case $CLOUD_PROVIDER in
        "$CLOUD_PROVIDER_AWS")
            source /usr/libexec/tests/osbuild-composer/api/aws.sh
            ;;
        "$CLOUD_PROVIDER_GCP")
            source /usr/libexec/tests/osbuild-composer/api/gcp.sh
            ;;
        "$CLOUD_PROVIDER_AZURE")
            source /usr/libexec/tests/osbuild-composer/api/azure.sh
            ;;
        *)
            echo "Unknown cloud provider: ${CLOUD_PROVIDER}"
            exit 1
    esac

    # Verify that this script is running in the right environment.
    checkEnv

    # Container image used for cloud provider CLI tools
    export CONTAINER_IMAGE_CLOUD_TOOLS="quay.io/osbuild/cloud-tools:latest"

    if type -p podman 2>/dev/null >&2; then
        export CONTAINER_RUNTIME=podman
    elif type -p docker 2>/dev/null >&2; then
        export CONTAINER_RUNTIME=docker
    else
        echo No container runtime found, install podman or docker.
        exit 1
    fi

    WORKDIR=$(mktemp -d)
    export WORKDIR
    function cleanups() {
        greenprint "Script execution stopped or finished - Cleaning up"
        set +eu
        cleanup
        sudo rm -rf "$WORKDIR"
        /usr/libexec/osbuild-composer-test/run-mock-auth-servers.sh stop
        set -eu
    }

    # install appropriate cloud environment client tool
    installClient
else
    # Source common functions
    # In the common case above, this file is sourced by 'aws.sh' / 'gcp.sh' / 'azure.sh'
    source /usr/libexec/tests/osbuild-composer/api/common/common.sh

    function cleanups() {
        greenprint "Script execution stopped or finished - Cleaning up"
        set +eu
        /usr/libexec/osbuild-composer-test/run-mock-auth-servers.sh stop
        set -eu
    }
fi

trap cleanups EXIT

# Verify that all the expected information is present in the buildinfo
function verify_buildinfo() {
    local buildid="${1}"
    local target_cloud="${2:-none}"

    local osbuild_version
    osbuild_version="$(osbuild --version | cut -d ' ' -f 2 -)"

    local extra_build_metadata
    # extract the extra build metadata JSON from the output
    extra_build_metadata="$(koji -s "${KOJI_HUB_URL}" --noauth call --json getBuild "${buildid}" | jq -r '.extra')"

    # sanity check the extra build metadata
    if [ -z "${extra_build_metadata}" ]; then
        echo "Extra build metadata is empty"
        exit 1
    fi

    # extract the image archives paths from the output and keep only the filenames
    local outputs_images
    outputs_images="$(koji -s "${KOJI_HUB_URL}" --noauth call --json listArchives "${buildid}" | jq -r 'map(select(.btype == "image" and .type_name != "json"))')"

    # we build one image for cloud test case and two for non-cloud test case
    local outputs_images_count
    outputs_images_count="$(echo "${outputs_images}" | jq 'length')"
    if [ "${target_cloud}" == "none" ]; then
        if [ "${outputs_images_count}" -ne 2 ]; then
            echo "Unexpected number of images in the buildinfo. Want 2, got ${outputs_images_count}."
            exit 1
        fi
    else
        if [ "${outputs_images_count}" -ne 1 ]; then
            echo "Unexpected number of images in the buildinfo. Want 1, got ${outputs_images_count}."
            exit 1
        fi

        # Verify that the target results are present in the image output metadata
        local target_results
        target_results="$(echo "${outputs_images}" | jq -r '.[0].extra.image.upload_target_results')"
        local target_results_count
        target_results_count="$(echo "${target_results}" | jq 'length')"
        if [ "$target_results_count" -ne 1 ]; then
            echo "Unexpected number of target results in the buildinfo. Want 1, got ${target_results_count}."
            exit 1
        fi

        local target_result_name
        target_result_name="$(echo "${target_results}" | jq -r '.[0].name')"
        local want_target_result_name
        case ${target_cloud} in
            "$CLOUD_PROVIDER_AWS")
                want_target_result_name="org.osbuild.aws"
                ;;
            "$CLOUD_PROVIDER_GCP")
                want_target_result_name="org.osbuild.gcp"
                ;;
            "$CLOUD_PROVIDER_AZURE")
                want_target_result_name="org.osbuild.azure.image"
                ;;
            *)
                echo "Unknown cloud provider: ${CLOUD_PROVIDER}"
                exit 1
        esac
        if [ "${target_result_name}" != "${want_target_result_name}" ]; then
            echo "Unexpected target result in the buildinfo. Want '${want_target_result_name}', got '${target_result_name}'."
            exit 1
        fi
    fi

    local outputs_manifests
    outputs_manifests="$(koji -s "${KOJI_HUB_URL}" --noauth call --json listArchives "${buildid}" | jq -r 'map(select(.btype == "image" and .type_name == "json" and (.filename | contains(".manifest."))))')"
    local outputs_manifests_count
    outputs_manifests_count="$(echo "${outputs_manifests}" | jq 'length')"
    if [ "${outputs_manifests_count}" -ne "${outputs_images_count}" ]; then
        echo "Mismatch between the number of image archives and image manifests in the buildinfo"
        exit 1
    fi

    local outputs_sboms
    outputs_sboms="$(koji -s "${KOJI_HUB_URL}" --noauth call --json listArchives "${buildid}" | jq -r 'map(select(.btype == "image" and .type_name == "json" and (.filename | contains(".spdx."))))')"
    local outputs_sboms_count
    outputs_sboms_count="$(echo "${outputs_sboms}" | jq 'length')"
    # there should be two SPDX files for each image, one for the image payload and one for the buildroot
    if [ "${outputs_sboms_count}" -ne $((outputs_images_count * 2)) ]; then
        echo "Mismatch between the number of image archives and SPDX SBOM files in the buildinfo"
        exit 1
    fi

    local outputs_logs
    outputs_logs="$(koji -s "${KOJI_HUB_URL}" --noauth call --json getBuildLogs "${buildid}" | jq -r 'map(select(.name != "cg_import.log"))')"
    local outputs_logs_count
    outputs_logs_count="$(echo "${outputs_logs}" | jq 'length')"
    if [ "${outputs_logs_count}" -ne "${outputs_images_count}" ]; then
        echo "Mismatch between the number of image archives and image logs in the buildinfo"
        exit 1
    fi

    local build_extra_md_image
    build_extra_md_image="$(echo "${extra_build_metadata}" | jq -r '.typeinfo.image')"

    for image_idx in $(seq 0 $((outputs_images_count - 1))); do
        local image
        image="$(echo "${outputs_images}" | jq -r ".[${image_idx}]")"

        local image_filename
        image_filename="$(echo "${image}" | jq -r '.filename')"

        local image_metadata_build
        image_metadata_build="$(echo "${build_extra_md_image}" | jq -r ".\"${image_filename}\"")"
        if [ "${image_metadata_build}" == "null" ]; then
            echo "Image metadata for '${image_filename}' is missing"
            exit 1
        fi

        local image_arch
        image_arch="$(echo "${image_metadata_build}" | jq -r '.arch')"
        if [ "${image_arch}" != "${ARCH}" ]; then
            echo "Unexpected arch for '${image_filename}'. Expected '${ARCH}', but got '${image_arch}'"
            exit 1
        fi

        local image_boot_mode
        image_boot_mode="$(echo "${image_metadata_build}" | jq -r '.boot_mode')"
        # for now, check just that the boot mode is a valid value
        case "${image_boot_mode}" in
            "uefi"|"legacy"|"hybrid")
                ;;
            "none"|*)
                # for now, we don't upload any images that have 'none' as boot mode, although it is a valid value
                echo "Unexpected boot mode for '${image_filename}'. Expected 'uefi', 'legacy' or 'hybrid', but got '${image_boot_mode}'"
                exit 1
                ;;
        esac

        local image_osbuild_artifact
        image_osbuild_artifact="$(echo "${image_metadata_build}" | jq -r '.osbuild_artifact')"
        if [ "${image_osbuild_artifact}" == "null" ]; then
            echo "Image osbuild artifact information for '${image_filename}' is missing"
            exit 1
        fi

        local image_osbuild_version
        image_osbuild_version="$(echo "${image_metadata_build}" | jq -r '.osbuild_version')"
        if [ "${image_osbuild_version}" != "${osbuild_version}" ]; then
            echo "Unexpected osbuild version for '${image_filename}'. Expected '${osbuild_version}', but got '${image_osbuild_version}'"
            exit 1
        fi

        local image_metadata_archive
        image_metadata_archive="$(echo "${image}" | jq -r '.extra.image')"
        if [ "${image_metadata_build}" != "${image_metadata_archive}" ]; then
            echo "Image extra metadata for '${image_filename}' in the build metadata and in the archive metadata differ"
            exit 1
        fi
    done

    local build_extra_md_manifest
    build_extra_md_manifest="$(echo "${extra_build_metadata}" | jq -r '.osbuild_manifest')"

    for manifest_idx in $(seq 0 $((outputs_manifests_count - 1))); do
        local manifest
        manifest="$(echo "${outputs_manifests}" | jq -r ".[${manifest_idx}]")"

        local manifest_filename
        manifest_filename="$(echo "${manifest}" | jq -r '.filename')"

        local manifest_metadata_build
        manifest_metadata_build="$(echo "${build_extra_md_manifest}" | jq -r ".\"${manifest_filename}\"")"
        if [ "${manifest_metadata_build}" == "null" ]; then
            echo "Manifest metadata for '${manifest_filename}' is missing"
            exit 1
        fi

        local manifest_arch
        manifest_arch="$(echo "${manifest_metadata_build}" | jq -r '.arch')"
        if [ "${image_arch}" != "${ARCH}" ]; then
            echo "Unexpected arch for '${manifest_filename}'. Expected '${ARCH}', but got '${manifest_arch}'"
            exit 1
        fi

        local manifest_info
        manifest_info="$(echo "${manifest_metadata_build}" | jq -r '.info')"
        if [ "${manifest_info}" == "null" ]; then
            echo "Manifest info for '${manifest_filename}' is missing"
            exit 1
        fi

        if [ "$(echo "${manifest_info}" | jq -r '.osbuild_composer_version')" == "null" ]; then
            echo "Manifest info for '${manifest_filename}' is missing osbuild-composer version"
            exit 1
        fi

        # check osbuild/images version info in the metadata
        local osbuild_composer_deps
        osbuild_composer_deps="$(echo "${manifest_info}" | jq -r '.osbuild_composer_deps')"
        if [ "$(echo "${osbuild_composer_deps}" | jq 'length')" -ne 1 ]; then
            echo "Manifest info for '${manifest_filename}' has unexpected number of osbuild-composer dependencies. \
                Expected 1, got '$(echo "${osbuild_composer_deps}" | jq 'length')'"
            exit 1
        fi
        if [ "$(echo "${osbuild_composer_deps}" | jq -r '.[0].path')" != "github.com/osbuild/images" ]; then
            echo "Manifest info for '${manifest_filename}' has unexpected osbuild-composer dependency path. \
                Expected 'github.com/osbuild/images', got '$(echo "${osbuild_composer_deps}" | jq -r '.[0].path')'"
            exit 1
        fi
        if [ "$(echo "${osbuild_composer_deps}" | jq -r '.[0].version')" == "null" ]; then
            echo "Manifest info for '${manifest_filename}' has missing 'github.com/osbuild/images' dependency version"
            exit 1
        fi

        local manifest_metadata_archive
        manifest_metadata_archive="$(echo "${manifest}" | jq -r '.extra.image')"
        if [ "${manifest_metadata_build}" != "${manifest_metadata_archive}" ]; then
            echo "Manifest extra metadata for '${manifest_filename}' in the build metadata and in the archive metadata differ"
            exit 1
        fi
    done
}

# Provision the software under test.
/usr/libexec/osbuild-composer-test/provision.sh jwt

greenprint "Starting containers"
sudo /usr/libexec/osbuild-composer-test/run-koji-container.sh start

greenprint "Adding kerberos config"
sudo cp \
    /tmp/osbuild-composer-koji-test/client.keytab \
    /etc/osbuild-worker/client.keytab
sudo cp \
    "${OSBUILD_COMPOSER_TEST_DATA}"/kerberos/krb5-local.conf \
    /etc/krb5.conf.d/local

greenprint "Adding the testsuite's CA cert to the system trust store"
sudo cp \
    /etc/osbuild-composer/ca-crt.pem \
    /etc/pki/ca-trust/source/anchors/osbuild-composer-tests-ca-crt.pem
sudo update-ca-trust

greenprint "Testing Koji"
koji --server="${KOJI_HUB_URL}" --user=osbuild --password=osbuildpass --authtype=password hello

greenprint "Creating Koji task"
koji --server="${KOJI_HUB_URL}" --user kojiadmin --password kojipass --authtype=password make-task image

# Always build the latest RHEL - that suits the koji API usecase the most.
if [[ "$DISTRO_CODE" == rhel-8* ]]; then
    DISTRO_CODE=rhel-8.10
elif [[ "$DISTRO_CODE" == rhel-9* ]]; then
    DISTRO_CODE=rhel-9.5
fi

case ${TEST_TYPE} in
    "$TEST_TYPE_CLOUDAPI")
        greenprint "Pushing compose to Koji (/api/image-builder-comoser/v2/)"
        COMPOSE_ID="$(sudo /usr/libexec/osbuild-composer-test/koji-compose.py "${DISTRO_CODE}" "${ARCH}")"
        ;;
    "$TEST_TYPE_CLOUD_UPLOAD")
        greenprint "Pushing compose to Koji (/api/image-builder-comoser/v2/) with cloud upload target"
        COMPOSE_ID="$(sudo -E /usr/libexec/osbuild-composer-test/koji-compose.py "${DISTRO_CODE}" "${ARCH}" "${CLOUD_PROVIDER}" "${IMAGE_TYPE}")"
        ;;
    *)
        echo "Unknown test type: ${TEST_TYPE}"
        exit 1
esac

if [[ "$TEST_TYPE" == "$TEST_TYPE_CLOUD_UPLOAD" ]]; then
    greenprint "Verify that image was uploaded to the cloud provider"

    COMPOSE_STATUS=$(compose_status "${COMPOSE_ID}")
    UPLOAD_OPTIONS=$(echo "${COMPOSE_STATUS}" | jq -r '.image_status.upload_status.options')

    # Authenticate with the appropriate cloud
    cloud_login

    case $CLOUD_PROVIDER in
        "$CLOUD_PROVIDER_AWS")
            AMI_IMAGE_ID=$(echo "${UPLOAD_OPTIONS}" | jq -r '.ami')
            $AWS_CMD ec2 describe-images \
                --owners self \
                --filters Name=image-id,Values="${AMI_IMAGE_ID}" \
                > "${WORKDIR}/ami.json"
            # extract the snapshot ID for the purpose of cleanup
            AWS_SNAPSHOT_ID=$(jq -r '.Images[].BlockDeviceMappings[].Ebs.SnapshotId' "$WORKDIR/ami.json")
            export AWS_SNAPSHOT_ID
            if [[ $(jq '.Images | length' "${WORKDIR}/ami.json") -ne 1 ]]; then
                echo "${AMI_IMAGE_ID} image not found in AWS"
                exit 1
            fi
            ;;
        "$CLOUD_PROVIDER_GCP")
            GCP_IMAGE_NAME=$(echo "${UPLOAD_OPTIONS}" | jq -r '.image_name')
            # The command exits with non-zero value if the image does not exist
            $GCP_CMD compute images describe "${GCP_IMAGE_NAME}"
            ;;
        "$CLOUD_PROVIDER_AZURE")
            AZURE_IMAGE_NAME=$(echo "${UPLOAD_OPTIONS}" | jq -r '.image_name')
            # The command exits with non-zero value if the image does not exist
            $AZURE_CMD image show --resource-group "${AZURE_RESOURCE_GROUP}" --name "${AZURE_IMAGE_NAME}"
            ;;
        *)
            echo "Unknown cloud provider: ${CLOUD_PROVIDER}"
            exit 1
    esac

    # if we got here, the image must have been found
    greenprint "Image was SUCCESSFULLY found in the respective cloud provider environment!"
fi

greenprint "Show Koji task"
koji --server="${KOJI_HUB_URL}" taskinfo 1

greenprint "Show Koji buildinfo"
koji --server="${KOJI_HUB_URL}" buildinfo 1

greenprint "Show Koji raw buildinfo"
koji --server="${KOJI_HUB_URL}" --noauth call --json getBuild 1

greenprint "Show Koji build archives"
koji --server="${KOJI_HUB_URL}" --noauth call --json listArchives 1

greenprint "Show Koji build logs"
koji --server="${KOJI_HUB_URL}" --noauth call --json getBuildLogs 1

greenprint "Verify the Koji build info and metadata"
verify_buildinfo 1 "${CLOUD_PROVIDER}"

greenprint "Run the integration test"
sudo /usr/libexec/osbuild-composer-test/osbuild-koji-tests

greenprint "Stopping containers"
sudo /usr/libexec/osbuild-composer-test/run-koji-container.sh stop

greenprint "Removing generated CA cert"
sudo rm \
    /etc/pki/ca-trust/source/anchors/osbuild-composer-tests-ca-crt.pem
sudo update-ca-trust
