#!/bin/bash
#
# Copyright (C) 2025 The pgagroal community
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list
# of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may
# be used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
set -eo pipefail

# Variables
ENV_PGVERSION="${TEST_PG_VERSION:-17}"
export TEST_PG_VERSION="${TEST_PG_VERSION:-17}"
IMAGE_NAME="pgagroal-test-postgresql${ENV_PGVERSION}-rocky10"
CONTAINER_NAME="pgagroal-test-postgresql${ENV_PGVERSION}"

SCRIPT_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
PROJECT_DIRECTORY=$(realpath "$SCRIPT_DIR/..")
EXECUTABLE_DIRECTORY=$PROJECT_DIRECTORY/build/src
TEST_DIRECTORY=$PROJECT_DIRECTORY/build/test
TEST_PG_DIRECTORY=$PROJECT_DIRECTORY/test/postgresql/src/postgresql${ENV_PGVERSION}

PGAGROAL_ROOT_DIR="/tmp/pgagroal-test"
BASE_DIR="$PROJECT_DIRECTORY/pgagroal-testsuite"
COVERAGE_DIR="$PGAGROAL_ROOT_DIR/coverage"
LOG_DIR="$PGAGROAL_ROOT_DIR/log"
PG_LOG_DIR="$PGAGROAL_ROOT_DIR/pg_log"

# BASE DIR holds all the run time data
CONFIGURATION_DIRECTORY=$BASE_DIR/conf
RESOURCE_DIRECTORY=$BASE_DIR/resource

PG_DATABASE=mydb
PG_USER_NAME=myuser
PG_USER_PASSWORD=yourpassword
PG_REPL_USER_NAME=repl
PG_REPL_PASSWORD=replpass
PG_UTF8_USER_NAME=utf8user
PG_UTF8_USER_PASSWORD=HelloПриветمرحبا你好नमस्तेสวัสดีこんにちは안녕하세요𠜎𡃁𩷶
PG_UTF8_DATABASE=utf8db
PGAGROAL_PORT=2345
USER=$(whoami)
MODE="dev"
PORT=6432

# Ports for failover cluster
PRIMARY_PORT=7432
STANDBY1_PORT=7433
STANDBY2_PORT=7434

# Use existing PGAGROAL_PORT if already set, otherwise default
PGAGROAL_PORT=${PGAGROAL_PORT:-6432}

# Use sudo only when not running as root (CI containers run as root)
if [ "$(id -u)" -eq 0 ]; then
  SUDO=""
else
  SUDO="sudo"
fi

# Detect container engine: Docker or Podman
# Called lazily since CI mode does not need containers
detect_container_engine() {
  if command -v podman &> /dev/null; then
    CONTAINER_ENGINE="podman"
  elif command -v docker &> /dev/null; then
    CONTAINER_ENGINE="$SUDO docker"
  else
    echo "Neither Docker nor Podman is installed. Please install one to proceed."
    exit 1
  fi
}


 # Port conflict resolution functions
stop_pgagroal() {
   echo "Stopping pgagroal instance..."
   set +e
   # Use the correct PID file path with port number
   PID_FILE="/tmp/pgagroal.${PGAGROAL_PORT}.pid"
   # First try graceful shutdown
   if [[ -f "$CONFIGURATION_DIRECTORY/pgagroal.conf" ]]; then
     $EXECUTABLE_DIRECTORY/pgagroal-cli -c $CONFIGURATION_DIRECTORY/pgagroal.conf shutdown 2>/dev/null
     sleep 3
   fi
   # Check if pgagroal is still running and force kill if necessary
   if pgrep pgagroal > /dev/null; then
     echo "pgagroal still running, force stopping..."
     kill -9 $(pgrep pgagroal) 2>/dev/null
     sleep 2
   fi
   # Clean up PID files
   rm -f /tmp/pgagroal.*.pid 2>/dev/null || true
   echo "pgagroal stop completed"
   set -e
}
is_port_in_use() {
    local port=$1
    if command -v ss &> /dev/null; then
        ss -tuln | grep ":$port " >/dev/null 2>&1
    elif command -v lsof &> /dev/null; then
        lsof -i:$port >/dev/null 2>&1
    elif command -v netstat &> /dev/null; then
        netstat -tuln | grep ":$port " >/dev/null 2>&1
    else
        # Fallback: try to bind to the port
        (echo >/dev/tcp/localhost/$port) >/dev/null 2>&1
    fi
    return $?
}

next_available_port() {
    local port=$1
    while true; do
        is_port_in_use $port
        if [ $? -ne 0 ]; then
            echo "$port"
            return 0
        else
            echo "Port $port is in use, trying next port..." >&2
            port=$((port + 1))
        fi
    done
}

# Use environment variable if set, otherwise find next available port
if [ -n "$PGAGROAL_TEST_PORT" ]; then
    PORT=$PGAGROAL_TEST_PORT
    echo "Using specified port: $PORT"
else
    PORT=$(next_available_port $PORT)
    echo "Using next available port: $PORT"
fi
echo "Container port is set to: $PORT"

cleanup() {
   echo "Clean up"
   set +e
   echo "Shutdown pgagroal"
   
   # Use the correct PID file path with port number
   PID_FILE="/tmp/pgagroal.${PGAGROAL_PORT}.pid"
   
   # First try graceful shutdown
   if [[ -f "$CONFIGURATION_DIRECTORY/pgagroal.conf" ]]; then
     $EXECUTABLE_DIRECTORY/pgagroal-cli -c $CONFIGURATION_DIRECTORY/pgagroal.conf shutdown 2>/dev/null
     sleep 3
   fi
   
   # Check if pgagroal is still running and force kill if necessary
   if pgrep pgagroal > /dev/null; then
     echo "pgagroal still running, force stopping..."
     kill -9 $(pgrep pgagroal) 2>/dev/null
     sleep 2
   fi
   
   # Clean up PID files
   rm -f /tmp/pgagroal.*.pid 2>/dev/null || true
   
   echo "pgagroal cleanup completed"

   echo "Clean Test Resources"
   if [[ -d $PGAGROAL_ROOT_DIR ]]; then
      if [[ -d $BASE_DIR ]]; then
        rm -Rf "$BASE_DIR"
      fi
      
      # Generate LLVM coverage reports
      if [[ $COVERAGE == "YES" ]]; then
         if ls "$COVERAGE_DIR"/*-*.profraw >/dev/null 2>&1; then
          echo "Generating coverage report, expect error when the binary is not covered at all"
          llvm-profdata merge -sparse $COVERAGE_DIR/*-*.profraw -o $COVERAGE_DIR/coverage.profdata
  
         if [[ -f "$EXECUTABLE_DIRECTORY/libpgagroal.so" ]]; then
             BIN_PATH="$EXECUTABLE_DIRECTORY"
         elif [[ -f "$EXECUTABLE_DIRECTORY/pgagroal/libpgagroal.so" ]]; then
             BIN_PATH="$EXECUTABLE_DIRECTORY/pgagroal"
         else
             echo "ERROR: Could not find libpgagroal.so in $EXECUTABLE_DIRECTORY or subdirectory."
             ls -R "$EXECUTABLE_DIRECTORY" # Debug: list files to see where they are
             exit 1
         fi

          echo "Generating $COVERAGE_DIR/coverage-report-libpgagroal.txt"
          llvm-cov report "$BIN_PATH/libpgagroal.so" \
            --instr-profile=$COVERAGE_DIR/coverage.profdata \
            --format=text > $COVERAGE_DIR/coverage-report-libpgagroal.txt 2>&1
          echo "Generating $COVERAGE_DIR/coverage-report-pgagroal.txt"
          llvm-cov report "$BIN_PATH/pgagroal" \
            --instr-profile=$COVERAGE_DIR/coverage.profdata \
            --format=text > $COVERAGE_DIR/coverage-report-pgagroal.txt 2>&1
         echo "Generating $COVERAGE_DIR/coverage-report-pgagroal-cli.txt"
         llvm-cov report "$BIN_PATH/pgagroal-cli" \
            --instr-profile=$COVERAGE_DIR/coverage.profdata \
            --format=text > $COVERAGE_DIR/coverage-report-pgagroal-cli.txt 2>&1
         echo "Generating $COVERAGE_DIR/coverage-report-pgagroal-admin.txt"
         llvm-cov report "$BIN_PATH/pgagroal-admin" \
            --instr-profile=$COVERAGE_DIR/coverage.profdata \
            --format=text > $COVERAGE_DIR/coverage-report-pgagroal-admin.txt 2>&1

          echo "Generating $COVERAGE_DIR/coverage-libpgagroal.txt"
          llvm-cov show $BIN_PATH/libpgagroal.so \
            --instr-profile=$COVERAGE_DIR/coverage.profdata \
            --format=text > $COVERAGE_DIR/coverage-libpgagroal.txt
          echo "Generating $COVERAGE_DIR/coverage-pgagroal.txt"
          llvm-cov show $BIN_PATH/pgagroal \
            --instr-profile=$COVERAGE_DIR/coverage.profdata \
            --format=text > $COVERAGE_DIR/coverage-pgagroal.txt
         echo "Generating $COVERAGE_DIR/coverage-pgagroal-cli.txt"
         llvm-cov show $BIN_PATH/pgagroal-cli \
            --instr-profile=$COVERAGE_DIR/coverage.profdata \
            --format=text > $COVERAGE_DIR/coverage-pgagroal-cli.txt
         echo "Generating $COVERAGE_DIR/coverage-pgagroal-admin.txt"
         llvm-cov show $BIN_PATH/pgagroal-admin \
            --instr-profile=$COVERAGE_DIR/coverage.profdata \
            --format=text > $COVERAGE_DIR/coverage-pgagroal-admin.txt
         
          echo "Coverage --> $COVERAGE_DIR"
       fi
    fi
      
      echo "Logs --> $LOG_DIR, $PG_LOG_DIR"
   else
     echo "$PGAGROAL_ROOT_DIR not present ... ok"
   fi

   if [[ $MODE != "ci" ]]; then
  echo "Removing postgres $ENV_PGVERSION container"
    remove_postgresql_container
   fi

   echo "Unsetting environment variables"
   unset_pgagroal_test_variables

   set -e
}

build_postgresql_image() {
  echo "Building the PostgreSQL $ENV_PGVERSION image $IMAGE_NAME"
  CUR_DIR=$(pwd)
  cd $TEST_PG_DIRECTORY
  set +e
  make clean
  set -e
  make build
  cd $CUR_DIR
}

cleanup_postgresql_image() {
  set +e
  echo "Cleanup of the PostgreSQL $ENV_PGVERSION image $IMAGE_NAME"
  CUR_DIR=$(pwd)
  cd $TEST_PG_DIRECTORY
  make clean
  cd $CUR_DIR
  set -e
}

start_postgresql_container() {
  # Remove any existing container with the same name before starting
  $CONTAINER_ENGINE stop $CONTAINER_NAME 2>/dev/null || true
  $CONTAINER_ENGINE rm -f $CONTAINER_NAME 2>/dev/null || true

  $CONTAINER_ENGINE run -p $PORT:5432 -v "$PG_LOG_DIR:/pglog:z" \
  --name $CONTAINER_NAME -d \
  -e PG_DATABASE=$PG_DATABASE \
  -e PG_USER_NAME=$PG_USER_NAME \
  -e PG_USER_PASSWORD=$PG_USER_PASSWORD \
  -e PG_REPL_USER_NAME=$PG_REPL_USER_NAME \
  -e PG_REPL_PASSWORD=$PG_REPL_PASSWORD \
  -e PG_UTF8_USER_NAME=$PG_UTF8_USER_NAME \
  -e PG_UTF8_USER_PASSWORD=$PG_UTF8_USER_PASSWORD \
  -e PG_UTF8_DATABASE=$PG_UTF8_DATABASE \
  -e PG_LOG_LEVEL=debug5 \
  $IMAGE_NAME

  echo "Checking PostgreSQL $ENV_PGVERSION container readiness"
  sleep 3
  for attempt in {1..6}; do
    if $CONTAINER_ENGINE exec $CONTAINER_NAME /usr/pgsql-$ENV_PGVERSION/bin/pg_isready -h localhost -p 5432 >/dev/null 2>&1; then
      break
    fi
    echo "PostgreSQL $ENV_PGVERSION not ready yet (attempt $attempt/6). Waiting 30 seconds before retry..."
    sleep 30
  done

  # Final readiness check; if still not ready after retries, fail
  if $CONTAINER_ENGINE exec $CONTAINER_NAME /usr/pgsql-$ENV_PGVERSION/bin/pg_isready -h localhost -p 5432 >/dev/null 2>&1; then
    echo "PostgreSQL $ENV_PGVERSION is ready!"
  else
    echo "Printing container logs..."
    $CONTAINER_ENGINE logs $CONTAINER_NAME
    echo ""
    echo "PostgreSQL $ENV_PGVERSION is not ready after 3 minutes, exiting"
    cleanup_postgresql_image
    exit 1
  fi
}

remove_postgresql_container() {
  $CONTAINER_ENGINE stop $CONTAINER_NAME 2>/dev/null || true
  $CONTAINER_ENGINE rm -f $CONTAINER_NAME 2>/dev/null || true
}

start_postgresql() {
  echo "Setting up PostgreSQL $ENV_PGVERSION directory"
  set +e
  $SUDO rm -Rf /conf /pgconf /pgdata /pgwal
  $SUDO cp -R $TEST_PG_DIRECTORY/root /
  $SUDO ls /root
  $SUDO mkdir -p /conf /pgconf /pgdata /pgwal /pglog

  $SUDO cp -R $TEST_PG_DIRECTORY/conf/* /conf/
  $SUDO ls /conf
  $SUDO chown -R postgres:postgres /conf /pgconf /pgdata /pgwal /pglog
  $SUDO chmod -R 777 /conf /pgconf /pgdata /pgwal /pglog /root
  $SUDO chmod +x /root/usr/bin/run-postgresql-local
  $SUDO mkdir -p /root/usr/local/bin

  echo "Setting up env variables"
  export PG_DATABASE=${PG_DATABASE}
  export PG_USER_NAME=${PG_USER_NAME}
  export PG_USER_PASSWORD=${PG_USER_PASSWORD}
  export PG_REPL_USER_NAME=${PG_REPL_USER_NAME}
  export PG_REPL_PASSWORD=${PG_REPL_PASSWORD}
  export PG_UTF8_USER_NAME=${PG_UTF8_USER_NAME}
  export PG_UTF8_USER_PASSWORD=${PG_UTF8_USER_PASSWORD}
  export PG_UTF8_DATABASE=${PG_UTF8_DATABASE}

  if [ "$(id -u)" -eq 0 ]; then
    runuser -m -u postgres -- /root/usr/bin/run-postgresql-local
  else
    sudo -E -u postgres /root/usr/bin/run-postgresql-local
  fi
  set -e
}

pgagroal_initialize_configuration() {
   touch $CONFIGURATION_DIRECTORY/pgagroal.conf $CONFIGURATION_DIRECTORY/pgagroal_hba.conf $CONFIGURATION_DIRECTORY/pgagroal_users.conf $CONFIGURATION_DIRECTORY/pgagroal_databases.conf $CONFIGURATION_DIRECTORY/pgagroal_frontend_users.conf
   echo "Creating pgagroal configuration files inside $CONFIGURATION_DIRECTORY ... ok"
   
   # Create pgagroal.conf
   cat <<EOF >$CONFIGURATION_DIRECTORY/pgagroal.conf
[pgagroal]
host = localhost
port = $PGAGROAL_PORT

log_type = file
log_level = debug5
log_path = $LOG_DIR/pgagroal.log

max_connections = 8
idle_timeout = 600
validation = off
unix_socket_dir = /tmp/
pipeline = 'performance'

[primary]
host = localhost
port = $PORT
EOF

   # Create HBA configuration
   cat <<EOF >$CONFIGURATION_DIRECTORY/pgagroal_hba.conf
host    all all all trust
EOF

   # Create database aliases configuration
   cat <<EOF >$CONFIGURATION_DIRECTORY/pgagroal_databases.conf
#
# DATABASE=ALIAS1,ALIAS2 USER MAX_SIZE INITIAL_SIZE MIN_SIZE
#
$PG_DATABASE=pgalias1,pgalias2 $PG_USER_NAME 6 6 1
$PG_UTF8_DATABASE $PG_UTF8_USER_NAME 2 2 1

EOF

   echo "Add test configuration to pgagroal.conf ... ok"
   
   # Create master key if it doesn't exist
   if [[ ! -e $HOME/.pgagroal/master.key ]]; then
     $EXECUTABLE_DIRECTORY/pgagroal-admin master-key -P $PG_USER_PASSWORD
   fi
   
   # Add PostgreSQL test user
   $EXECUTABLE_DIRECTORY/pgagroal-admin -f $CONFIGURATION_DIRECTORY/pgagroal_users.conf -U $PG_USER_NAME -P $PG_USER_PASSWORD user add
   echo "Add user $PG_USER_NAME to pgagroal_users.conf file ... ok"
   
   # Add system user for test executable compatibility
  $EXECUTABLE_DIRECTORY/pgagroal-admin -f $CONFIGURATION_DIRECTORY/pgagroal_users.conf -U $USER -P $PG_USER_PASSWORD user add
  echo "Add user $USER to pgagroal_users.conf file ... ok"

  # Add UTF-8 test user
  $EXECUTABLE_DIRECTORY/pgagroal-admin -f $CONFIGURATION_DIRECTORY/pgagroal_users.conf -U "$PG_UTF8_USER_NAME" -P "$PG_UTF8_USER_PASSWORD" user add
  echo "Add UTF-8 user $PG_UTF8_USER_NAME to pgagroal_users.conf file ... ok"
   
   # Create empty frontend users configuration to avoid default path conflicts
   cat <<EOF >$CONFIGURATION_DIRECTORY/pgagroal_frontend_users.conf
# pgagroal frontend users configuration for testing
# This file prevents pgagroal from reading default system frontend users
EOF
   echo "Created pgagroal_frontend_users.conf file ... ok"
   echo ""
}

export_pgagroal_test_variables() {
  echo "export PGAGROAL_TEST_BASE_DIR=$BASE_DIR"
  export PGAGROAL_TEST_BASE_DIR=$BASE_DIR

  echo "export PGAGROAL_TEST_CONF=$CONFIGURATION_DIRECTORY/pgagroal.conf"
  export PGAGROAL_TEST_CONF=$CONFIGURATION_DIRECTORY/pgagroal.conf

  echo "export PGPASSWORD=$PG_USER_PASSWORD"
  export PGPASSWORD=$PG_USER_PASSWORD
}

unset_pgagroal_test_variables() {
  unset PGAGROAL_TEST_BASE_DIR
  unset PGAGROAL_TEST_CONF
  unset PGPASSWORD
  unset LLVM_PROFILE_FILE
  unset CC
}

# Returns 0 if setup is already done, 1 if setup is needed.
need_build() {
  # Require test runner and server binary 
  if [[ ! -f "$TEST_DIRECTORY/pgagroal_test" ]] || [[ ! -f "$EXECUTABLE_DIRECTORY/pgagroal" ]]; then
    return 1
  fi
  if [[ $MODE != "ci" ]]; then
    if ! $CONTAINER_ENGINE image inspect "$IMAGE_NAME" >/dev/null 2>&1; then
      return 1
    fi
  fi
  if [[ ! -d "$PGAGROAL_ROOT_DIR" ]] || [[ ! -f "$CONFIGURATION_DIRECTORY/pgagroal.conf" ]]; then
    return 1
  fi
  return 0
}

execute_testcases() {
   local config_name="${1:-default}"
   echo "Execute MCTF Testcases"
   set +e
   
   if pgrep -f pgagroal >/dev/null 2>&1 || [[ -f "/tmp/pgagroal.${PGAGROAL_PORT}.pid" ]]; then
      echo "Clean up any existing pgagroal processes"
      if [[ -f "/tmp/pgagroal.${PGAGROAL_PORT}.pid" ]]; then
         $EXECUTABLE_DIRECTORY/pgagroal-cli -c $CONFIGURATION_DIRECTORY/pgagroal.conf shutdown 2>/dev/null || true
         sleep 3
      fi
      if pgrep -f pgagroal >/dev/null 2>&1; then
         echo "Killing existing pgagroal processes"
         pkill -9 -f "${EXECUTABLE_DIRECTORY}/pgagroal " || true
         pkill -9 -f "${EXECUTABLE_DIRECTORY}/pgagroal-cli " || true
         sleep 2
      fi
      rm -f "/tmp/pgagroal.${PGAGROAL_PORT}.pid"
      
      echo "Cleaning up shared memory segments"
      ipcs -m 2>/dev/null | awk '/^0x0/ {print $2}' | xargs -r -I {} ipcrm -m {} 2>/dev/null || true
      sleep 1
   fi
   
   echo "Starting pgagroal server in daemon mode"
   $EXECUTABLE_DIRECTORY/pgagroal -c $CONFIGURATION_DIRECTORY/pgagroal.conf -a $CONFIGURATION_DIRECTORY/pgagroal_hba.conf -u $CONFIGURATION_DIRECTORY/pgagroal_users.conf -l $CONFIGURATION_DIRECTORY/pgagroal_databases.conf -F $CONFIGURATION_DIRECTORY/pgagroal_frontend_users.conf -d 2>$LOG_DIR/pgagroal-startup-stderr.log
   echo "Wait for pgagroal to be ready"
   sleep 10
   
   for i in {1..5}; do
      if [[ -f "/tmp/pgagroal.${PGAGROAL_PORT}.pid" ]]; then
         if $EXECUTABLE_DIRECTORY/pgagroal-cli -c $CONFIGURATION_DIRECTORY/pgagroal.conf status details >/dev/null 2>&1; then
            echo "pgagroal server started ... ok"
            break
         fi
      fi
      if [[ $i -eq 5 ]]; then
         echo "pgagroal server not started ... not ok"
         echo "Checking logs:"
         tail -20 $LOG_DIR/pgagroal.log 2>/dev/null || echo "Log file not found"
         echo "Checking startup stderr:"
         cat $LOG_DIR/pgagroal-startup-stderr.log 2>/dev/null || echo "No stderr output"
         echo "Checking pgagroal config used:"
         cat $CONFIGURATION_DIRECTORY/pgagroal.conf 2>/dev/null || echo "Config not found"
         exit 1
      fi
      sleep 2
   done

   echo "Start running MCTF tests"
   if [[ -f "$TEST_DIRECTORY/pgagroal_test" ]]; then
      TEST_FILTER_ARGS=()
      if [[ -n "${TEST_FILTER:-}" ]]; then
         TEST_FILTER_ARGS=(-t "$TEST_FILTER")
      elif [[ -n "${MODULE_FILTER:-}" ]]; then
         TEST_FILTER_ARGS=(-m "$MODULE_FILTER")
      fi
      $TEST_DIRECTORY/pgagroal_test "${TEST_FILTER_ARGS[@]}" $PROJECT_DIRECTORY $PG_USER_NAME $PG_DATABASE
      if [[ $? -ne 0 ]]; then
         exit 1
      fi
   else
      echo "Test binary not found: $TEST_DIRECTORY/pgagroal_test"
      echo "Please build the project first"
      exit 1
   fi
   set -e
}

run_multiple_config_tests() {
   echo "Running tests on multiple pgagroal configurations"
   
   TEST_CONFIG_DIRECTORY="$PROJECT_DIRECTORY/test/conf"
   
   if [ -d "$TEST_CONFIG_DIRECTORY" ]; then
      for entry in "$TEST_CONFIG_DIRECTORY"/*; do
         entry=$(realpath "$entry")
         config_name=$(basename "$entry")
         
         if [[ -d "$entry" && -f "$entry/pgagroal.conf" && -f "$entry/pgagroal_hba.conf" ]]; then
            echo ""
            echo "=========================================="
            echo "Testing configuration: $config_name"
            echo "=========================================="
            
            # Backup current configuration
            cp "$CONFIGURATION_DIRECTORY/pgagroal.conf" "$CONFIGURATION_DIRECTORY/pgagroal.conf.backup"
            cp "$CONFIGURATION_DIRECTORY/pgagroal_hba.conf" "$CONFIGURATION_DIRECTORY/pgagroal_hba.conf.backup"
            cp "$CONFIGURATION_DIRECTORY/pgagroal_databases.conf" "$CONFIGURATION_DIRECTORY/pgagroal_databases.conf.backup"

            # Copy test configuration
            cp "$entry/pgagroal.conf" "$CONFIGURATION_DIRECTORY/pgagroal.conf"
            cp "$entry/pgagroal_hba.conf" "$CONFIGURATION_DIRECTORY/pgagroal_hba.conf"
            if [ -f "$entry/pgagroal_databases.conf" ]; then
               cp "$entry/pgagroal_databases.conf" "$CONFIGURATION_DIRECTORY/pgagroal_databases.conf"
            fi
            
            # Update log path in the configuration to use our log directory
            sed -i "s|log_path = test.log|log_path = $LOG_DIR/pgagroal-$config_name.log|g" "$CONFIGURATION_DIRECTORY/pgagroal.conf"
            
            # Update port to match our PostgreSQL container
            sed -i "s|port = 5432|port = $PORT|g" "$CONFIGURATION_DIRECTORY/pgagroal.conf"
            
            # Stop any running pgagroal instance before starting new config
            stop_pgagroal
            # Run tests for this configuration
            execute_testcases "$config_name"
            # Stop after test in case test leaves it running
            stop_pgagroal
            
            # Restore original configuration
            cp "$CONFIGURATION_DIRECTORY/pgagroal.conf.backup" "$CONFIGURATION_DIRECTORY/pgagroal.conf"
            cp "$CONFIGURATION_DIRECTORY/pgagroal_hba.conf.backup" "$CONFIGURATION_DIRECTORY/pgagroal_hba.conf"
            cp "$CONFIGURATION_DIRECTORY/pgagroal_databases.conf.backup" "$CONFIGURATION_DIRECTORY/pgagroal_databases.conf"
            
            echo "Configuration $config_name: PASSED"
         else
            echo "Warning: Configuration directory '$entry' is missing required files"
            echo "  Required: pgagroal.conf and pgagroal_hba.conf"
         fi
      done
      
      # Clean up backup files
      rm -f "$CONFIGURATION_DIRECTORY/pgagroal.conf.backup"
      rm -f "$CONFIGURATION_DIRECTORY/pgagroal_hba.conf.backup"
      rm -f "$CONFIGURATION_DIRECTORY/pgagroal_databases.conf.backup"
      
      echo ""
      echo "=========================================="
      echo "All configuration tests completed!"
      echo "=========================================="
   else
      echo "Configuration directory $TEST_CONFIG_DIRECTORY not present"
      exit 1
   fi
}

usage() {
  echo "Usage: $0 [options] [sub-command]"
  echo "Subcommands:"
  echo "  build                  Set up environment (image, build, PostgreSQL, pgagroal) without running tests"
  echo "  setup                  Install dependencies and build PostgreSQL image (one-time setup)"
  echo "  clean                  Clean up test suite environment and remove PostgreSQL image"
  echo "  run-configs            Run the testsuite on multiple pgagroal configurations (containerized)"
  echo "  failover-tests         Run failover notify script tests" 
  echo "  ci                     Run in CI mode (local PostgreSQL, no container)"
  echo "  run-configs-ci         Run multiple configuration tests using local PostgreSQL (like ci + run-configs)"
  echo "  ci-nonbuild            Run in CI mode (local PostgreSQL, skip build step)"
  echo "  run-configs-ci-nonbuild Run multiple configuration tests using local PostgreSQL, skip build step"
  echo "  (no sub-command)       Default: run all tests in containerized mode"
  echo "  gcc                    Compile with gcc and no-coverage"
  echo ""
  echo "Options (run tests with optional filter; default is full suite):"
  echo "  -t, --test NAME        Run only tests matching NAME"
  echo "  -m, --module NAME      Run all tests in module NAME"
  echo ""
  echo "Examples:"
  echo "  $0                      Run full test suite"
  echo "  $0 build                Set up environment only; then run e.g. $0 -t connection"
  echo "  $0 -t connection        Run test matching 'connection'"
  echo "  $0 -m connection        Run all tests in module 'connection'"
  exit 1
}

failover_container_name() {
  echo "pgagroal-test-postgresql${ENV_PGVERSION}-$1"
}

failover_prepare_dirs() {
  local FAILOVER_BASE="$1"
  
  mkdir -p "$FAILOVER_BASE/log"
  mkdir -p "$FAILOVER_BASE/conf"
  mkdir -p "$FAILOVER_BASE/pglog"/{primary,standby1,standby2}

  POSTGRES_UID=$($CONTAINER_ENGINE run --rm $IMAGE_NAME id -u postgres 2>/dev/null || echo "26")

  for dir in "$FAILOVER_BASE/pglog" "$FAILOVER_BASE/pglog/primary" "$FAILOVER_BASE/pglog/standby1" "$FAILOVER_BASE/pglog/standby2"; do
    if [ -d "$dir" ]; then
      $CONTAINER_ENGINE unshare chown -R ${POSTGRES_UID}:${POSTGRES_UID} "$dir" 2>/dev/null || \
      $SUDO chown -R ${POSTGRES_UID}:${POSTGRES_UID} "$dir" 2>/dev/null || \
      echo "Warning: Could not change ownership of $dir"
    fi
  done
}

failover_start_container() {

  local name=$1
  local host_port=$2
  local container_port=$3
  local FAILOVER_PG_LOG_DIR=$4

  local cname
  cname=$(failover_container_name "$name")

  $CONTAINER_ENGINE rm -f "$cname" >/dev/null 2>&1 || true

  $CONTAINER_ENGINE run -d \
    --name "$cname" \
    -p ${host_port}:${container_port} \
    -v "$FAILOVER_PG_LOG_DIR/$name:/pglog:Z" \
    -e PG_DATABASE="mydb" \
    -e PG_USER_NAME="myuser" \
    -e PG_USER_PASSWORD="yourpassword" \
    -e PG_REPL_USER_NAME="myrepl" \
    -e PG_REPL_PASSWORD="replpass" \
    -e PG_UTF8_USER_NAME="utf8user" \
    -e PG_UTF8_USER_PASSWORD="yourpassword" \
    -e PG_UTF8_DATABASE="myutf8db" \
    -e PG_LOG_LEVEL=debug5 \
    $IMAGE_NAME
}

failover_wait_ready() {

  local cname=$1

  for attempt in {1..20}; do
    if $CONTAINER_ENGINE exec "$cname" \
      /usr/pgsql-${ENV_PGVERSION}/bin/pg_isready -h localhost -p 5432 \
      >/dev/null 2>&1
    then
      echo "$cname ready"
      return
    fi
    sleep 2
  done

  echo "$cname failed to start"
  $CONTAINER_ENGINE logs "$cname"
  exit 1
}

failover_start_postgresql_cluster() {
  local FAILOVER_PG_LOG_DIR=$1

  echo ""
  echo "Starting PostgreSQL cluster..."

  failover_start_container primary $PRIMARY_PORT 5432 "$FAILOVER_PG_LOG_DIR"
  failover_start_container standby1 $STANDBY1_PORT 5433 "$FAILOVER_PG_LOG_DIR"
  failover_start_container standby2 $STANDBY2_PORT 5434 "$FAILOVER_PG_LOG_DIR"

  failover_wait_ready "$(failover_container_name primary)"
  failover_wait_ready "$(failover_container_name standby1)"
  failover_wait_ready "$(failover_container_name standby2)"
}

failover_stop_postgresql_cluster() {

  echo "Stopping PostgreSQL cluster"

  for name in primary standby1 standby2
  do
    $CONTAINER_ENGINE rm -f "$(failover_container_name $name)" \
      >/dev/null 2>&1 || true
  done
}

failover_create_script() {

  local script_path=$1
  local exit_code=$2
  local log_file=$3

  cat > "$script_path" <<EOF
#!/bin/bash
echo "\$(date): Script called with \$# arguments" >> $log_file
echo "\$(date): Args: \$@" >> $log_file
exit $exit_code
EOF

  chmod +x "$script_path"
}

failover_create_config() {

  local notify_script=$1
  local FAILOVER_CONF_DIR=$2
  local FAILOVER_LOG_DIR=$3
  local failover_script="/tmp/failover.sh"

  failover_create_script "$failover_script" 0 "/tmp/failover_test.log"

  cat > "$FAILOVER_CONF_DIR/pgagroal.conf" <<EOF
[pgagroal]
host = *
port = $PGAGROAL_PORT

log_type = file
log_level = debug
log_path = $FAILOVER_LOG_DIR/pgagroal.log

failover = on
failover_script = $failover_script
failover_notify_script = $notify_script

unix_socket_dir = /tmp/

[primary]
host = localhost
port = 7432

[standby1]
host = localhost
port = 7433

[standby2]
host = localhost
port = 7434
EOF

  cat > "$FAILOVER_CONF_DIR/pgagroal_hba.conf" <<EOF
host all all all all
EOF
}

failover_stop_pgagroal() {

  pkill -f pgagroal >/dev/null 2>&1 || true
  sleep 2
  pkill -9 -f pgagroal >/dev/null 2>&1 || true
  rm -f /tmp/pgagroal.*.pid
}

test_failover_notify_success() {

  local FAILOVER_BASE=$1
  local FAILOVER_LOG_DIR="$FAILOVER_BASE/log"
  local FAILOVER_CONF_DIR="$FAILOVER_BASE/conf"
  local FAILOVER_PG_LOG_DIR="$FAILOVER_BASE/pglog"

  echo ""
  echo "=========================================="
  echo "Test 1: Failover Notify Success"
  echo "=========================================="

  local notify_script="/tmp/test_notify_success.sh"
  local log_file="/tmp/failover_notify_test.log"

  rm -f "$notify_script" "$log_file"

  failover_create_script "$notify_script" 0 "$log_file"

  failover_start_postgresql_cluster "$FAILOVER_PG_LOG_DIR"  

  failover_create_config "$notify_script" "$FAILOVER_CONF_DIR" "$FAILOVER_LOG_DIR"

  echo "Starting pgagroal..."

  $EXECUTABLE_DIRECTORY/pgagroal \
    -c "$FAILOVER_CONF_DIR/pgagroal.conf" \
    -a "$FAILOVER_CONF_DIR/pgagroal_hba.conf" \
    -d

  sleep 5

  echo "Testing connection"

  PGPASSWORD="yourpassword" \
  timeout 5 psql -h localhost -p $PGAGROAL_PORT \
  -U myuser -d mydb \
  -c "SELECT 1" >/dev/null 2>&1 || true

  echo ""
  echo "Simulating primary failure"

  $CONTAINER_ENGINE stop "$(failover_container_name primary)"

  PGPASSWORD="yourpassword" \
  timeout 5 psql -h localhost -p $PGAGROAL_PORT \
  -U myuser -d mydb \
  -c "CREATE TABLE failover_test(id int);" \
  >/dev/null 2>&1 || true

  sleep 15

  if [ -f "$log_file" ]; then
    echo "Notify script executed"
    cat "$log_file"
  else
    echo "Notify script not executed"
    tail -20 "$FAILOVER_LOG_DIR/pgagroal.log"
  fi

  failover_stop_pgagroal
  failover_stop_postgresql_cluster
}

test_failover_notify_failure() {
  local FAILOVER_BASE=$1
  local FAILOVER_LOG_DIR="$FAILOVER_BASE/log"
  local FAILOVER_CONF_DIR="$FAILOVER_BASE/conf"
  local FAILOVER_PG_LOG_DIR="$FAILOVER_BASE/pglog"

  echo ""
  echo "=========================================="
  echo "Test 2: Failover Notify Failure"
  echo "=========================================="

  local notify_script="/tmp/test_notify_fail.sh"
  local log_file="/tmp/failover_notify_test.log"

  rm -f "$notify_script" "$log_file"

  failover_create_script "$notify_script" 1 "$log_file"

  failover_start_postgresql_cluster "$FAILOVER_PG_LOG_DIR"

  failover_create_config "$notify_script" "$FAILOVER_CONF_DIR" "$FAILOVER_LOG_DIR"

  $EXECUTABLE_DIRECTORY/pgagroal \
    -c "$FAILOVER_CONF_DIR/pgagroal.conf" \
    -a "$FAILOVER_CONF_DIR/pgagroal_hba.conf" \
    -d

  sleep 5

  $CONTAINER_ENGINE stop "$(failover_container_name primary)"

  PGPASSWORD="yourpassword" \
  timeout 5 psql -h localhost -p $PGAGROAL_PORT \
  -U myuser -d mydb \
  -c "CREATE TABLE failover_test(id int);" \
  >/dev/null 2>&1 || true

  sleep 15

  if [ -f "$log_file" ]; then
    echo "Notify script executed"
    cat "$log_file"
  fi

  if grep -q "Error from notify script" "$LOG_DIR/pgagroal.log"; then
    echo "Error correctly logged"
  fi

  failover_stop_pgagroal
  failover_stop_postgresql_cluster
}

failover_cleanup() {
  local FAILOVER_BASE=$1

  echo ""
  echo "=========================================="
  echo "Cleaning up"
  echo "=========================================="

  set +e 
  
  for name in primary standby1 standby2
  do
    cname="pgagroal-test-postgresql${ENV_PGVERSION}-$name"
    $CONTAINER_ENGINE stop "$cname" >/dev/null 2>&1 || true
    $CONTAINER_ENGINE rm -f "$cname" >/dev/null 2>&1 || true
  done

  rm -f "$HOME/.pgagroal/master.key" 2>/dev/null || true
  rm -f /tmp/pgagroal.*.key 2>/dev/null || true

  pkill -9 -f pgagroal >/dev/null 2>&1 || true
  sleep 2

  rm -f /tmp/test_notify_*.sh 2>/dev/null || true
  rm -f /tmp/failover*.sh 2>/dev/null || true
  rm -f /tmp/failover_notify_test.log 2>/dev/null || true
  rm -f /tmp/failover_test.log 2>/dev/null || true
  rm -f /tmp/pgagroal.*.pid 2>/dev/null || true

  if [ -d "$FAILOVER_BASE" ]; then
    rm -rf "$FAILOVER_BASE" 2>/dev/null || {
      echo "Warning: Could not remove $FAILOVER_BASE, trying with sudo..."
      $SUDO rm -rf "$FAILOVER_BASE" 2>/dev/null || true
    }
  fi
  
  set -e
}


run_failover_tests() {

  local FAILOVER_BASE="/tmp/pgagroal-failover-test"

  echo "Checking environment before starting failover tests..."

  set +e

  # Check required ports
  for port in $PRIMARY_PORT $STANDBY1_PORT $STANDBY2_PORT $PGAGROAL_PORT
  do
    if ss -tuln | grep -q ":$port "; then
      echo "WARNING: Port $port is already in use. Attempting to continue..."
    fi
  done

  # Clean up any existing containers first
  echo "Cleaning up any existing containers..."
  for name in primary standby1 standby2
  do
    cname="pgagroal-test-postgresql${ENV_PGVERSION}-$name"
    $CONTAINER_ENGINE stop "$cname" 2>/dev/null || true
    $CONTAINER_ENGINE rm -f "$cname" 2>/dev/null || true
  done

  # Clean up any existing pgagroal processes
  pkill -f pgagroal 2>/dev/null || true
  sleep 2
  pkill -9 -f pgagroal 2>/dev/null || true
  rm -f /tmp/pgagroal.*.pid 2>/dev/null || true

  echo "Environment checks passed."

  echo ""
  echo "=========================================="
  echo "Running Failover Notification Tests"
  echo "=========================================="

  failover_prepare_dirs "$FAILOVER_BASE" || {
    echo "Failed to prepare directories"
    set -e
    return 1
  }

  if ! $CONTAINER_ENGINE image inspect "$IMAGE_NAME" >/dev/null 2>&1
  then
    echo "Building PostgreSQL image"
    build_postgresql_image || {
      echo "Failed to build PostgreSQL image"
      set -e
      return 1
    }
  fi

  test_failover_notify_success "$FAILOVER_BASE" || echo "Test 1 failed but continuing..."
  test_failover_notify_failure "$FAILOVER_BASE" || echo "Test 2 failed but continuing..."

  failover_cleanup "$FAILOVER_BASE"

  echo ""
  echo "Failover tests completed"
  set -e
}

do_setup() {
  local always_build="${1:-}"
  if [[ $MODE != "ci" ]]; then
    echo "Building PostgreSQL $ENV_PGVERSION image if necessary"
    if $CONTAINER_ENGINE image inspect "$IMAGE_NAME" >/dev/null 2>&1; then
      echo "Image $IMAGE_NAME exists, skip building"
    else
      build_postgresql_image
    fi
  fi

  if [[ $MODE == "gcc" ]]; then
    COVERAGE=NO
  else
    COVERAGE=YES
  fi

  echo "Preparing the pgagroal directory"
  rm -Rf "$PGAGROAL_ROOT_DIR"
  mkdir -p "$PGAGROAL_ROOT_DIR"
  # Set up LLVM coverage
  if [[ $COVERAGE == "YES" ]]; then 
    export LLVM_PROFILE_FILE="$COVERAGE_DIR/coverage-%m-%p.profraw"
    mkdir -p "$LOG_DIR" "$PG_LOG_DIR" "$COVERAGE_DIR" "$BASE_DIR" "$RESOURCE_DIRECTORY"
  else 
    mkdir -p "$LOG_DIR" "$PG_LOG_DIR" "$BASE_DIR" "$RESOURCE_DIRECTORY"
  fi

  cp -R "$PROJECT_DIRECTORY/test/resource" $BASE_DIR
  mkdir -p "$CONFIGURATION_DIRECTORY"
  mkdir -p "$PROJECT_DIRECTORY/log"
  # Check if pgbench is available
  if ! command -v pgbench &> /dev/null; then
    echo "Warning: pgbench not found in PATH. Tests may fail."
    find /usr -name "pgbench" 2>/dev/null || echo "pgbench not found in /usr"
  else
    echo "pgbench found: $(which pgbench)"
  fi
  chmod -R 777 "$PGAGROAL_ROOT_DIR"
  if [[ "$always_build" == "force" ]] || [[ "$1" != "ci-nonbuild" && "$1" != "run-configs-ci-nonbuild" ]]; then
   echo "Building pgagroal"
   mkdir -p "$PROJECT_DIRECTORY/build"
   cd "$PROJECT_DIRECTORY/build"
   # Configure build with LLVM coverage or GCC without coverage
   if [[ $MODE != gcc ]]; then
     export CC=$(which clang)
     echo "Using Clang compiler with LLVM coverage: $CC"
   else
     export CC=$(which gcc)
     echo "Using GCC compiler without coverage: $CC"
   cmake -DCMAKE_C_COMPILER=$CC \
        -DCMAKE_BUILD_TYPE=Debug \
        ..
   fi
   make -j$(nproc)
   cd ..
  fi
  if [[ $MODE == "ci" ]]; then
  echo "Start PostgreSQL $ENV_PGVERSION locally"
    start_postgresql
  else
  echo "Start PostgreSQL $ENV_PGVERSION container"
    start_postgresql_container
  fi

  # Initialize pgbench database for main user
  echo "Initializing pgbench database for main user..."
  PGPASSWORD="$PG_USER_PASSWORD" pgbench -i -s 1 -h localhost -p "$PORT" -U "$PG_USER_NAME" -d "$PG_DATABASE"
  if [[ $? -ne 0 ]]; then
    echo "pgbench initialization failed for main user!"
    cleanup_postgresql_image
    exit 1
  fi
  echo "pgbench initialization for main user completed."

  # Initialize pgbench database for UTF-8 user
  echo "Initializing pgbench database for UTF-8 user..."
  PGPASSWORD="$PG_UTF8_USER_PASSWORD" pgbench -i -s 1 -h localhost -p "$PORT" -U "$PG_UTF8_USER_NAME" -d "$PG_UTF8_DATABASE"
  if [[ $? -ne 0 ]]; then
    echo "pgbench initialization failed for UTF-8 user!"
    cleanup_postgresql_image
    exit 1
  fi
  echo "pgbench initialization for UTF-8 user completed."

  echo "Initialize pgagroal"
  pgagroal_initialize_configuration
  export_pgagroal_test_variables
}

run_tests() {
  if need_build; then
    do_setup
  else
    # Double-check: binaries and config must exist (cleanup removes BASE_DIR/conf, so config can be missing)
    if [[ ! -f "$EXECUTABLE_DIRECTORY/pgagroal" ]] || [[ ! -f "$TEST_DIRECTORY/pgagroal_test" ]] \
       || [[ ! -f "$CONFIGURATION_DIRECTORY/pgagroal.conf" ]]; then
      echo "Environment incomplete (binaries or config missing), running build"
      do_setup
    else
      echo "Environment already ready, skipping build"
    fi
  fi
  if [ "$1" = "run-configs" ] || [ "$1" = "run-configs-ci" ] || [ "$1" = "run-configs-ci-nonbuild" ]; then
    run_multiple_config_tests
  else
    execute_testcases
  fi
}

TEST_FILTER=""
MODULE_FILTER=""
SUBCOMMAND=""

# Parse command-line options
while [[ $# -gt 0 ]]; do
   case "$1" in
      -t|--test)
         [[ -n "$MODULE_FILTER" ]] && { echo "Error: Cannot specify both -t and -m options"; usage; }
         shift
         [[ $# -eq 0 ]] && { echo "Error: -t/--test requires NAME"; usage; }
         TEST_FILTER="$1"
         shift
         ;;
      -m|--module)
         [[ -n "$TEST_FILTER" ]] && { echo "Error: Cannot specify both -t and -m options"; usage; }
         shift
         [[ $# -eq 0 ]] && { echo "Error: -m/--module requires NAME"; usage; }
         MODULE_FILTER="$1"
         shift
         ;;
      build)
         [[ -n "$SUBCOMMAND" ]] && usage
         SUBCOMMAND="build"
         shift
         ;;
      setup)
         [[ -n "$SUBCOMMAND" ]] && usage
         SUBCOMMAND="setup"
         shift
         ;;
      clean)
         [[ -n "$SUBCOMMAND" ]] && usage
         SUBCOMMAND="clean"
         shift
         ;;
      run-configs)
         [[ -n "$SUBCOMMAND" ]] && usage
         SUBCOMMAND="run-configs"
         shift
         ;;
      failover-tests)
         run_failover_tests
         exit 0
         ;;
      ci)
         [[ -n "$SUBCOMMAND" ]] && usage
         SUBCOMMAND="ci"
         shift
         ;;
      run-configs-ci)
         [[ -n "$SUBCOMMAND" ]] && usage
         SUBCOMMAND="run-configs-ci"
         shift
         ;;
      ci-nonbuild)
         [[ -n "$SUBCOMMAND" ]] && usage
         SUBCOMMAND="ci-nonbuild"
         shift
         ;;
      run-configs-ci-nonbuild)
         [[ -n "$SUBCOMMAND" ]] && usage
         SUBCOMMAND="run-configs-ci-nonbuild"
         shift
         ;;
      gcc)
         [[ -n "$SUBCOMMAND" ]] && usage
         SUBCOMMAND="gcc"
         shift
         ;;
      -h|--help)
         usage
         ;;
      -*)
         echo "Invalid option: $1"
         usage
         ;;
      *)
         echo "Invalid parameter: $1"
         usage
         ;;
   esac
done

if [[ -n "$SUBCOMMAND" ]]; then
  if [[ "$SUBCOMMAND" == "build" ]]; then
    detect_container_engine
    do_setup force
    exit 0
  fi
  if [[ "$SUBCOMMAND" == "setup" ]]; then
    detect_container_engine
    build_postgresql_image
    # Install LLVM coverage dependencies or GCC dependencies
    $SUDO dnf install -y \
      cmake \
      make \
      libev libev-devel \
      openssl openssl-devel \
      systemd systemd-devel \
      zlib zlib-devel \
      libzstd libzstd-devel \
      lz4 lz4-devel \
      libssh libssh-devel \
      libatomic \
      bzip2 bzip2-devel \
      libarchive libarchive-devel
    if [[ $MODE != "gcc" ]]; then
      echo "Installing LLVM coverage tools..."
      $SUDO dnf install -y \
        clang \
        clang-analyzer \
        llvm \
        libasan \
        libasan-static
    else
      echo "Using GCC mode (no LLVM coverage tools)"
      $SUDO dnf install -y gcc gcc-c++
    fi

    echo "Setup complete"
  elif [[ "$SUBCOMMAND" == "clean" ]]; then
    detect_container_engine
    rm -Rf $COVERAGE_DIR
    cleanup
    cleanup_postgresql_image
    rm -Rf $PGAGROAL_ROOT_DIR
  elif [[ "$SUBCOMMAND" == "run-configs" ]]; then
    detect_container_engine
    trap cleanup EXIT SIGINT
    run_tests "run-configs"
  elif [[ "$SUBCOMMAND" == "ci" ]]; then
    MODE="ci"
    PORT=5432
    trap cleanup EXIT
    run_tests
  elif [[ "$SUBCOMMAND" == "run-configs-ci" ]]; then
    MODE="ci"
    PORT=5432
    trap cleanup EXIT
    run_tests "run-configs"
  elif [[ "$SUBCOMMAND" == "ci-nonbuild" ]]; then
    MODE="ci"
    PORT=5432
    trap cleanup EXIT
    run_tests "ci-nonbuild"
  elif [[ "$SUBCOMMAND" == "run-configs-ci-nonbuild" ]]; then
    MODE="ci"
    PORT=5432
    trap cleanup EXIT
    run_tests "run-configs-ci-nonbuild"
  elif [[ "$SUBCOMMAND" == "gcc" ]]; then
    MODE="gcc"
    trap cleanup EXIT SIGINT
    run_tests
  fi
else
   # If no subcommand is provided, run tests (full suite or with -t/-m filter)
   detect_container_engine
   trap cleanup EXIT SIGINT
   run_tests
fi
