#!/bin/bash
set -euo pipefail

# Repeatable pgbench benchmark harness.
# Usage example:
#   PGHOST=localhost PGPORT=5432 PGUSER=postgres PGPASSWORD=secret \
#   ./pgbench_benchmark.sh
#
# Optional environment variables:
#   BENCH_DB=pgbench
#   SCALE=50
#   DURATION=60
#   CLIENTS="1 4 8 16 32"
#   JOBS=4
#   WARMUP_SECONDS=15
#   RUN_READ_ONLY=1
#   RUN_CUSTOM=0
#   CUSTOM_SQL_FILE=pgbench_custom_workload.sql
#   CUSTOM_MODE_NAME=custom_sql
#   CUSTOM_READ_PCT=80
#   CUSTOM_HOT_PCT=90
#   CUSTOM_TXN_SIZE=2
#   CUSTOM_HOT_ACCOUNTS=10000
#   REINIT=0

PGHOST="${PGHOST:-localhost}"
PGPORT="${PGPORT:-5432}"
PGUSER="${PGUSER:-postgres}"
BENCH_DB="${BENCH_DB:-pgbench}"
SCALE="${SCALE:-50}"
DURATION="${DURATION:-60}"
CLIENTS="${CLIENTS:-1 4 8 16 32}"
JOBS="${JOBS:-4}"
WARMUP_SECONDS="${WARMUP_SECONDS:-15}"
RUN_READ_ONLY="${RUN_READ_ONLY:-1}"
RUN_CUSTOM="${RUN_CUSTOM:-0}"
CUSTOM_SQL_FILE="${CUSTOM_SQL_FILE:-pgbench_custom_workload.sql}"
CUSTOM_MODE_NAME="${CUSTOM_MODE_NAME:-custom_sql}"
CUSTOM_READ_PCT="${CUSTOM_READ_PCT:-80}"
CUSTOM_HOT_PCT="${CUSTOM_HOT_PCT:-90}"
CUSTOM_TXN_SIZE="${CUSTOM_TXN_SIZE:-2}"
CUSTOM_HOT_ACCOUNTS="${CUSTOM_HOT_ACCOUNTS:-10000}"
REINIT="${REINIT:-0}"

TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
RESULT_DIR="pgbench_results_${TIMESTAMP}"
SUMMARY_CSV="${RESULT_DIR}/summary.csv"

PSQL=(psql -X -v ON_ERROR_STOP=1 -h "$PGHOST" -p "$PGPORT" -U "$PGUSER")
PGBENCH=(pgbench -h "$PGHOST" -p "$PGPORT" -U "$PGUSER")

require_command() {
    local cmd="$1"
    if ! command -v "$cmd" >/dev/null 2>&1; then
        echo "Missing required command: $cmd" >&2
        exit 1
    fi
}

create_database_if_missing() {
    local exists
    exists="$("${PSQL[@]}" -d postgres -Atc "SELECT 1 FROM pg_database WHERE datname = '${BENCH_DB}';" || true)"
    if [[ "$exists" != "1" ]]; then
        echo "Creating benchmark database: ${BENCH_DB}"
        "${PSQL[@]}" -d postgres -c "CREATE DATABASE ${BENCH_DB};"
    fi
}

needs_init() {
    local rel
    rel="$("${PSQL[@]}" -d "$BENCH_DB" -Atc "SELECT to_regclass('public.pgbench_accounts') IS NOT NULL;")"
    [[ "$rel" != "t" ]]
}

init_or_reinit_data() {
    if [[ "$REINIT" == "1" ]] || needs_init; then
        echo "Initializing pgbench schema/data (scale=${SCALE})"
        "${PGBENCH[@]}" -i -s "$SCALE" "$BENCH_DB"
    else
        echo "Using existing pgbench data in ${BENCH_DB}"
    fi
}

extract_tps() {
    local output_file="$1"
    awk -F'= ' '/tps =/{print $2}' "$output_file" | awk '{print $1}' | tail -n 1
}

extract_avg_latency() {
    local output_file="$1"
    awk -F'= ' '/latency average =/{print $2}' "$output_file" | awk '{print $1}' | tail -n 1
}

select_percentile_ms_from_prefix() {
    local log_prefix="$1"
    local percentile="$2"
    local -a log_files
    local -a values_us
    local count rank value_us

    shopt -s nullglob
    log_files=("${log_prefix}"*)
    shopt -u nullglob

    if (( ${#log_files[@]} == 0 )); then
        echo ""
        return
    fi

    mapfile -t values_us < <(awk 'NF >= 3 && $3 ~ /^[0-9]+(\.[0-9]+)?$/ {print $3}' "${log_files[@]}" | sort -n)
    count="${#values_us[@]}"
    if (( count == 0 )); then
        echo ""
        return
    fi

    rank=$(( (percentile * count + 99) / 100 ))
    if (( rank < 1 )); then rank=1; fi
    if (( rank > count )); then rank=count; fi

    value_us="${values_us[$((rank - 1))]}"
    awk -v us="$value_us" 'BEGIN { printf "%.3f", us/1000.0 }'
}

run_case() {
    local mode="$1"
    local clients="$2"
    shift 2
    local -a extra_args=("$@")
    local log_file="${RESULT_DIR}/${mode}_c${clients}.txt"
    local tx_log_prefix="${RESULT_DIR}/${mode}_c${clients}_txlog"

    echo "Running mode=${mode}, clients=${clients}, duration=${DURATION}s"
    # Warm cache and avoid measuring first-run effects.
    "${PGBENCH[@]}" -n -M prepared -j "$JOBS" -c "$clients" -T "$WARMUP_SECONDS" "${extra_args[@]}" "$BENCH_DB" > /dev/null

    "${PGBENCH[@]}" -n -M prepared -j "$JOBS" -c "$clients" -T "$DURATION" -r -l --log-prefix="$tx_log_prefix" "${extra_args[@]}" "$BENCH_DB" > "$log_file"

    local tps latency p95 p99
    tps="$(extract_tps "$log_file")"
    latency="$(extract_avg_latency "$log_file")"
    p95="$(select_percentile_ms_from_prefix "$tx_log_prefix" 95)"
    p99="$(select_percentile_ms_from_prefix "$tx_log_prefix" 99)"

    echo "${mode},${clients},${DURATION},${JOBS},${tps},${latency},${p95},${p99},${log_file}" >> "$SUMMARY_CSV"
}

main() {
    require_command psql
    require_command pgbench

    mkdir -p "$RESULT_DIR"
    echo "mode,clients,duration_seconds,jobs,tps,latency_ms,p95_ms,p99_ms,details_file" > "$SUMMARY_CSV"

    create_database_if_missing
    init_or_reinit_data

    if [[ "$RUN_CUSTOM" == "1" ]]; then
        if [[ ! -f "$CUSTOM_SQL_FILE" ]]; then
            echo "Custom SQL file not found: $CUSTOM_SQL_FILE" >&2
            exit 1
        fi
    fi

    for c in $CLIENTS; do
        run_case "read_write" "$c"

        if [[ "$RUN_READ_ONLY" == "1" ]]; then
            run_case "read_only" "$c" "-S"
        fi

        if [[ "$RUN_CUSTOM" == "1" ]]; then
            run_case "$CUSTOM_MODE_NAME" "$c" \
                "-D" "read_pct=${CUSTOM_READ_PCT}" \
                "-D" "hot_pct=${CUSTOM_HOT_PCT}" \
                "-D" "txn_size=${CUSTOM_TXN_SIZE}" \
                "-D" "hot_accounts=${CUSTOM_HOT_ACCOUNTS}" \
                "-f" "$CUSTOM_SQL_FILE"
        fi
    done

    echo
    echo "Benchmark completed."
    echo "Summary: $SUMMARY_CSV"
    echo "Raw outputs: $RESULT_DIR"
}

main "$@"
