#!/bin/bash
set -uo pipefail

# ============================================================
# MDEV-36411 end-to-end reproduction script (fully automated)
#
# Usage:
#   ./mdev_36411_e2e.sh <BASEDIR> [DURATION_SECONDS] [JAVA_BATCH_SIZE]
#
# Example:
#   ./mdev_36411_e2e.sh ~/Downloads/mariadb-10.11.11-linux-systemd-x86_64
#   ./mdev_36411_e2e.sh ~/Projects/mariadb/10.11_release/data/Server_bin/10.11_release 600
#
# BASEDIR is any MariaDB install/build root that contains mariadbd,
# mariadb-install-db, and the mariadb client somewhere underneath it
# (this script finds them automatically -- works for both official
# downloaded packages and source builds, whose layouts differ
# slightly).
#
# Everything else is derived automatically: test directory (unique
# per BASEDIR, so multiple versions can be tested without clobbering
# each other), a free TCP port, and the JDBC driver jar (downloaded
# once, then reused across runs).
# ============================================================

BASEDIR="${1:?Usage: $0 <BASEDIR> [DURATION_SECONDS] [JAVA_BATCH_SIZE]}"
DURATION_SECONDS="${2:-300}"
JAVA_BATCH_SIZE="${3:-20}"
JAVA_ITERATIONS=1000000   # effectively unbounded; DURATION_SECONDS via `timeout` is the real limit

if [ ! -d "$BASEDIR" ]; then
  echo "ERROR: BASEDIR '$BASEDIR' does not exist."
  exit 1
fi

echo "=== MDEV-36411 end-to-end reproduction ==="
echo "BASEDIR: $BASEDIR"

# ---- Auto-locate binaries ----
MARIADBD_BIN=$(find "$BASEDIR" -maxdepth 4 -type f -name "mariadbd" 2>/dev/null | head -1)
MARIADB_INSTALL_DB_BIN=$(find "$BASEDIR" -maxdepth 4 -type f -name "mariadb-install-db" 2>/dev/null | grep -v '\.1$' | head -1)
MARIADB_CLIENT_BIN=$(find "$BASEDIR" -maxdepth 4 -type f -name "mariadb" 2>/dev/null | head -1)

if [ -z "$MARIADBD_BIN" ]; then
  echo "ERROR: could not find mariadbd under $BASEDIR"
  exit 1
fi
if [ -z "$MARIADB_INSTALL_DB_BIN" ]; then
  echo "ERROR: could not find mariadb-install-db under $BASEDIR"
  exit 1
fi
if [ -z "$MARIADB_CLIENT_BIN" ]; then
  echo "ERROR: could not find mariadb client under $BASEDIR"
  exit 1
fi

echo "Found mariadbd:              $MARIADBD_BIN"
echo "Found mariadb-install-db:    $MARIADB_INSTALL_DB_BIN"
echo "Found mariadb client:        $MARIADB_CLIENT_BIN"

# ---- Auto-derive a unique test directory per BASEDIR ----
BASEDIR_TAG=$(echo "$BASEDIR" | md5sum | cut -c1-10)
TEST_DIR="$HOME/mdev_36411_e2e_${BASEDIR_TAG}"
echo "Test directory: $TEST_DIR (unique per BASEDIR)"

# ---- Auto-pick a free TCP port ----
find_free_port() {
  for candidate in $(shuf -i 13000-19999 -n 50); do
    if ! ss -ltn 2>/dev/null | grep -q ":$candidate "; then
      echo "$candidate"
      return 0
    fi
  done
  echo "ERROR: could not find a free port" >&2
  return 1
}
PORT=$(find_free_port)
if [ -z "$PORT" ]; then
  exit 1
fi
echo "Using port: $PORT"

MARIADB_JDBC_JAR_URL="https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/3.3.3/mariadb-java-client-3.3.3.jar"
SHARED_JAR_CACHE="$HOME/.cache/mdev_36411/mariadb-java-client-3.3.3.jar"

DATADIR="$TEST_DIR/data"
SOCKET="$TEST_DIR/mysql.sock"
JAR="$TEST_DIR/mariadb-java-client-3.3.3.jar"
JAVA_SRC_DIR="$TEST_DIR/java"
ROTATOR_LOG="$TEST_DIR/rotator.log"
WRITER_LOG="$TEST_DIR/writer.log"
SERVER_LOG="$TEST_DIR/mariadbd.log"

mkdir -p "$TEST_DIR" "$DATADIR" "$JAVA_SRC_DIR" "$(dirname "$SHARED_JAR_CACHE")"

# ---- 1. Initialize and start the server ----
if [ ! -d "$DATADIR/mysql" ]; then
  echo "--- Initializing datadir ---"
  "$MARIADB_INSTALL_DB_BIN" --no-defaults --datadir="$DATADIR" --auth-root-authentication-method=normal
fi

echo "--- Starting mariadbd on port $PORT ---"
"$MARIADBD_BIN" \
  --no-defaults \
  --datadir="$DATADIR" \
  --socket="$SOCKET" \
  --port="$PORT" \
  --bind-address=127.0.0.1 \
  --log-error="$SERVER_LOG" \
  > "$TEST_DIR/mariadbd.out" 2>&1 &
SERVER_PID=$!

echo "Waiting for server to accept connections..."
for i in $(seq 1 30); do
  if "$MARIADB_CLIENT_BIN" -h 127.0.0.1 -P "$PORT" -u root -e "SELECT 1" > /dev/null 2>&1; then
    echo "Server is up (pid $SERVER_PID)."
    break
  fi
  sleep 1
done

if ! "$MARIADB_CLIENT_BIN" -h 127.0.0.1 -P "$PORT" -u root -e "SELECT 1" > /dev/null 2>&1; then
  echo "ERROR: server did not come up. Check $SERVER_LOG and $TEST_DIR/mariadbd.out"
  kill "$SERVER_PID" 2>/dev/null
  exit 1
fi

# ---- 2. Create the reporter's exact table ----
echo "--- Creating table ---"
"$MARIADB_CLIENT_BIN" -h 127.0.0.1 -P "$PORT" -u root <<'SQL'
DROP DATABASE IF EXISTS test;
CREATE DATABASE test;
USE test;
CREATE TABLE `example_table` (
  `logfile` varchar(50) NOT NULL,
  `obsnr` int(11) NOT NULL,
  `linenr` int(11) NOT NULL,
  `obstime` datetime(6) NOT NULL,
  `debug` tinyint(4) NOT NULL,
  `examplecolumn` tinyint(4) NOT NULL,
  `loglevel` varchar(10) NOT NULL,
  `classname` varchar(250) NOT NULL,
  `threadname` varchar(250) NOT NULL,
  `message` varchar(20000) DEFAULT NULL,
  PRIMARY KEY (`logfile`,`obsnr`,`obstime`),
  KEY `obstimeindex` (`obstime`),
  KEY `levelkey` (`loglevel`),
  KEY `debugkey` (`debug`),
  KEY `examplekey` (`examplecolumn`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci ROW_FORMAT=COMPRESSED
    PARTITION BY RANGE (to_days(`obstime`))
        (PARTITION `verleden` VALUES LESS THAN (0) ENGINE = InnoDB,
        PARTITION `van20250226` VALUES LESS THAN (739674) ENGINE = InnoDB,
        PARTITION `van20250227` VALUES LESS THAN (739675) ENGINE = InnoDB,
        PARTITION `van20250228` VALUES LESS THAN (739676) ENGINE = InnoDB,
        PARTITION `van20250301` VALUES LESS THAN (739677) ENGINE = InnoDB,
        PARTITION `van20250302` VALUES LESS THAN (739678) ENGINE = InnoDB,
        PARTITION `van20250303` VALUES LESS THAN (739679) ENGINE = InnoDB,
        PARTITION `van20250304` VALUES LESS THAN (739680) ENGINE = InnoDB,
        PARTITION `van20250305` VALUES LESS THAN (739681) ENGINE = InnoDB,
        PARTITION `van20250306` VALUES LESS THAN (739682) ENGINE = InnoDB,
        PARTITION `van20250307` VALUES LESS THAN (739683) ENGINE = InnoDB,
        PARTITION `van20250308` VALUES LESS THAN (739684) ENGINE = InnoDB,
        PARTITION `van20250309` VALUES LESS THAN (739685) ENGINE = InnoDB,
        PARTITION `van20250310` VALUES LESS THAN (739686) ENGINE = InnoDB,
        PARTITION `van20250311` VALUES LESS THAN (739687) ENGINE = InnoDB,
        PARTITION `van20250312` VALUES LESS THAN (739688) ENGINE = InnoDB,
        PARTITION `van20250313` VALUES LESS THAN (739689) ENGINE = InnoDB,
        PARTITION `van20250314` VALUES LESS THAN (739690) ENGINE = InnoDB,
        PARTITION `van20250315` VALUES LESS THAN (739691) ENGINE = InnoDB,
        PARTITION `van20250316` VALUES LESS THAN (739692) ENGINE = InnoDB,
        PARTITION `van20250317` VALUES LESS THAN (739693) ENGINE = InnoDB,
        PARTITION `van20250318` VALUES LESS THAN (739694) ENGINE = InnoDB,
        PARTITION `van20250319` VALUES LESS THAN (739695) ENGINE = InnoDB,
        PARTITION `van20250320` VALUES LESS THAN (739696) ENGINE = InnoDB,
        PARTITION `van20250321` VALUES LESS THAN (739697) ENGINE = InnoDB,
        PARTITION `van20250322` VALUES LESS THAN (739698) ENGINE = InnoDB,
        PARTITION `van20250323` VALUES LESS THAN (739699) ENGINE = InnoDB,
        PARTITION `van20250324` VALUES LESS THAN (739700) ENGINE = InnoDB,
        PARTITION `van20250325` VALUES LESS THAN (739701) ENGINE = InnoDB,
        PARTITION `van20250326` VALUES LESS THAN (739702) ENGINE = InnoDB,
        PARTITION `van20250327` VALUES LESS THAN (739703) ENGINE = InnoDB,
        PARTITION `van20250328` VALUES LESS THAN (739704) ENGINE = InnoDB,
        PARTITION `van20250329` VALUES LESS THAN (739705) ENGINE = InnoDB,
        PARTITION `van20250330` VALUES LESS THAN (739706) ENGINE = InnoDB,
        PARTITION `van20250331` VALUES LESS THAN (739707) ENGINE = InnoDB,
        PARTITION `van20250401` VALUES LESS THAN (739708) ENGINE = InnoDB,
        PARTITION `van20250402` VALUES LESS THAN (739709) ENGINE = InnoDB,
        PARTITION `van20250403` VALUES LESS THAN (739710) ENGINE = InnoDB,
        PARTITION `van20250404` VALUES LESS THAN (739711) ENGINE = InnoDB,
        PARTITION `van20250405` VALUES LESS THAN (739712) ENGINE = InnoDB,
        PARTITION `van20250406` VALUES LESS THAN (739713) ENGINE = InnoDB,
        PARTITION `van20250407` VALUES LESS THAN (739714) ENGINE = InnoDB,
        PARTITION `toekomst` VALUES LESS THAN MAXVALUE ENGINE = InnoDB);
SQL

if [ $? -ne 0 ]; then
  echo "ERROR: table creation failed"
  kill "$SERVER_PID"
  exit 1
fi

# ---- 3. Get the JDBC driver (cached once, shared across all runs) ----
if [ ! -f "$SHARED_JAR_CACHE" ]; then
  echo "--- Downloading mariadb-java-client 3.3.3 (one-time, cached for future runs) ---"
  curl -sSL -o "$SHARED_JAR_CACHE" "$MARIADB_JDBC_JAR_URL" || wget -q -O "$SHARED_JAR_CACHE" "$MARIADB_JDBC_JAR_URL"
fi

if [ ! -f "$SHARED_JAR_CACHE" ]; then
  echo "ERROR: could not obtain JDBC driver jar -- download it manually to $SHARED_JAR_CACHE and re-run."
  kill "$SERVER_PID"
  exit 1
fi
cp "$SHARED_JAR_CACHE" "$JAR"

# ---- 4. Write and compile the Java writer (cached/compiled once, reused if unchanged) ----
cat > "$JAVA_SRC_DIR/BatchWriter.java" <<'JAVAEOF'
import java.sql.*;
import java.util.concurrent.atomic.AtomicInteger;

public class BatchWriter {
    public static void main(String[] args) throws Exception {
        String host = args[0];
        int port = Integer.parseInt(args[1]);
        String db = args[2];
        String user = args[3];
        String password = args[4];
        int iterations = Integer.parseInt(args[5]);
        int batchSize = Integer.parseInt(args[6]);

        String url = String.format(
            "jdbc:mariadb://%s:%d/%s?useBatchMultiSend=true&useBulkStmts=true",
            host, port, db
        );

        AtomicInteger successCount = new AtomicInteger(0);
        AtomicInteger errorCount = new AtomicInteger(0);
        AtomicInteger obsnrCounter = new AtomicInteger(0);

        try (Connection conn = DriverManager.getConnection(url, user, password)) {
            conn.setAutoCommit(true);

            String sql =
                "INSERT INTO example_table " +
                "(logfile, obsnr, linenr, obstime, debug, examplecolumn, loglevel, classname, threadname, message) " +
                "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " +
                "ON DUPLICATE KEY UPDATE message = VALUES(message)";

            for (int batch = 0; batch < iterations; batch++) {
                try (PreparedStatement ps = conn.prepareStatement(sql)) {
                    for (int j = 0; j < batchSize; j++) {
                        int obsnr = obsnrCounter.getAndIncrement();
                        ps.setString(1, "log1");
                        ps.setInt(2, obsnr);
                        ps.setInt(3, obsnr);
                        ps.setTimestamp(4, new Timestamp(System.currentTimeMillis()));
                        ps.setInt(5, 0);
                        ps.setInt(6, 0);
                        ps.setString(7, "INFO");
                        ps.setString(8, "MyClass");
                        ps.setString(9, "MyThread");
                        ps.setString(10, "x".repeat(500));
                        ps.addBatch();
                    }
                    try {
                        int[] results = ps.executeBatch();
                        successCount.addAndGet(results.length);
                    } catch (BatchUpdateException e) {
                        errorCount.incrementAndGet();
                        System.out.println("### JAVA_CLIENT_ERROR at batch " + batch
                            + " errorCode=" + e.getErrorCode()
                            + " sqlState=" + e.getSQLState()
                            + " message=" + e.getMessage());
                        Throwable cause = e.getCause();
                        int depth = 0;
                        while (cause != null && depth < 5) {
                            System.out.println("    caused by: " + cause);
                            cause = cause.getCause();
                            depth++;
                        }
                    } catch (SQLException e) {
                        errorCount.incrementAndGet();
                        System.out.println("### JAVA_CLIENT_ERROR (non-batch) at batch " + batch
                            + " errorCode=" + e.getErrorCode()
                            + " sqlState=" + e.getSQLState()
                            + " message=" + e.getMessage());
                    }
                }
                if (batch % 200 == 0) {
                    System.out.println("Progress: batch " + batch + "/" + iterations
                        + " successes=" + successCount.get() + " errors=" + errorCount.get());
                }
            }
        }
        System.out.println("DONE. Total successes=" + successCount.get() + " total errors=" + errorCount.get());
    }
}
JAVAEOF

echo "--- Compiling BatchWriter.java ---"
javac -cp "$JAR" -d "$JAVA_SRC_DIR" "$JAVA_SRC_DIR/BatchWriter.java"
if [ $? -ne 0 ]; then
  echo "ERROR: compilation failed"
  kill "$SERVER_PID"
  exit 1
fi

# ---- 5. Start the rotator loop in the background ----
echo "--- Starting rotator loop (partition reorganize, repeating) ---"
(
  while true; do
    "$MARIADB_CLIENT_BIN" -h 127.0.0.1 -P "$PORT" -u root test -e \
      "ALTER TABLE example_table REORGANIZE PARTITION toekomst INTO (
         PARTITION van20250408 VALUES LESS THAN (739715) ENGINE = InnoDB,
         PARTITION toekomst VALUES LESS THAN MAXVALUE ENGINE = InnoDB
       );" 2>>"$ROTATOR_LOG"
    "$MARIADB_CLIENT_BIN" -h 127.0.0.1 -P "$PORT" -u root test -e \
      "ALTER TABLE example_table REORGANIZE PARTITION van20250408, toekomst INTO (
         PARTITION toekomst VALUES LESS THAN MAXVALUE ENGINE = InnoDB
       );" 2>>"$ROTATOR_LOG"
  done
) &
ROTATOR_PID=$!
echo "Rotator loop running (pid $ROTATOR_PID)."

# ---- 6. Run the real Java batch writer concurrently ----
echo "--- Running BatchWriter (mariadb-java-client 3.3.3, real executeBatch) ---"
echo "This will run for up to $DURATION_SECONDS seconds..."

timeout "$DURATION_SECONDS" java -cp "$JAR:$JAVA_SRC_DIR" BatchWriter \
  127.0.0.1 "$PORT" test root "" "$JAVA_ITERATIONS" "$JAVA_BATCH_SIZE" \
  > "$WRITER_LOG" 2>&1

# ---- 7. Stop the rotator loop ----
echo "--- Stopping rotator loop ---"
kill "$ROTATOR_PID" 2>/dev/null
wait "$ROTATOR_PID" 2>/dev/null

# ---- 8. Report results ----
echo ""
echo "=== RESULTS ==="
echo "BASEDIR tested: $BASEDIR"
"$MARIADB_CLIENT_BIN" -h 127.0.0.1 -P "$PORT" -u root -e "SELECT VERSION();" 2>/dev/null
ERROR_LINES=$(grep -c "JAVA_CLIENT_ERROR" "$WRITER_LOG" || true)
echo "JDBC client-visible errors captured: $ERROR_LINES"
ROW_COUNT=$("$MARIADB_CLIENT_BIN" -h 127.0.0.1 -P "$PORT" -u root test -NB -e "SELECT COUNT(*) FROM example_table;" 2>/dev/null)
PART_COUNT=$("$MARIADB_CLIENT_BIN" -h 127.0.0.1 -P "$PORT" -u root -NB -e "SELECT COUNT(*) FROM information_schema.partitions WHERE table_schema='test' AND table_name='example_table';" 2>/dev/null)
echo "Final row count in example_table: $ROW_COUNT"
echo "Final partition count: $PART_COUNT (expect 43 if unchanged)"
ROTATOR_ERR_LINES=$(wc -l < "$ROTATOR_LOG" 2>/dev/null || echo 0)
echo "Rotator-side (ALTER) stderr lines: $ROTATOR_ERR_LINES"

if [ "$ERROR_LINES" -gt 0 ]; then
  echo ""
  echo "--- Sample error details (first 20 lines) ---"
  grep -A 6 "JAVA_CLIENT_ERROR" "$WRITER_LOG" | head -20
fi
echo ""
echo "Full writer log:  $WRITER_LOG"
echo "Rotator log:      $ROTATOR_LOG"

# ---- 9. Shut down the server ----
echo "--- Shutting down server ---"
"$MARIADB_CLIENT_BIN" -h 127.0.0.1 -P "$PORT" -u root shutdown 2>/dev/null
sleep 2
kill "$SERVER_PID" 2>/dev/null

echo "=== Done. Test artifacts kept in $TEST_DIR ==="
