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

ROOT="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
OPENQ4_VENDOR="${ROOT}/vendor/openq4"
OPENQ4_PIN="975e296b80b66f63b817d16ea78b53e0685c5c55"
PATCH="${ROOT}/port/native/patches/0014-cache-web-pk4-central-directory.patch"
HARNESS="${ROOT}/tests/native/unzip-cache-harness.cpp"
CXX="${CXX:-g++}"

for command in git tar timeout zip "${CXX}"; do
    command -v "${command}" >/dev/null 2>&1 || {
        echo "Required command is missing: ${command}" >&2
        exit 1
    }
done

[[ -d "${OPENQ4_VENDOR}/.git" ]] || {
    echo "Pinned OpenQ4 checkout is missing; run tools/sync-upstream.sh first" >&2
    exit 1
}
[[ -f "${PATCH}" && -f "${HARNESS}" ]] || {
    echo "ZIP cache patch or runtime harness is missing" >&2
    exit 1
}
git -C "${OPENQ4_VENDOR}" cat-file -e "${OPENQ4_PIN}^{commit}"

TMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/q4-zip-cache.XXXXXX")"
trap 'rm -rf "${TMP_ROOT}"' EXIT
SOURCE_ROOT="${TMP_ROOT}/source"
FIXTURE_ROOT="${TMP_ROOT}/fixture"
mkdir -p "${SOURCE_ROOT}" "${FIXTURE_ROOT}"

git -C "${OPENQ4_VENDOR}" archive "${OPENQ4_PIN}" \
    src/framework/Unzip.cpp src/framework/Unzip.h | tar -x -C "${SOURCE_ROOT}"
git -C "${SOURCE_ROOT}" apply \
    --include='src/framework/Unzip.cpp' \
    --include='src/framework/Unzip.h' \
    "${PATCH}"
grep -Fq 'unzlocal_CacheCentralDirectory' "${SOURCE_ROOT}/src/framework/Unzip.cpp"
grep -Fq 'central_dir_cache' "${SOURCE_ROOT}/src/framework/Unzip.h"

printf 'first payload\n' >"${FIXTURE_ROOT}/00-first.txt"
printf 'alpha payload\n' >"${FIXTURE_ROOT}/01-alpha.txt"
printf 'beta payload\n' >"${FIXTURE_ROOT}/02-beta.txt"
for ((index = 0; index < 128; index++)); do
    printf 'middle-payload-%03d:0123456789abcdef0123456789abcdef\n' "${index}"
done >"${FIXTURE_ROOT}/03-middle.txt"
printf 'gamma payload\n' >"${FIXTURE_ROOT}/04-gamma.txt"
printf 'delta payload\n' >"${FIXTURE_ROOT}/05-delta.txt"
printf 'last payload\n' >"${FIXTURE_ROOT}/06-last.txt"

entries=(
    00-first.txt
    01-alpha.txt
    02-beta.txt
    03-middle.txt
    04-gamma.txt
    05-delta.txt
    06-last.txt
)
(
    cd "${FIXTURE_ROOT}"
    zip -X -9 -q fixture.zip "${entries[@]}"
)
{
    printf 'Q4-WEB-SFX-PREFIX-0123456789\n'
    cat "${FIXTURE_ROOT}/fixture.zip"
} >"${FIXTURE_ROOT}/fixture-sfx.zip"

common_flags=(
    -std=c++17
    -O1
    -g
    -D_GNU_SOURCE
    -fno-omit-frame-pointer
    -Wall
    -Wextra
    -Wno-dangling-else
    -Wno-implicit-fallthrough
    -Wno-misleading-indentation
    -Wno-unused-parameter
    "-DQ4_UNZIP_CPP=\"${SOURCE_ROOT}/src/framework/Unzip.cpp\""
)

# GCC 11's PIE ASan runtime can recursively fault before main() while reserving
# shadow memory on kernels with 32-bit mmap randomization. A non-PIE harness
# keeps the sanitizer active without depending on permission to disable ASLR.
"${CXX}" "${common_flags[@]}" -fno-pie -no-pie -fsanitize=address \
    "${HARNESS}" -o "${TMP_ROOT}/unzip-cache-asan"
run_asan() {
    local archive="$1"
    local log="${TMP_ROOT}/asan-$(basename -- "${archive}").log"
    if (
        ulimit -f 2048
        ASAN_OPTIONS='detect_leaks=1:halt_on_error=1:abort_on_error=1' \
            timeout --signal=TERM --kill-after=2s 30s \
            "${TMP_ROOT}/unzip-cache-asan" "${archive}"
    ) >"${log}" 2>&1; then
        cat "${log}"
        return 0
    else
        local status=$?
        sed -n '1,200p' "${log}" >&2
        return "${status}"
    fi
}
run_asan "${FIXTURE_ROOT}/fixture.zip"
run_asan "${FIXTURE_ROOT}/fixture-sfx.zip"

"${CXX}" "${common_flags[@]}" -fsanitize=undefined \
    "${HARNESS}" -o "${TMP_ROOT}/unzip-cache-ubsan"
UBSAN_OPTIONS='halt_on_error=1:print_stacktrace=1' \
    "${TMP_ROOT}/unzip-cache-ubsan" "${FIXTURE_ROOT}/fixture.zip"
UBSAN_OPTIONS='halt_on_error=1:print_stacktrace=1' \
    "${TMP_ROOT}/unzip-cache-ubsan" "${FIXTURE_ROOT}/fixture-sfx.zip"

echo "OpenQ4 web ZIP cache ASan/UBSan regression PASS"
