#!/bin/sh
set -eu

DIST="$1"
RELEASE="$2"
VARIANT="$3"
TYPE="$4"
META_URL="$5"
DATA_URL="$6"

cleanup() {
    rm -Rf "/tmp/${TEST_IMAGE}"
    incus delete -f "${TEST_IMAGE}" 2>/dev/null || true
    incus delete -f "${TEST_IMAGE}-priv" 2>/dev/null || true
    incus delete -f "${TEST_IMAGE}-unpriv" 2>/dev/null || true
    incus image delete "${TEST_IMAGE}" 2>/dev/null || true

    if [ "${FAIL}" = "1" ]; then
        exit 1
    fi

    exit 0
}

# Skip VM tests on arm64
if [ "${TYPE}" = "vm" ] && [ "$(uname -m)" = "aarch64" ]; then
    echo "==> SKIP: Can't test VM image on arm64 (lack nested support)"
    exit
fi

# Setup the test environment
TEST_IMAGE="$(mktemp -u test-image-XXXXXX)"
mkdir -p "/tmp/${TEST_IMAGE}"

FAIL=1
trap cleanup EXIT HUP INT TERM

echo "==> Fetching the image"
curl "${META_URL}" -s -o "/tmp/${TEST_IMAGE}/meta"
curl "${DATA_URL}" -s -o "/tmp/${TEST_IMAGE}/root"
FINGERPRINT=$(cat "/tmp/${TEST_IMAGE}/meta" "/tmp/${TEST_IMAGE}/root" | sha256sum | cut -d' ' -f1)
incus image delete "${FINGERPRINT}" 2>/dev/null || true
incus image import "/tmp/${TEST_IMAGE}/meta" "/tmp/${TEST_IMAGE}/root" --alias "${TEST_IMAGE}"

echo "==> Creating the instances"
INSTANCES=""
if [ "${TYPE}" = "vm" ]; then
    incus init "${TEST_IMAGE}" "${TEST_IMAGE}" --vm -c security.secureboot=false -c limits.cpu=4 -c limits.memory=4GB
    INSTANCES="${TEST_IMAGE}"

    # Handle distros needing the agent drive
    if [ "${DIST}" = "almalinux" ] || [ "${DIST}" = "centos" ] || [ "${DIST}" = "openeuler" ] || [ "${DIST}" = "rockylinux" ]; then
        incus config device add "${TEST_IMAGE}" agent disk source=agent:config
    fi

    # Cloud-init testing
    if [ "${VARIANT}" = "cloud" ]; then
        incus config set "${TEST_IMAGE}" user.user-data "$(cat << EOF
#cloud-config
write_files:
  - content: "foo\n"
    path: /user-data
EOF
)"

        incus config set "${TEST_IMAGE}" user.vendor-data "$(cat << EOF
#cloud-config
bootcmd:
  - "echo bar > /vendor-data"
EOF
)"
    fi
else
    for PRIV in "priv" "unpriv"; do
        if [ "${PRIV}" = "priv" ] && [ "${DIST}" = "nixos" ] && [ "${RELEASE}" = "23.11" ]; then
            # NixOS 23.11 will never support privileged containers, but future versions do
            continue
        fi

        incus init "${TEST_IMAGE}" "${TEST_IMAGE}-${PRIV}"
        INSTANCES="${INSTANCES} ${TEST_IMAGE}-${PRIV}"

        # FIXME: workaround for Linux 6.6.3 apparmor regression.
        printf "2\ndenylist\nreject_force_umount\n[all]\nfsconfig errno 38\nfsopen errno 38\n" | incus config set "${TEST_IMAGE}-${PRIV}" raw.seccomp -

        if [ "${PRIV}" = "priv" ]; then
            incus config set "${TEST_IMAGE}-${PRIV}" security.privileged=true
        fi

        if [ "${DIST}" = "voidlinux" ]; then
            # Workaround weird init system
            incus config set "${TEST_IMAGE}-${PRIV}" raw.lxc lxc.signal.halt=SIGCONT
        fi

        if [ "${DIST}" = "slackware" ]; then
            # Workaround weird init system
            incus config set "${TEST_IMAGE}-${PRIV}" raw.lxc lxc.signal.halt=SIGKILL
        fi

        # Cloud-init testing
        if [ "${VARIANT}" = "cloud" ]; then
            incus config set "${TEST_IMAGE}-${PRIV}" user.user-data "$(cat << EOF
#cloud-config
write_files:
  - content: "foo\n"
    path: /user-data
EOF
)"

            incus config set "${TEST_IMAGE}-${PRIV}" user.vendor-data "$(cat << EOF
#cloud-config
bootcmd:
  - "echo bar > /vendor-data"
EOF
)"
        fi
    done
fi

# Start them all
echo "==> Starting the instances"
for i in ${INSTANCES}; do
    incus start "${i}"
done

# Wait for things to settle
echo "==> Waiting for instances to start"
[ "${TYPE}" = "container" ] && sleep 2m
[ "${TYPE}" = "vm" ] && sleep 3m
incus list "${TEST_IMAGE}"

# Check that all instances have an IPv4 and IPv6 address
echo "==> Performing network tests"
FAIL=0
for url in $(incus query "/1.0/instances" | jq -r .[] | grep "${TEST_IMAGE}"); do
    name=$(echo "${url}" | cut -d/ -f4)

    # Skip busybox as it wouldn't pass any test
    if [ "${DIST}" = "busybox" ]; then
        echo "===> SKIP: Busybox is untestable"
        continue
    fi

    # Skip CentOS 7 VMs due to racy agent
    if [ "${TYPE}" = "vm" ] && [ "${DIST}" = "centos" ] && [ "${RELEASE}" = "7" ]; then
        echo "===> SKIP: CentOS 7 has an unstable agent: ${name}"
        continue
    fi

    # Skip Ubuntu noble containers due to cgroup2 being required
    if [ "${TYPE}" = "container" ] && [ "${DIST}" = "ubuntu" ] && [ "${RELEASE}" = "noble" ]; then
        echo "===> SKIP: Ubuntu noble container due to cgroup2 requirement: ${name}"
        continue
    fi

    # Systemd cleanliness
    if incus exec "${name}" -- sh -c "type systemctl" >/dev/null 2>&1; then
        incus exec "${name}" -- systemctl reset-failed systemd-networkd-wait-online.service >/dev/null 2>&1 || true
        if incus exec "${name}" -- systemctl --failed 2>&1 | grep -q '\sfailed\s'; then
            echo "===> FAIL: systemd clean: ${name}"

            # Show the systemd failures
            echo "===> DEBUG: systemd failed: ${name}"
            incus exec "${name}" -- systemctl --failed
            FAIL=1
        else if incus exec "${name}" -- systemctl -a | grep -q ssh; then
            echo "===> FAIL: systemd clean: SSH server present"
        else
            echo "===> PASS: systemd clean: ${name}"
        fi
    else
        echo "===> SKIP: systemd clean: ${name}"
    fi

    # Get the addresses
    address=$(incus query "${url}/state" | jq -r ".network.eth0.addresses | .[] | select(.scope | contains(\"global\")) | .address" 2>/dev/null || true)
    if [ -z "${address}" ]; then
        address=$(incus query "${url}/state" | jq -r ".network.enp5s0.addresses | .[] | select(.scope | contains(\"global\")) | .address" 2>/dev/null || true)
    fi

    if [ -z "${address}" ]; then
        echo "===> FAIL: No network interface: ${name}"

        # Show the network state
        echo "===> DEBUG: network state: ${name}"
        incus info "${name}"
        FAIL=1
    fi

    # IPv4 address
    if echo "${address}" | grep "\." -q; then
        echo "===> PASS: IPv4 address: ${name}"
    else
        echo "===> FAIL: IPv4 address: ${name}"
        FAIL=1
    fi

    # IPv6 address
    if echo "${address}" | grep ":" -q; then
        echo "===> PASS: IPv6 address: ${name}"
    else
        echo "===> FAIL: IPv6 address: ${name}"
        FAIL=1
    fi

    # DNS resolution
    DNS=0
    for i in $(seq 3); do
        if incus exec "${name}" -- getent hosts linuxcontainers.org >/dev/null 2>&1; then
            DNS=1
            break
        fi

        if incus exec "${name}" -- ping -c1 -W1 linuxcontainers.org >/dev/null 2>&1; then
            DNS=1
            break
        fi

        sleep 1
    done
    if [ "${DNS}" = "1" ]; then
        echo "===> PASS: DNS resolution: ${name}"
    else
        echo "===> FAIL: DNS resolution: ${name}"
        FAIL=1
    fi

    # Cloud-init testing
    if [ "${VARIANT}" = "cloud" ]; then
        if [ "$(incus file pull "${name}/user-data" - 2>/dev/null)" = "foo" ]; then
            echo "===> PASS: cloud-init user-data provisioning: ${name}"
        else
            echo "===> FAIL: cloud-init user-data provisioning: ${name}"
            FAIL=1
        fi

        if [ "$(incus file pull "${name}/vendor-data" - 2>/dev/null)" = "bar" ]; then
            echo "===> PASS: cloud-init vendor-data provisioning: ${name}"
        else
            echo "===> FAIL: cloud-init vendor-data provisioning: ${name}"
            FAIL=1
        fi
    fi
done

# Check that all instances can be stopped
echo "==> Performing shutdown test"
STOPPED=0
for i in $(seq 10); do
    # shellcheck disable=SC2086
    if incus stop ${INSTANCES} --timeout=30 >/dev/null 2>&1; then
        STOPPED=1
        break
    else
        COUNT="$(incus list "${TEST_IMAGE}" | grep -c RUNNING)"
        if [ "${COUNT}" = "0" ]; then
            STOPPED=1
            break
        fi

        echo "${COUNT} instances still running"
    fi
done

incus list "${TEST_IMAGE}"

[ "${STOPPED}" = "0" ] && FAIL=1
