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

EXPECTED_HOST=sqjcDB02
EXPECTED_IP=10.2.208.107
PRIMARY_IP=10.2.208.106
INNER_ARCHIVE_SHA256=77821b30effff6527888ddf4b5c68d9890000b725864f9b21e2195db17e56708
INNER_ARCHIVE=${INNER_ARCHIVE:-/root/sanquan-mysql-q220-222-ha-gates-20260729.tar.gz}
RUNTIME_ROOT=/root/.zhct-mysql-q220-221-fast-chain
INPUT_ROOT=${INPUT_ROOT:-/root/zhct-q220-222-inputs}
WRAPPER_EVIDENCE_ROOT=/var/backups/zhct-q220-221-fast-chain
MUTATION_STARTED=0

die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
require_file() { [[ -f "$1" ]] || die "required file missing: $1"; }
secure_file() { require_file "$1"; [[ $(stat -c '%U:%G' "$1") == root:root ]] || die "unsafe owner: $1"; case "$(stat -c '%a' "$1")" in 400|600) ;; *) die "unsafe mode: $1" ;; esac; }
receipt_gate() { local file=$1 marker=$2; secure_file "$file"; grep -Fxq "$marker=PASS" "$file" || die "$marker receipt is not PASS"; grep -Eq '^archive_sha256=[0-9a-f]{64}$' "$file" || die "$marker receipt SHA missing"; }
receipt_value() { awk -F= -v key="$2" '$1==key{print substr($0,index($0,"=")+1)}' "$1"; }
policy_value() { awk -F= -v key="$1" '$1==key{print substr($0,index($0,"=")+1)}' "$INPUT_ROOT/q221-policy.env"; }
mysql_value() { local client=$1 cnf=$2 sql=$3; "$client" --defaults-extra-file="$cnf" --batch --skip-column-names -e "$sql"; }
static_validate() { require_file "$INNER_ARCHIVE"; [[ $(sha256sum "$INNER_ARCHIVE" | awk '{print $1}') == "$INNER_ARCHIVE_SHA256" ]] || die 'inner mature archive SHA mismatch'; bash -n "${BASH_SOURCE[0]}"; printf 'MYSQL02_Q221_REPLICA_SIDE_STATIC_VALIDATE_OK\n'; }
check_listener() { local port=$1; ss -lntH | awk -v endpoint="$EXPECTED_IP:$port" '$4==endpoint{ok=1} END{exit !ok}' || die "listener missing: $EXPECTED_IP:$port"; }
check_unit() { systemctl is-enabled --quiet "$1" || die "unit not enabled: $1"; systemctl is-active --quiet "$1" || die "unit not active: $1"; }
prepare_payload() {
  local runtime="$RUNTIME_ROOT/$RUN_ID/payload"
  require_file "$INNER_ARCHIVE"
  [[ $(sha256sum "$INNER_ARCHIVE" | awk '{print $1}') == "$INNER_ARCHIVE_SHA256" ]] || die 'inner mature archive SHA mismatch'
  [[ ! -e "$runtime" ]] || die "runtime path already exists: $runtime"
  install -d -m 0700 -o root -g root "$runtime"
  tar -xzf "$INNER_ARCHIVE" -C "$runtime" --no-same-owner
  (cd "$runtime" && sha256sum -c SHA256SUMS)
  bash -n "$runtime/q220-222-mysql.sh"
  bash "$runtime/q220-222-mysql.sh" --static-validate
  INNER_ENTRY="$runtime/q220-222-mysql.sh"
}
rollback_on_failure() {
  local rc=$?; trap - EXIT
  if [[ $rc -ne 0 && $MUTATION_STARTED -eq 1 && -x ${INNER_ENTRY:-/nonexistent} ]]; then
    printf 'failure_rollback_begin=YES\n'
    INPUT_ROOT="$INPUT_ROOT" bash "$INNER_ENTRY" q221 rollback --node mysql02 --approve-dangerous || printf 'failure_rollback_rc=%s\n' "$?"
  fi
  printf 'finished_at=%s\nEND MYSQL02_Q221_REPLICA_SIDE RC=%s\n' "$(date --iso-8601=seconds)" "$rc"; exit "$rc"
}

[[ ${1:-} == --static-validate ]] && { static_validate; exit 0; }
[[ ${1:-} == run && $# -eq 1 ]] || die 'usage: mysql02-replica-side.sh --static-validate|run'
static_validate
printf 'BEGIN MYSQL02_Q221_REPLICA_SIDE\nstarted_at=%s\n' "$(date --iso-8601=seconds)"
[[ $(id -u) -eq 0 ]] || die 'root is required'
[[ "$(hostname -s)" == "$EXPECTED_HOST" ]] || die 'wrong hostname'
ip -o -4 addr show scope global | awk -v ip="$EXPECTED_IP" '{split($4,a,"/");if(a[1]==ip)ok=1}END{exit !ok}' || die "expected IP missing: $EXPECTED_IP"
RUN_ID="mysql02-q221-fast-$(date '+%Y%m%dT%H%M%S%z')"; EVIDENCE_DIR="$WRAPPER_EVIDENCE_ROOT/$RUN_ID"
install -d -m 0700 -o root -g root "$EVIDENCE_DIR"; exec > >(tee -a "$EVIDENCE_DIR/operator.log") 2>&1
trap rollback_on_failure EXIT
printf 'target_identity=PASS\ntarget_hostname=%s\ntarget_ip=%s\nrun_id=%s\n' "$EXPECTED_HOST" "$EXPECTED_IP" "$RUN_ID"

receipt_gate "$INPUT_ROOT/q200-pass.env" Q200
receipt_gate "$INPUT_ROOT/q210-pass.env" Q210
receipt_gate "$INPUT_ROOT/q220-pass.env" Q220
printf 'dependency_gate=Q200_PASS_Q210_PASS_Q220_PASS\n'
for unit in mysql56.service mysql80.service mysql-health56.service mysql-health80.service; do check_unit "$unit"; done
for port in 3306 3308 9201 9202; do check_listener "$port"; done
printf 'service_listener_gate=PASS\n'

secure_file "$INPUT_ROOT/mysql56-admin.cnf"; secure_file "$INPUT_ROOT/mysql80-admin.cnf"; secure_file "$INPUT_ROOT/q221-policy.env"
id56=$(mysql_value /opt/mysql/5.6.51/bin/mysql "$INPUT_ROOT/mysql56-admin.cnf" 'SELECT CONCAT(@@server_id,"|",@@server_uuid,"|",@@read_only,"|",@@global.binlog_format)')
id80=$(mysql_value /opt/mysql/8.0.46/bin/mysql "$INPUT_ROOT/mysql80-admin.cnf" 'SELECT CONCAT(@@server_id,"|",@@server_uuid,"|",@@read_only,"|",@@super_read_only,"|",@@global.binlog_format)')
IFS='|' read -r sid56 uuid56 ro56 fmt56 <<<"$id56"; IFS='|' read -r sid80 uuid80 ro80 sro80 fmt80 <<<"$id80"
[[ $sid56 == 56107 && $sid80 == 80107 ]] || die 'replica server_id mismatch'
[[ $uuid56 =~ ^[0-9a-fA-F-]{36}$ && $uuid80 =~ ^[0-9a-fA-F-]{36}$ && $uuid56 != "$uuid80" ]] || die 'replica UUID gate failed'
[[ $uuid56 != "$(receipt_value "$INPUT_ROOT/q220-pass.env" mysql56_primary_server_uuid)" ]] || die 'MySQL56 UUID conflicts with primary'
[[ $uuid80 != "$(receipt_value "$INPUT_ROOT/q220-pass.env" mysql80_primary_server_uuid)" ]] || die 'MySQL80 UUID conflicts with primary'
[[ $ro56 == 1 && $ro80 == 1 && $sro80 == 1 ]] || die 'replica read-only policy mismatch'
[[ ${fmt56^^} == ROW && ${fmt80^^} == ROW ]] || die 'binlog_format must be ROW'
[[ $(policy_value MYSQL56_SOURCE_HOST) == "$PRIMARY_IP" && $(policy_value MYSQL56_SOURCE_PORT) == 3306 ]] || die 'MySQL56 source policy mismatch'
[[ $(policy_value MYSQL80_SOURCE_HOST) == "$PRIMARY_IP" && $(policy_value MYSQL80_SOURCE_PORT) == 3308 ]] || die 'MySQL80 source policy mismatch'
for key in MYSQL56_REPLICATION_MODE MYSQL80_REPLICATION_MODE MYSQL56_MAX_LAG_SECONDS MYSQL80_MAX_LAG_SECONDS MYSQL56_SEMISYNC_REQUIRED MYSQL80_SEMISYNC_REQUIRED; do [[ -n $(policy_value "$key") ]] || die "policy missing: $key"; done
printf 'replica_identity_readonly_policy_gate=PASS\n'

prepare_payload
MUTATION_STARTED=1
INPUT_ROOT="$INPUT_ROOT" bash "$INNER_ENTRY" q221 execute --node mysql02 --approve-dangerous
INPUT_ROOT="$INPUT_ROOT" bash "$INNER_ENTRY" q221 verify --node mysql02
INNER_RUN_ID=$(< /var/lib/zhct-q220-222/q221-mysql02.last); INNER_EVIDENCE="/var/backups/zhct-q220-222/$INNER_RUN_ID"
for version in 56 80; do
  status="$INNER_EVIDENCE/mysql${version}-replication-status.txt"; require_file "$status"
  grep -Eq '(Master_Host|Source_Host):[[:space:]]*10\.2\.208\.106$' "$status" || die "MySQL$version source host mismatch"
  expected_port=3306; [[ $version == 80 ]] && expected_port=3308
  grep -Eq "(Master_Port|Source_Port):[[:space:]]*$expected_port$" "$status" || die "MySQL$version source port mismatch"
  grep -Eq '(Slave_IO_Running|Replica_IO_Running):[[:space:]]*Yes$' "$status" || die "MySQL$version IO thread not running"
  grep -Eq '(Slave_SQL_Running|Replica_SQL_Running):[[:space:]]*Yes$' "$status" || die "MySQL$version SQL thread not running"
  lag=$(awk -F: '/Seconds_Behind_(Master|Source):/{gsub(/[[:space:]]/,"",$2);print $2}' "$status")
  max_lag=$(policy_value "MYSQL${version}_MAX_LAG_SECONDS")
  [[ $lag =~ ^[0-9]+$ && $max_lag =~ ^[0-9]+$ && $lag -le $max_lag ]] || die "MySQL$version lag exceeds policy"
done
INPUT_ROOT="$INPUT_ROOT" bash "$INNER_ENTRY" q221 archive --node mysql02
MUTATION_STARTED=0
INNER_ARCHIVE="/var/backups/zhct-q220-222/${INNER_RUN_ID}.tar.gz"; require_file "$INNER_ARCHIVE"; INNER_RESULT_SHA=$(sha256sum "$INNER_ARCHIVE" | awk '{print $1}')
printf 'Q221=PASS\narchive_sha256=%s\nbusiness_sql=NOT_PERFORMED_Q261_GATE\n' "$INNER_RESULT_SHA" > /root/q221-pass.env
chmod 0600 /root/q221-pass.env
sha256sum /root/q221-pass.env "$INNER_ARCHIVE" | tee "$EVIDENCE_DIR/output-sha256.txt"
printf 'MYSQL56_REPLICATION_STATUS=PASS\nMYSQL80_REPLICATION_STATUS=PASS\nreplica_read_only=PASS\n'
printf 'Q221_DUAL_VERSION_REPLICATION_EXECUTE_VERIFY_ARCHIVE_PASS\nq221_receipt=/root/q221-pass.env\n'
printf 'Q222=NOT_PERFORMED_REQUIRES_ORCHESTRATOR_HAPROXY_NODES\nQ261_BUSINESS_SQL=NOT_PERFORMED\n'
