#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BINARY="$ROOT/target/release/systemd-resolved"
CLIENT="$ROOT/target/release/resolvectl"
NSS_MODULE=
SUITE=TEST-75-RESOLVED
OUTPUT=
SYSTEMD_TREE=
KEEP=false

usage() {
    cat <<'EOF'
Usage: sudo-preserving-env scripts/run-upstream-test-75.sh [OPTIONS]

Runs a supported pinned upstream resolver integration suite without changing any
recorded upstream test file. Candidate binaries enter the image through
additional mkosi/testdata overlays and a service drop-in. A proof marker must
appear in the captured log.

Options:
  --suite NAME         TEST-75-RESOLVED (default) or TEST-89-RESOLVED-MDNS
  --binary PATH        Candidate systemd-resolved binary
  --client PATH        Candidate resolvectl binary
  --nss-module PATH    Candidate libnss_resolve.so.2 (required for TEST-75)
  --output PATH        Stable output directory
  --systemd-tree PATH  Reuse an exact pinned upstream checkout
  --keep               Keep the temporary upstream checkout
  -h, --help           Show this help
EOF
}

while (($#)); do
    case "$1" in
        --suite)
            SUITE=${2:?missing suite name}
            shift 2
            ;;
        --binary)
            BINARY=${2:?missing binary path}
            shift 2
            ;;
        --client)
            CLIENT=${2:?missing client path}
            shift 2
            ;;
        --nss-module)
            NSS_MODULE=${2:?missing NSS module path}
            shift 2
            ;;
        --output)
            OUTPUT=${2:?missing output path}
            shift 2
            ;;
        --systemd-tree)
            SYSTEMD_TREE=${2:?missing systemd tree path}
            shift 2
            ;;
        --keep)
            KEEP=true
            shift
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *)
            printf 'Unknown option: %s\n' "$1" >&2
            exit 2
            ;;
    esac
done

case "$SUITE" in
    TEST-75-RESOLVED|TEST-89-RESOLVED-MDNS)
        ;;
    *)
        printf 'Unsupported pinned resolver suite: %s\n' "$SUITE" >&2
        exit 2
        ;;
esac

SUITE_SLUG="${SUITE,,}"
SUITE_SLUG="${SUITE_SLUG//-/_}"
SUITE_MARKER="${SUITE//-/_}"
if [[ -z $OUTPUT ]]; then
    case "$SUITE" in
        TEST-75-RESOLVED)
            OUTPUT="$ROOT/target/upstream-test-75"
            ;;
        TEST-89-RESOLVED-MDNS)
            OUTPUT="$ROOT/target/upstream-test-89-mdns"
            ;;
    esac
fi

for command in gcc git journalctl meson mkosi python3 sha256sum sudo tee; do
    command -v "$command" >/dev/null || {
        printf 'Required command is missing: %s\n' "$command" >&2
        exit 2
    }
done
MKOSI="$(command -v mkosi)"
MULTIARCH="$(gcc -print-multiarch)"
[[ $MULTIARCH =~ ^[A-Za-z0-9_-]+$ ]] || {
    printf 'The compiler returned an invalid multiarch directory.\n' >&2
    exit 2
}
[[ -x $BINARY && -x $CLIENT ]] || {
    printf 'Candidate release binaries are missing.\n' >&2
    exit 2
}
if [[ $SUITE == TEST-75-RESOLVED && ! -f $NSS_MODULE ]]; then
    printf 'Candidate NSS module is missing.\n' >&2
    exit 2
fi

BINARY="$(readlink -f "$BINARY")"
CLIENT="$(readlink -f "$CLIENT")"
if [[ -n $NSS_MODULE ]]; then
    NSS_MODULE="$(readlink -f "$NSS_MODULE")"
fi
OUTPUT="$(readlink -m "$OUTPUT")"
BASELINE="$ROOT/compat/upstream-systemd"
COMMIT="$(cat "$BASELINE/commit")"
RELEASE="$(cat "$BASELINE/release")"
SOURCE_TREE="$(git -C "$ROOT" rev-parse 'HEAD^{tree}')"
DAEMON_HASH="$(sha256sum "$BINARY" | awk '{print $1}')"
CLIENT_HASH="$(sha256sum "$CLIENT" | awk '{print $1}')"
NSS_HASH=
if [[ -n $NSS_MODULE ]]; then
    NSS_HASH="$(sha256sum "$NSS_MODULE" | awk '{print $1}')"
fi
if [[ $SUITE == TEST-75-RESOLVED ]]; then
    MARKER="RUSTD_RESOLVED_TEST_75_${SOURCE_TREE}_${DAEMON_HASH}_${NSS_HASH}"
else
    MARKER="RUSTD_RESOLVED_${SUITE_MARKER}_${SOURCE_TREE}_${DAEMON_HASH}"
fi

rm -rf "$OUTPUT"
mkdir -p "$OUTPUT"
LOG="$OUTPUT/$SUITE.log"

CREATED_TREE=false
if [[ -z $SYSTEMD_TREE ]]; then
    SYSTEMD_TREE="$(mktemp -d -t "systemd-${SUITE_SLUG}.XXXXXX")"
    CREATED_TREE=true
    git clone --filter=blob:none --no-checkout \
        https://github.com/systemd/systemd.git "$SYSTEMD_TREE"
    git -C "$SYSTEMD_TREE" fetch --depth 1 origin "$COMMIT"
    git -C "$SYSTEMD_TREE" checkout --detach "$COMMIT"
else
    SYSTEMD_TREE="$(readlink -f "$SYSTEMD_TREE")"
fi

cleanup() {
    status=$?
    if [[ $CREATED_TREE == true && $KEEP != true ]]; then
        sudo rm -rf -- "$SYSTEMD_TREE"
    else
        printf 'Upstream tree retained at %s\n' "$SYSTEMD_TREE" >&2
    fi
    exit "$status"
}
trap cleanup EXIT HUP INT TERM

[[ $(git -C "$SYSTEMD_TREE" rev-parse HEAD) == "$COMMIT" ]] || {
    printf 'Upstream tree is not at the pinned commit.\n' >&2
    exit 1
}
(
    cd "$SYSTEMD_TREE"
    sha256sum --check --strict "$BASELINE/resolve-source.sha256"
    sha256sum --check --strict "$BASELINE/resolve-tests.sha256"
)

TEST_DIR="$SYSTEMD_TREE/test/integration-tests/$SUITE"
[[ -d $TEST_DIR ]] || {
    printf 'Pinned systemd tree has no %s.\n' "$SUITE" >&2
    exit 1
}

install_overlay() {
    local root=$1
    install -d -m 0755 \
        "$root/usr/lib/systemd" \
        "$root/usr/bin" \
        "$root/etc/systemd/system/systemd-resolved.service.d"
    install -m 0755 "$BINARY" "$root/usr/lib/systemd/rustd-resolved"
    install -m 0755 "$CLIENT" "$root/usr/bin/resolvectl"
    if [[ -n $NSS_MODULE ]]; then
        install -D -m 0755 "$NSS_MODULE" \
            "$root/usr/lib/$MULTIARCH/libnss_resolve.so.2"
    fi
    cat >"$root/usr/lib/systemd/rustd-resolved-wrapper" <<EOF
#!/bin/sh
printf '%s\n' '$MARKER' || exit 126
export RUSTD_RESOLVED_QUERY_DIAGNOSTICS=1
exec /usr/lib/systemd/rustd-resolved "\$@"
EOF
    chmod 0755 "$root/usr/lib/systemd/rustd-resolved-wrapper"
    cat >"$root/etc/systemd/system/systemd-resolved.service.d/99-rustd-resolved.conf" <<'EOF'
[Service]
ExecStart=
ExecStart=/usr/lib/systemd/rustd-resolved-wrapper
Environment=RUSTD_RESOLVED_MDNS=yes
StandardOutput=journal+console
EOF
}

install_overlay "$SYSTEMD_TREE/mkosi/mkosi.extra"

if [[ -d $SYSTEMD_TREE/mkosi/mkosi.conf.d ]]; then
    cat >"$SYSTEMD_TREE/mkosi/mkosi.conf.d/99-rustd-resolved-runtime.conf" <<'EOF'
[Distribution]
Distribution=ubuntu
Release=noble

[Content]
Packages=libssl3,libgfortran5,libgcc-s1,libstdc++6
EOF
fi

MESON_OPTIONS=(
    --buildtype=debugoptimized
    -Dtests=true
)
if grep -Eq "option\(['\"]integration-tests['\"]" "$SYSTEMD_TREE/meson_options.txt"; then
    MESON_OPTIONS+=( -Dintegration-tests=true )
fi
if grep -Eq "option\(['\"]slow-tests['\"]" "$SYSTEMD_TREE/meson_options.txt"; then
    MESON_OPTIONS+=( -Dslow-tests=true )
fi
if [[ ! -f $SYSTEMD_TREE/mkosi.key || ! -f $SYSTEMD_TREE/mkosi.crt ]]; then
    mkosi --directory "$SYSTEMD_TREE" genkey
fi
sudo --preserve-env=PATH,HOME,TERM \
    "$MKOSI" --directory "$SYSTEMD_TREE" --force box -- true
if [[ ! -f $SYSTEMD_TREE/build/build.ninja ]]; then
    sudo --preserve-env=PATH,HOME,TERM \
        "$MKOSI" --directory "$SYSTEMD_TREE" box -- \
        meson setup "${MESON_OPTIONS[@]}" build
else
    sudo --preserve-env=PATH,HOME,TERM \
        "$MKOSI" --directory "$SYSTEMD_TREE" box -- \
        meson setup --reconfigure "${MESON_OPTIONS[@]}" build
fi
sudo --preserve-env=PATH,HOME,TERM \
    "$MKOSI" --directory "$SYSTEMD_TREE" --force box -- \
    meson compile -C build mkosi

set +e
{
    printf 'Pinned release: %s\nPinned commit: %s\nSource tree: %s\n' \
        "$RELEASE" "$COMMIT" "$SOURCE_TREE"
    printf 'Candidate daemon SHA-256: %s\n' "$DAEMON_HASH"
    printf 'Candidate client SHA-256: %s\n' "$CLIENT_HASH"
    if [[ -n $NSS_HASH ]]; then
        printf 'Candidate NSS module SHA-256: %s\n' "$NSS_HASH"
    fi
    printf 'Using the pinned Meson and mkosi integration-test entrypoint.\n'
    sudo --preserve-env=PATH,HOME,TERM \
        "$MKOSI" --directory "$SYSTEMD_TREE" box -- \
        env \
        SYSTEMD_INTEGRATION_TESTS=1 \
        TEST_NO_NSPAWN=1 \
        TEST_NO_QEMU=0 \
        meson test -C build \
        --no-rebuild \
        --setup=integration \
        --print-errorlogs \
        --no-stdsplit \
        --verbose \
        "$SUITE"
} >"$LOG" 2>&1
STATUS=$?
set -e

while IFS= read -r -d '' journal; do
    printf '\nCandidate daemon journal: %s\n' "$journal" >>"$LOG"
    sudo journalctl --file "$journal" --no-pager -o short-monotonic \
        -u systemd-resolved.service 2>&1 | tee -a "$LOG" >/dev/null || true
done < <(find "$SYSTEMD_TREE/build/test/journal" -maxdepth 1 -type f \
    -name "$SUITE-*.journal" -print0)

(
    cd "$SYSTEMD_TREE"
    sha256sum --check --strict "$BASELINE/resolve-source.sha256"
    sha256sum --check --strict "$BASELINE/resolve-tests.sha256"
) >>"$LOG" 2>&1

if ! grep -Fq "$MARKER" "$LOG"; then
    printf 'The suite ran without proof that the candidate daemon executed.\n' >&2
    exit 1
fi
if ((STATUS != 0)); then
    printf '%s failed against the candidate; see %s\n' "$SUITE" "$LOG" >&2
    exit "$STATUS"
fi

python3 - \
    "$OUTPUT/evidence.json" "$RELEASE" "$COMMIT" "$SOURCE_TREE" \
    "$DAEMON_HASH" "$CLIENT_HASH" "$NSS_HASH" "$MARKER" "$LOG" "$SUITE" <<'PY'
from datetime import datetime, timezone
import hashlib
import json
from pathlib import Path
import sys

(
    output,
    release,
    upstream_commit,
    source_tree,
    daemon_hash,
    client_hash,
    nss_hash,
    marker,
    log_path,
    suite,
) = sys.argv[1:]
log = Path(log_path)
payload = {
    "schema": 1,
    "suite": suite,
    "unmodified_recorded_upstream_files": True,
    "upstream_release": release,
    "upstream_commit": upstream_commit,
    "source_tree": source_tree,
    "daemon_sha256": daemon_hash,
    "client_sha256": client_hash,
    "runtime_marker": marker,
    "completed_at": datetime.now(timezone.utc).isoformat(),
    "log": {
        "name": log.name,
        "size": log.stat().st_size,
        "sha256": hashlib.sha256(log.read_bytes()).hexdigest(),
    },
}
if nss_hash:
    payload["nss_module_sha256"] = nss_hash
Path(output).write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
PY

printf 'Pinned upstream %s passed with the candidate daemon.\n' "$SUITE"
