#!/usr/bin/env bash

set -eu

: ${PROSE_POD_API_DIR:="$(realpath "$(dirname $0)"/../..)"}
export PROSE_POD_API_DIR
: ${REPOSITORY_ROOT:="${PROSE_POD_API_DIR:?}"}
export REPOSITORY_ROOT
LOCAL_RUN_DIR="${REPOSITORY_ROOT:?}"/local-run
BENCHMARK_DIR="${REPOSITORY_ROOT:?}"/benchmarks/prosody-storage-speeds
: ${SCENARIOS_DIR:="${BENCHMARK_DIR:?}"/scenarios}
export SCENARIOS_DIR
EPHEMERAL_SCENARIOS_FILE="${SCENARIOS_DIR:?}"/ephemeral-scenarios.txt

if ! which gdate >/dev/null; then
	echo 'date doesn’t support milliseconds. Please install gdate.' >&2
	echo 'On macOS, you can use `brew install coreutils`.' >&2
	exit 1
fi

SUMMARY_FILE="${BENCHMARK_DIR:?}/results.md"
REPETITIONS=10

export LOG_TRACE=1

write() {
	printf "%b" "$@\n" >> "${SUMMARY_FILE:?}"
}

test-storage() {
	SCENARIO_NAME=${1:?"Must pass a storage (scenario name)"}
	export SCENARIO_NAME
	SCENARIO_DIR="${SCENARIOS_DIR:?}/${SCENARIO_NAME:?}"

	if [ -f "${SCENARIO_DIR:?}"/Dockerfile.server ]; then
		export PROSE_POD_SERVER_IMAGE_TAG=bench
		docker build -f "${SCENARIO_DIR:?}"/Dockerfile.server \
			-t proseim/prose-pod-server:"${PROSE_POD_SERVER_IMAGE_TAG:?}" \
			--build-arg TAG="${PROSE_POD_SERVER_IMAGE_TAG_DEFAULT:?}" \
			.
	else
		export PROSE_POD_SERVER_IMAGE_TAG="${PROSE_POD_SERVER_IMAGE_TAG_DEFAULT:?}"
	fi

	write '\n## `'"$*"'`\n'

	# Generate demo data if needed.
	# NOTE: `prosody/data` isn’t committed, to reduce repository bloat.
	if ! [ -d "${SCENARIO_DIR:?}/prosody/data" ]; then
		. "${LOCAL_RUN_DIR:?}"/scripts/make-demo-scenario
	fi

	echo Running the API…
	unset SCENARIO_DIR
	task local:run -- --scenario="${SCENARIO_NAME:?}" \
		--api="${PROSE_POD_API_IMAGE_TAG:?}" \
		--ephemeral --detach

	# Read ephemeral scenario name.
	SCENARIO_NAME="$(tail -n 1 "${EPHEMERAL_SCENARIOS_FILE:?}")"
	SCENARIO_DIR="${SCENARIOS_DIR:?}/${SCENARIO_NAME:?}"

	if [[ "${1-}" == sqlite* ]]; then
		if [ "${2-}" == 'journal_mode=WAL' ]; then
			echo 'Setting `PRAGMA journal_mode=WAL` in `prosody.sqlite`…'
			docker run --rm --entrypoint sqlite3 \
				-v "${SCENARIO_DIR:?}"/prosody/data/prosody.sqlite:/var/lib/prosody/prosody.sqlite \
				proseim/prose-pod-server:"${PROSE_POD_SERVER_IMAGE_TAG:?}" \
				/var/lib/prosody/prosody.sqlite \
				'PRAGMA journal_mode=WAL;'
		elif [[ "${2-}" == 'sqlite_tune="normal"'* ]]; then
			echo 'Checking for `PRAGMA journal_mode=WAL` in `prosody.sqlite`…'
			local journal_mode="$(docker run --rm --entrypoint sqlite3 \
				-v "${SCENARIO_DIR:?}"/prosody/data/prosody.sqlite:/var/lib/prosody/prosody.sqlite \
				proseim/prose-pod-server:"${PROSE_POD_SERVER_IMAGE_TAG:?}" \
				/var/lib/prosody/prosody.sqlite \
				'PRAGMA journal_mode;')"
			if [ "${journal_mode:?}" != wal ]; then
				write '`sqlite_tune="normal"` didn’t work (`journal_mode` is `'"${journal_mode:?}"'`).'
				return 1
			fi
		fi
	fi

	ADMIN_TOKEN="$(xh POST :8080/v1/login -a 'pauline.collins@example.org:demo' -b | jq -r '.token')"

	add-member-n() {
		local n="${1:?}"
		echo "Add member ${n} (via auto-accepted invitation)"
		xh POST :8080/v1/invitations -q \
			auto_accept==true \
			username="member.${n}" \
			channel=email \
			email_address="member.${n}@example.org" \
			-A bearer -a "${ADMIN_TOKEN:?}"
	}
	delete-member-n() {
		local n="${1:?}"
		xh DELETE :8080/v1/members/"member.${n}@example.org" -q \
			-A bearer -a "${ADMIN_TOKEN:?}"
	}

	local test_member member_count
	write "| member # | min.   | max.   | avg.   |"
	write "| -------- | ------ | ------ | ------ |"
	for test_member in 25 50 75 100; do
		member_count="$(xh GET :8080/v1/members page_size==1 -h -A bearer -a "${ADMIN_TOKEN:?}" | grep '^Pagination-Item-Count:' | cut -d' ' -f2-)"

		for n in $(seq $(( member_count + 1 )) $(( test_member - 1 ))); do
			add-member-n "${n:?}"
		done

		echo "Performing the benchmark…"
		local total_ms=0 min_ms=999999 max_ms=0 start_ms end_ms duration_ms
		for n in $(seq 1 ${REPETITIONS:?}); do
			# NOTE: We cannot use `test_member` only to create JIDs as rosters aren’t
			#   cleaned up properly when deleting a member and that would speed up
			#   subsequent subscriptions.
			member_n="${test_member:?}-$(gdate +%s%3N)"

			start_ms=$(gdate +%s%3N)
			add-member-n ${member_n:?}
			end_ms=$(gdate +%s%3N)

			duration_ms=$(( end_ms - start_ms ))
			echo "[$n] Took ${duration_ms:?}ms."
			max_ms=$(( duration_ms > max_ms ? duration_ms : max_ms ))
			min_ms=$(( duration_ms < min_ms ? duration_ms : min_ms ))
			total_ms=$(( total_ms + duration_ms ))

			delete-member-n ${member_n:?}
		done
		write "| $(printf "%-3d" "$test_member")      | $(printf "%4d" "$min_ms")ms | $(printf "%4d" "$max_ms")ms | $(printf "%4d" "$(( total_ms / REPETITIONS ))")ms |"
	done

	echo Stopping the API…
	task local:stop
}

if [ -f "${SUMMARY_FILE:?}" ]; then
	# Create a backup of the previous results.
	mv "${SUMMARY_FILE:?}" "${SUMMARY_FILE%'.md'}.$(date +%s).bak.md"
fi

write '# Prosody storages benchmark results (`'"$(date -Iseconds -u)"'`)'

build-api-commit() {
	local docker_tag="${1:?"Must pass a git ref"}"
	local checkout_ref="${2:?"Must pass a git ref"}"
	local git_head="$(git rev-parse HEAD)"
	git checkout "${checkout_ref:?}"
	# NOTE: We need to build the API locally because we need to use `debug_only`
	#   configuration.
	task build-image -- --tag="${docker_tag:?}" --profile=dev --locked
	# NOTE: We have to check out the commit we were on so this script doesn’t
	#   change during its execution.
	git checkout "${git_head:?}"
}

build-api-commit bench-no-delay d5244ee6042ad8645c6810477b0b3239af5382bf
PROSE_POD_API_IMAGE_TAG=bench-no-delay

# NOTE: `0.3.14` includes all community modules (therefore adding support for
#   `mod_appendmap`).
PROSE_POD_SERVER_IMAGE_TAG_DEFAULT=0.3.14
test-storage internal
test-storage appendmap

# NOTE: `0.3.14` doesn’t have anything related to SQLite.
#   We add SQLite and LuaDBI by hand in `Dockerfile.server`.
PROSE_POD_SERVER_IMAGE_TAG_DEFAULT=0.3.14
test-storage sqlite-luadbi
test-storage sqlite-luadbi 'journal_mode=WAL'
if ! test-storage sqlite-luadbi 'sqlite_tune="normal" (journal_mode=WAL)'; then
	echo '`sqlite_tune="normal"` didn’t work with LuaDBI.'
fi

# NOTE: `0.3.15` adds LuaSQLite3.
PROSE_POD_SERVER_IMAGE_TAG_DEFAULT=0.3.15
test-storage sqlite-luasqlite 'sqlite_tune="normal" (journal_mode=WAL)'
test-storage sqlite-as-default-luasqlite 'sqlite_tune="normal" (journal_mode=WAL)'

build-api-commit bench-delay 229fb4d29a8460440ff73500a13c013ac10f7ca8
# NOTE: `bench-delay` delays roster updates.
PROSE_POD_API_IMAGE_TAG=bench-delay

# NOTE: `0.3.16` adds delayed sync in `mod_admin_rest`.
PROSE_POD_SERVER_IMAGE_TAG_DEFAULT=0.3.16
test-storage internal '(delayed roster updates)'
test-storage appendmap '(delayed roster updates)'
test-storage sqlite-luasqlite 'sqlite_tune="normal" (journal_mode=WAL) (delayed roster updates)'
test-storage sqlite-as-default-luasqlite 'sqlite_tune="normal" (journal_mode=WAL) (delayed roster updates)'
