#!/usr/bin/env bash
# onx-db-migration-dryrun — Backup'tan paralel DB instance kurar ve target engine'de test eder.
#
# Akış:
#   1. Backup .sql.gz dosyasını /tmp'a aç
#   2. Hedef sürümün binary'sini geçici TCP port'a (örn: 3399) başlat (datadir=/var/lib/mysql-dryrun)
#   3. Backup'ı paralel instance'a restore et
#   4. Schema farkını çıkar (mysqldump --no-data karşılaştırması)
#   5. Data checksum karşılaştırması (CHECKSUM TABLE her tablo)
#   6. Paralel instance'ı kapat, datadir'i sil
#
# Production DB'ye DOKUNMAZ.
#
# Input (stdin JSON):
#   target_version  string  "11.4" | "8.0"
#   target_engine   string  "mariadb" | "mysql"
#   backup_path     string  Preflight ile alınan backup
#
# Output (stdout JSON):
#   {
#     "ok": true,
#     "duration_seconds": 425,
#     "schema_diff_count": 2,
#     "schema_diffs": ["public_settings: ENGINE=Aria → InnoDB", ...],
#     "data_checksum_mismatches": 0,
#     "checksum_results": [{"table":"users","prod":"abc123","dryrun":"abc123","match":true}, ...],
#     "warnings": ["mysql_upgrade reported 3 warnings", ...]
#   }
#
# Exit: 0=ok 1=invalid-input 2=preflight-fail 3=exec-fail 4=rolled-back

set -euo pipefail

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
source "${SCRIPT_DIR}/_lib/common.sh"

DRYRUN_PORT=3399
DRYRUN_DATADIR="/var/lib/mysql-dryrun"
DRYRUN_SOCK="/tmp/mysql-dryrun.sock"
DRYRUN_PIDFILE="/tmp/mysql-dryrun.pid"
DRYRUN_LOG="/var/log/onoxsoft/dryrun.log"

INPUT=$(cat)
onx_require_json "${INPUT}"

TARGET_VERSION=$(onx_json_get "${INPUT}" "target_version")
TARGET_ENGINE=$(onx_json_get "${INPUT}" "target_engine")
BACKUP_PATH=$(onx_json_get "${INPUT}" "backup_path")

[[ -z "${TARGET_VERSION}" || -z "${TARGET_ENGINE}" ]] && onx_die 1 "target_version and target_engine required"
[[ -z "${BACKUP_PATH}" || ! -f "${BACKUP_PATH}" ]] && onx_die 1 "backup file not found"
[[ $EUID -eq 0 ]] || onx_die 2 "must be run as root"

START_TS=$(date +%s)
mkdir -p "$(dirname "${DRYRUN_LOG}")"

# ── Cleanup function (her exit path'ta çağrılır) ───────────────────────────
cleanup_dryrun() {
    if [[ -f "${DRYRUN_PIDFILE}" ]]; then
        local pid
        pid=$(cat "${DRYRUN_PIDFILE}" 2>/dev/null || echo "")
        [[ -n "${pid}" ]] && kill "${pid}" 2>/dev/null || true
        rm -f "${DRYRUN_PIDFILE}"
    fi
    rm -rf "${DRYRUN_DATADIR}" 2>/dev/null || true
    rm -f "${DRYRUN_SOCK}" 2>/dev/null || true
}
trap cleanup_dryrun EXIT

# ── 1. Hedef binary tespit et ──────────────────────────────────────────────
if [[ "${TARGET_ENGINE}" == "mariadb" ]]; then
    MYSQLD_BIN=$(command -v mariadbd 2>/dev/null || command -v mysqld 2>/dev/null || echo "")
else
    MYSQLD_BIN=$(command -v mysqld 2>/dev/null || echo "")
fi

[[ -z "${MYSQLD_BIN}" ]] && onx_die 2 "${TARGET_ENGINE} binary not found — install it first"

INSTALLED_VERSION=$("${MYSQLD_BIN}" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+' | head -1)
if [[ "${INSTALLED_VERSION}" != "${TARGET_VERSION}" ]]; then
    onx_log "warning: installed ${INSTALLED_VERSION} != requested ${TARGET_VERSION}, using installed"
fi

# ── 2. Datadir init ────────────────────────────────────────────────────────
rm -rf "${DRYRUN_DATADIR}"
mkdir -p "${DRYRUN_DATADIR}"
chown -R mysql:mysql "${DRYRUN_DATADIR}" 2>/dev/null || true

if [[ "${TARGET_ENGINE}" == "mariadb" ]]; then
    mariadb-install-db --user=mysql --datadir="${DRYRUN_DATADIR}" >> "${DRYRUN_LOG}" 2>&1 \
        || mysql_install_db --user=mysql --datadir="${DRYRUN_DATADIR}" >> "${DRYRUN_LOG}" 2>&1 \
        || onx_die 3 "datadir init failed"
else
    "${MYSQLD_BIN}" --initialize-insecure --user=mysql --datadir="${DRYRUN_DATADIR}" >> "${DRYRUN_LOG}" 2>&1 \
        || onx_die 3 "mysqld --initialize failed"
fi

# ── 3. Paralel instance başlat ─────────────────────────────────────────────
"${MYSQLD_BIN}" --user=mysql \
    --datadir="${DRYRUN_DATADIR}" \
    --port="${DRYRUN_PORT}" \
    --socket="${DRYRUN_SOCK}" \
    --pid-file="${DRYRUN_PIDFILE}" \
    --skip-networking=0 \
    --bind-address=127.0.0.1 \
    --general-log=0 \
    --slow-query-log=0 \
    >> "${DRYRUN_LOG}" 2>&1 &

# Wait for socket
for i in {1..60}; do
    [[ -S "${DRYRUN_SOCK}" ]] && break
    sleep 1
done
[[ -S "${DRYRUN_SOCK}" ]] || onx_die 3 "dryrun instance did not start within 60s"

# Wait for ping
for i in {1..30}; do
    mysqladmin --socket="${DRYRUN_SOCK}" ping >/dev/null 2>&1 && break
    sleep 1
done
mysqladmin --socket="${DRYRUN_SOCK}" ping >/dev/null 2>&1 \
    || onx_die 3 "dryrun instance not responding"

onx_log "dryrun instance up on socket ${DRYRUN_SOCK}"

# ── 4. Backup'ı restore et ─────────────────────────────────────────────────
DECOMPRESS="cat"
if [[ "${BACKUP_PATH}" == *.gz ]]; then
    DECOMPRESS="zcat"
fi

${DECOMPRESS} "${BACKUP_PATH}" | mysql --socket="${DRYRUN_SOCK}" --force >> "${DRYRUN_LOG}" 2>&1 \
    || onx_die 3 "backup restore to dryrun instance failed"

# Run mysql_upgrade on dryrun (collect warnings)
UPGRADE_WARNINGS=0
if command -v mariadb-upgrade >/dev/null 2>&1; then
    UPGRADE_WARNINGS=$(mariadb-upgrade --socket="${DRYRUN_SOCK}" --force 2>&1 | grep -c -iE 'warning|error' || true)
elif command -v mysql_upgrade >/dev/null 2>&1; then
    UPGRADE_WARNINGS=$(mysql_upgrade --socket="${DRYRUN_SOCK}" --force 2>&1 | grep -c -iE 'warning|error' || true)
fi

# ── 5. Schema diff (production vs dryrun) ─────────────────────────────────
SCHEMA_DIFFS=()
DBS=$(mysql --socket="${DRYRUN_SOCK}" -N -e "SHOW DATABASES" 2>/dev/null | grep -vE '^(information_schema|mysql|performance_schema|sys)$' || true)

for DB in ${DBS}; do
    # Production schema
    PROD_DUMP="/tmp/dryrun-prod-${DB}.sql"
    DRYRUN_DUMP="/tmp/dryrun-dryrun-${DB}.sql"
    mysqldump --no-data --skip-comments --skip-extended-insert "${DB}" 2>/dev/null > "${PROD_DUMP}" || continue
    mysqldump --socket="${DRYRUN_SOCK}" --no-data --skip-comments --skip-extended-insert "${DB}" 2>/dev/null > "${DRYRUN_DUMP}" || continue

    if ! diff -q "${PROD_DUMP}" "${DRYRUN_DUMP}" >/dev/null 2>&1; then
        # İlk 3 fark çıkarılır
        DIFF_LINES=$(diff "${PROD_DUMP}" "${DRYRUN_DUMP}" 2>/dev/null | grep -E '^[<>]' | head -6 | tr '\n' '|' || echo "")
        SCHEMA_DIFFS+=("${DB}:${DIFF_LINES}")
    fi
    rm -f "${PROD_DUMP}" "${DRYRUN_DUMP}"
done

# ── 6. Data checksum karşılaştırması ──────────────────────────────────────
CHECKSUM_MISMATCHES=0
CHECKSUM_RESULTS="["
FIRST=1
for DB in ${DBS}; do
    TABLES=$(mysql -N -e "SHOW TABLES FROM \`${DB}\`" 2>/dev/null | head -50 || true)
    for TBL in ${TABLES}; do
        PROD_SUM=$(mysql -N -e "CHECKSUM TABLE \`${DB}\`.\`${TBL}\`" 2>/dev/null | awk '{print $2}' || echo "")
        DRYRUN_SUM=$(mysql --socket="${DRYRUN_SOCK}" -N -e "CHECKSUM TABLE \`${DB}\`.\`${TBL}\`" 2>/dev/null | awk '{print $2}' || echo "")

        MATCH="true"
        if [[ "${PROD_SUM}" != "${DRYRUN_SUM}" ]]; then
            MATCH="false"
            CHECKSUM_MISMATCHES=$((CHECKSUM_MISMATCHES + 1))
        fi

        [[ ${FIRST} -eq 0 ]] && CHECKSUM_RESULTS+=","
        FIRST=0
        CHECKSUM_RESULTS+=$(jq -n \
            --arg db "${DB}" --arg t "${TBL}" \
            --arg p "${PROD_SUM}" --arg d "${DRYRUN_SUM}" \
            --argjson m "${MATCH}" \
            '{db:$db, table:$t, prod:$p, dryrun:$d, match:$m}')
    done
done
CHECKSUM_RESULTS+="]"

# ── Output ───────────────────────────────────────────────────────────────
END_TS=$(date +%s)
DURATION=$((END_TS - START_TS))

SCHEMA_DIFF_JSON=$(printf '%s\n' "${SCHEMA_DIFFS[@]}" 2>/dev/null | jq -R . | jq -s . 2>/dev/null || echo "[]")

jq -n \
    --argjson ok true \
    --argjson duration "${DURATION}" \
    --argjson schema_diff_count "${#SCHEMA_DIFFS[@]}" \
    --argjson schema_diffs "${SCHEMA_DIFF_JSON}" \
    --argjson data_checksum_mismatches "${CHECKSUM_MISMATCHES}" \
    --argjson checksum_results "${CHECKSUM_RESULTS}" \
    --argjson upgrade_warnings "${UPGRADE_WARNINGS}" \
    --arg target_engine "${TARGET_ENGINE}" \
    --arg target_version "${TARGET_VERSION}" \
    '{
        ok: $ok,
        target_engine: $target_engine,
        target_version: $target_version,
        duration_seconds: $duration,
        schema_diff_count: $schema_diff_count,
        schema_diffs: $schema_diffs,
        data_checksum_mismatches: $data_checksum_mismatches,
        checksum_results: $checksum_results,
        upgrade_warnings: $upgrade_warnings
    }'

onx_log "dryrun complete: ${TARGET_ENGINE} ${TARGET_VERSION}, schema_diff=${#SCHEMA_DIFFS[@]}, checksum_mismatch=${CHECKSUM_MISMATCHES}, duration=${DURATION}s"
