#!/usr/bin/env bash
set -Eeuo pipefail
umask 077

BUNDLE_ROOT=/opt/zhct-deploy
Q060_RUN=20260727T092302+0800
RUN_ID=${1:-$(date '+%Y%m%dT%H%M%S%z')}
EVIDENCE_ROOT=/var/backups/zhct-q061
EVIDENCE_DIR="$EVIDENCE_ROOT/$RUN_ID"
TEMP_ROOT="/var/tmp/zhct-q061-mysql56-$RUN_ID"
QUARANTINE="$EVIDENCE_DIR/failure-quarantine"
INSTALL_SCRIPT="$BUNDLE_ROOT/common/scripts/install-role.sh"
Q060_EVIDENCE="/var/backups/zhct-q060/$Q060_RUN"
SUCCESS=0
TEMP_PID=

die() { printf 'ERROR: %s\n' "$*" >&2; return 1; }
record() { printf '%s=%s\n' "$1" "$2" >>"$EVIDENCE_DIR/result.env"; }

units=(mysql56.service mysql80.service mysql-health56.service mysql-health80.service)
formal_paths=(
  /opt/mysql/5.6.51
  /opt/mysql/8.0.46
  /data/mysql56
  /data/mysql80
  /var/log/mysql56
  /var/log/mysql80
  /etc/zhct/templates/mysql
  /etc/systemd/system/mysql56.service
  /etc/systemd/system/mysql80.service
  /etc/systemd/system/mysql-health56.service
  /etc/systemd/system/mysql-health80.service
  /usr/local/sbin/mysql-primary-health.sh
)

quarantine_path() {
  local path=$1 destination
  [[ -e "$path" || -L "$path" ]] || return 0
  destination="$QUARANTINE/${path#/}"
  install -d -m 0700 "$(dirname "$destination")"
  mv -- "$path" "$destination"
}

rollback_failure() {
  local rc=$?
  trap - ERR EXIT INT TERM
  set +e
  printf 'failure_exit=%s\n' "$rc" >>"$EVIDENCE_DIR/result.env"
  systemctl disable --now "${units[@]}" >"$EVIDENCE_DIR/rollback-units.txt" 2>&1
  if [[ -n "${TEMP_PID:-}" ]] && kill -0 "$TEMP_PID" 2>/dev/null; then
    if tr '\0' ' ' <"/proc/$TEMP_PID/cmdline" | grep -Fq -- "$TEMP_ROOT"; then
      kill -TERM "$TEMP_PID"
      for _ in {1..30}; do kill -0 "$TEMP_PID" 2>/dev/null || break; sleep 1; done
    fi
  fi
  for path in "${formal_paths[@]}" /var/tmp/zhct-build/mysql "$TEMP_ROOT"; do
    quarantine_path "$path"
  done
  systemctl daemon-reload >>"$EVIDENCE_DIR/rollback-units.txt" 2>&1

  : >"$EVIDENCE_DIR/rollback-rpm-actions.txt"
  while IFS= read -r name; do
    [[ -n "$name" ]] || continue
    if rpm -q --quiet "$name" && rpm -e --test "$name" >>"$EVIDENCE_DIR/rollback-rpm-actions.txt" 2>&1; then
      rpm -e "$name" >>"$EVIDENCE_DIR/rollback-rpm-actions.txt" 2>&1
    else
      printf 'RETAINED_FOR_SAFETY %s\n' "$name" >>"$EVIDENCE_DIR/rollback-rpm-actions.txt"
    fi
  done <"$EVIDENCE_DIR/missing-package-names-before.txt"

  ps -eo pid,args | awk '$0 ~ /[m]ysqld/ {print}' >"$EVIDENCE_DIR/rollback-mysql-processes.txt"
  ss -lntup >"$EVIDENCE_DIR/rollback-listeners.txt"
  if grep -Eq ':(3306|3308|9201|9202)([[:space:]]|$)' "$EVIDENCE_DIR/rollback-listeners.txt" ||
     [[ -s "$EVIDENCE_DIR/rollback-mysql-processes.txt" ]]; then
    printf 'rollback_result=FAIL_TARGET_RUNTIME_REMAINS\n' >>"$EVIDENCE_DIR/result.env"
  else
    printf 'rollback_result=PASS_RUNTIME_ZERO_FORMAL_PATHS_QUARANTINED\n' >>"$EVIDENCE_DIR/result.env"
  fi
  printf 'install_result=FAIL\n' >>"$EVIDENCE_DIR/result.env"
  printf 'Q061_INSTALL_FAILED_ROLLBACK_ATTEMPTED run_id=%s evidence=%s\n' "$RUN_ID" "$EVIDENCE_DIR" >&2
  exit "$rc"
}
[[ $(id -u) -eq 0 ]] || die "root required"
[[ "$RUN_ID" =~ ^[0-9]{8}T[0-9]{6}[+-][0-9]{4}$ ]] || die "invalid run id"
[[ ! -e "$EVIDENCE_DIR" ]] || die "evidence already exists: $EVIDENCE_DIR"
install -d -m 0700 "$EVIDENCE_DIR"
printf 'run_id=%s\nstarted_at=%s\n' "$RUN_ID" "$(date --iso-8601=seconds)" >"$EVIDENCE_DIR/result.env"

hostname -s | tee "$EVIDENCE_DIR/hostname.txt"
[[ $(hostname -s | tr '[:upper:]' '[:lower:]') == sqjcdb01 ]] || die "wrong hostname"
ip -o -4 addr show scope global >"$EVIDENCE_DIR/ip-addresses.txt"
ip -o -4 addr show scope global | awk '{print $4}' | cut -d/ -f1 | grep -Fxq 10.2.208.106 ||
  die "expected IP missing"
[[ -f "$Q060_EVIDENCE/locked-assets-verification-result.env" ]] || die "Q060 evidence missing"
grep -Fxq 'source_sha256_recheck=PASS' "$Q060_EVIDENCE/locked-assets-verification-result.env"
grep -Fxq 'fresh_installroot_dependency_resolution=PASS_ASSUMENO' "$Q060_EVIDENCE/locked-assets-verification-result.env"
grep -Fxq 'install_execution=NOT_PERFORMED' "$Q060_EVIDENCE/locked-assets-verification-result.env"
[[ -x "$INSTALL_SCRIPT" ]] || die "install script missing"
for shell_file in "$INSTALL_SCRIPT" "$BUNDLE_ROOT/common/bin/role-action.sh" \
  "$BUNDLE_ROOT/common/lib/runtime.sh" "$BUNDLE_ROOT/roles/mysql/hooks.sh"; do
  bash -n "$shell_file"
done

for path in "${formal_paths[@]}"; do
  [[ ! -e "$path" && ! -L "$path" ]] || die "formal path already exists: $path"
done
for unit in "${units[@]}"; do
  ! systemctl is-active --quiet "$unit" || die "unit active before install: $unit"
done
ps -eo pid,args | awk '$0 ~ /[m]ysqld/ {print}' >"$EVIDENCE_DIR/mysql-processes-before.txt"
[[ ! -s "$EVIDENCE_DIR/mysql-processes-before.txt" ]] || die "mysqld process exists before install"
ss -lntup >"$EVIDENCE_DIR/listeners-before.txt"
! grep -Eq ':(3306|3308|9201|9202)([[:space:]]|$)' "$EVIDENCE_DIR/listeners-before.txt" ||
  die "target listener exists before install"
df -Pk / >"$EVIDENCE_DIR/root-disk-before.txt"
available_kb=$(df -Pk / | awk 'NR==2 {print $4}')
((available_kb >= 9011200)) || die "root free space below 8.8 GiB"
rpm -qa | sort >"$EVIDENCE_DIR/rpm-packages-before.txt"
dnf history list --reverse >"$EVIDENCE_DIR/dnf-history-before.txt"

: >"$EVIDENCE_DIR/missing-package-names-before.txt"
while IFS= read -r -d '' rpm_file; do
  name=$(rpm -qp --qf '%{NAME}' "$rpm_file")
  rpm -q --quiet "$name" || printf '%s\n' "$name" >>"$EVIDENCE_DIR/missing-package-names-before.txt"
done < <(find "$BUNDLE_ROOT/packages/rpms" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z)
sort -u -o "$EVIDENCE_DIR/missing-package-names-before.txt" "$EVIDENCE_DIR/missing-package-names-before.txt"
printf '%s\n' ncurses-compat-libs socat >"$EVIDENCE_DIR/expected-missing-package-names.txt"
cmp -s "$EVIDENCE_DIR/expected-missing-package-names.txt" "$EVIDENCE_DIR/missing-package-names-before.txt" ||
  die "missing package set changed"

trap rollback_failure ERR INT TERM
set +e
RUN_ID="$RUN_ID" BUNDLE_ROOT="$BUNDLE_ROOT" \
  bash "$INSTALL_SCRIPT" --role mysql --bundle-root "$BUNDLE_ROOT" \
  > >(tee "$EVIDENCE_DIR/install.stdout") 2> >(tee "$EVIDENCE_DIR/install.stderr" >&2)
install_rc=$?
set -e
printf 'install_exit=%s\n' "$install_rc" >>"$EVIDENCE_DIR/result.env"
((install_rc == 0)) || false

for unit in "${units[@]}"; do
  state=$(systemctl is-enabled "$unit" 2>/dev/null || true)
  [[ "$state" == disabled ]] || die "unit is not disabled: $unit ($state)"
  ! systemctl is-active --quiet "$unit" || die "unit active after install: $unit"
done
/opt/mysql/5.6.51/bin/mysqld --version | tee "$EVIDENCE_DIR/mysql56-version.txt" | grep -Fq 5.6.51
/opt/mysql/8.0.46/bin/mysqld --version | tee "$EVIDENCE_DIR/mysql80-version.txt" | grep -Fq 8.0.46

install -d -m 0750 -o mysql -g mysql "$TEMP_ROOT/data" "$TEMP_ROOT/run" "$TEMP_ROOT/log"
/opt/mysql/5.6.51/scripts/mysql_install_db \
  --basedir=/opt/mysql/5.6.51 --datadir="$TEMP_ROOT/data" --user=mysql --skip-name-resolve \
  >"$EVIDENCE_DIR/mysql56-init.log" 2>&1

/opt/mysql/5.6.51/bin/mysqld --no-defaults \
  --basedir=/opt/mysql/5.6.51 --datadir="$TEMP_ROOT/data" --user=mysql \
  --bind-address=127.0.0.1 --port=3306 --socket="$TEMP_ROOT/run/mysql.sock" \
  --pid-file="$TEMP_ROOT/run/mysqld.pid" --log-error="$TEMP_ROOT/log/error.log" \
  --server-id=560061 --skip-name-resolve &
TEMP_PID=$!
for _ in {1..60}; do
  /opt/mysql/5.6.51/bin/mysqladmin --no-defaults --socket="$TEMP_ROOT/run/mysql.sock" ping >/dev/null 2>&1 && break
  kill -0 "$TEMP_PID" 2>/dev/null || die "temporary mysqld exited"
  sleep 1
done
/opt/mysql/5.6.51/bin/mysqladmin --no-defaults --socket="$TEMP_ROOT/run/mysql.sock" ping |
  tee "$EVIDENCE_DIR/mysql56-ping.txt" | grep -Fq 'mysqld is alive'
ss -lntp | tee "$EVIDENCE_DIR/mysql56-listener.txt" | grep -Eq '127[.]0[.]0[.]1:3306'
/opt/mysql/5.6.51/bin/mysql --no-defaults --socket="$TEMP_ROOT/run/mysql.sock" -NBe \
  "CREATE DATABASE q061_acceptance; CREATE TABLE q061_acceptance.probe(id INT PRIMARY KEY, value VARCHAR(16)); INSERT INTO q061_acceptance.probe VALUES(1,'q061-pass'); SELECT CONCAT(id,':',value) FROM q061_acceptance.probe; DROP DATABASE q061_acceptance;" |
  tee "$EVIDENCE_DIR/mysql56-sql-test.txt" | grep -Fxq '1:q061-pass'
/opt/mysql/5.6.51/bin/mysqladmin --no-defaults --socket="$TEMP_ROOT/run/mysql.sock" shutdown
wait "$TEMP_PID"
TEMP_PID=
! ss -lntp | grep -Eq ':(3306)([[:space:]]|$)' || die "3306 remains listening"
cp -a "$TEMP_ROOT/log/error.log" "$EVIDENCE_DIR/mysql56-error.log"
case "$TEMP_ROOT" in
  /var/tmp/zhct-q061-mysql56-"$RUN_ID") rm -rf -- "$TEMP_ROOT" ;;
  *) die "refuse unsafe temporary cleanup path: $TEMP_ROOT" ;;
esac

rpm -qa | sort >"$EVIDENCE_DIR/rpm-packages-after.txt"
comm -13 "$EVIDENCE_DIR/rpm-packages-before.txt" "$EVIDENCE_DIR/rpm-packages-after.txt" \
  >"$EVIDENCE_DIR/rpm-packages-added.txt"
dnf history list --reverse >"$EVIDENCE_DIR/dnf-history-after.txt"
ss -lntup >"$EVIDENCE_DIR/listeners-after.txt"
ps -eo pid,args | awk '$0 ~ /[m]ysqld/ {print}' >"$EVIDENCE_DIR/mysql-processes-after.txt"
[[ ! -s "$EVIDENCE_DIR/mysql-processes-after.txt" ]] || die "mysqld remains after temporary verification"
! grep -Eq ':(3306|3308|9201|9202)([[:space:]]|$)' "$EVIDENCE_DIR/listeners-after.txt" ||
  die "target listener remains after temporary verification"

record install_result PASS
record mysql56_temporary_initialize PASS
record mysql56_port_3306 PASS_LOOPBACK_TEMPORARY
record mysql56_sql_read_write PASS
record temporary_mysql_stopped PASS
record formal_data_initialized NOT_PERFORMED
record mysql80_temporary_execution NOT_PERFORMED
record post_clone_execution NOT_PERFORMED
record service_activation NOT_PERFORMED
record completed_at "$(date --iso-8601=seconds)"
SUCCESS=1
trap - ERR INT TERM
printf 'Q061_MYSQL56_INSTALL_VERIFY_OK\nrun_id=%s\nevidence=%s\n' "$RUN_ID" "$EVIDENCE_DIR"
