#!/usr/bin/env bash
# Fixed-purpose runner. Credentials are supplied as a root-only MySQL option file.
set -Eeuo pipefail
umask 077
phase=INIT
fail() { printf 'STOP:%s:%s\n' "$phase" "$1" >&2; exit 1; }
on_error() { printf 'STOP:%s:COMMAND_FAILED line=%s exit=%s\n' "$phase" "$2" "$1" >&2; exit "$1"; }
trap 'on_error $? "$LINENO"' ERR
[[ $# == 4 ]] || fail INVALID_ARGUMENTS
cnf=$1
package_hash=$2
script_hash=$3
incoming=$4
[[ $cnf =~ ^/root/\.zhct-upgrade-220/run-[a-f0-9]{32}/mysql\.cnf$ ]] || fail INVALID_CREDENTIAL_PATH
[[ $package_hash =~ ^[a-f0-9]{64}$ && $script_hash =~ ^[a-f0-9]{64}$ ]] || fail INVALID_HASH
[[ $incoming =~ ^/workspace/wwwroot/zhct_zhangjiagang/\.zhct_220-[a-f0-9]{32}\.tgz$ ]] || fail INVALID_UPLOAD_PATH
trap 'rm -f -- "$cnf" "$incoming"' EXIT
dir=/workspace/wwwroot/zhct_zhangjiagang
db=zhct_zhangjiagang
state=/workspace/zhct-upgrade-backups/zhangjiagang/220
pkg="$dir/zhct_220.tgz"
deploy="$dir/deploy_zhct.sh"
mark() { phase=$1; printf 'PHASE:%s\n' "$phase"; }
mark PRECHECK
[[ $(id -u) == 0 ]] || fail ROOT_REQUIRED
for tool in mysql mysqldump sha256sum tar gzip flock timeout realpath stat df du awk curl; do
  command -v "$tool" >/dev/null || fail "MISSING_TOOL_$tool"
done
[[ -f $cnf && ! -L $cnf && $(stat -c '%a:%u' "$cnf") == 600:0 ]] || fail UNSAFE_CREDENTIAL_FILE
[[ -d $dir && $(realpath "$dir") == "$dir" ]] || fail TARGET_PATH_MISMATCH
[[ -d $dir/project && ! -L $dir/project ]] || fail PROJECT_DIRECTORY_MISSING
[[ -f $deploy && ! -L $deploy && -x $deploy ]] || fail DEPLOY_SCRIPT_MISSING
[[ -f $incoming && ! -L $incoming ]] || fail PACKAGE_MISSING
[[ $(sha256sum "$incoming" | awk '{print $1}') == "$package_hash" ]] || fail PACKAGE_HASH_MISMATCH
[[ $(sha256sum "$deploy" | awk '{print $1}') == "$script_hash" ]] || fail DEPLOY_SCRIPT_HASH_MISMATCH
gzip -t "$incoming"
# State is kept outside the web root; a second click cannot run concurrently.
mkdir -p "$state"
[[ $(realpath "$state") == "$state" ]] || fail BACKUP_PATH_MISMATCH
chmod 700 "$state"
exec 9>"$state/upgrade.lock"
flock -n 9 || fail UPGRADE_ALREADY_RUNNING
sql() { mysql --defaults-extra-file="$cnf" --batch --skip-column-names "$db" -e "$1"; }
[[ $(sql 'SELECT DATABASE();') == "$db" ]] || fail WRONG_DATABASE
engine=$(sql 'SELECT VERSION();')
printf 'MYSQL_VERSION:%s\n' "$engine"
[[ $(sql "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$db' AND table_name IN ('ydy_config','ydy_meal_order');") == 2 ]] || fail REQUIRED_TABLE_MISSING
[[ $(sql "SELECT COUNT(*) FROM ydy_config WHERE config_key='ossHost';") == 1 ]] || fail OSS_CONFIG_NOT_UNIQUE
[[ $(sql "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='$db' AND table_name='ydy_meal_order' AND column_name='pay_method';") == 1 ]] || fail PAY_METHOD_MISSING
column_count() { sql "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='$db' AND table_name='ydy_meal_order' AND column_name='bank_user_name';"; }
verify_column() {
  [[ $(sql "SELECT COUNT(*) FROM information_schema.columns c JOIN information_schema.columns p ON p.table_schema=c.table_schema AND p.table_name=c.table_name AND p.column_name='pay_method' WHERE c.table_schema='$db' AND c.table_name='ydy_meal_order' AND c.column_name='bank_user_name' AND c.data_type='varchar' AND c.character_maximum_length=255 AND c.is_nullable='NO' AND c.column_default='' AND c.column_comment='张家港银行核销返回的消费人姓名' AND c.ordinal_position=p.ordinal_position+1;") == 1 ]] || fail BANK_COLUMN_CONFLICT
}
count=$(column_count)
[[ $count == 0 || $count == 1 ]] || fail COLUMN_STATE_UNKNOWN
if [[ $count == 1 ]]; then verify_column; fi
if [[ -e $state/deploy.started && ! -e $state/deploy.ok ]]; then fail DEPLOY_INTERRUPTED_ENGINEER_REQUIRED; fi
if [[ -e $state/package.sha256 ]]; then
  [[ $(cat "$state/package.sha256") == "$package_hash" ]] || fail VERSION_PACKAGE_CHANGED
fi
[[ ! -L $pkg ]] || fail PACKAGE_PATH_UNSAFE
# Only publish the incoming package after holding the deployment lock.
mv "$incoming" "$pkg"
if [[ ! -e $state/backup.ok ]]; then
  mark BACKUP
  # A consistent database snapshot requires InnoDB for all base tables.
  [[ $(sql "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$db' AND table_type='BASE TABLE' AND engine <> 'InnoDB';") == 0 ]] || fail NON_INNODB_REQUIRES_MAINTENANCE_BACKUP
  # Reserve an uncompressed DB + code estimate, staging room, and 1 GiB headroom.
  db_bytes=$(sql "SELECT COALESCE(SUM(data_length+index_length),0) FROM information_schema.tables WHERE table_schema='$db';")
  code_bytes=$(du -sb "$dir/project" | awk '{print $1}')
  free_bytes=$(df -PB1 "$state" | awk 'END {print $4}')
  pkg_bytes=$(stat -c %s "$pkg")
  [[ $db_bytes =~ ^[0-9]+$ && $code_bytes =~ ^[0-9]+$ && $free_bytes =~ ^[0-9]+$ ]] || fail DISK_ESTIMATE_FAILED
  (( free_bytes > db_bytes * 2 + code_bytes * 2 + pkg_bytes * 4 + 1073741824 )) || fail NOT_ENOUGH_DISK
  mysqldump --defaults-extra-file="$cnf" --single-transaction --routines --triggers --events --no-tablespaces "$db" 2>"$state/database-backup.error" | gzip >"$state/database.sql.gz.part"
  gzip -t "$state/database.sql.gz.part"
  [[ $(gzip -cd "$state/database.sql.gz.part" | wc -c) -gt 0 ]] || fail EMPTY_DATABASE_BACKUP
  mv "$state/database.sql.gz.part" "$state/database.sql.gz"
  tar -czf "$state/code.tgz.part" -C "$dir" project deploy_zhct.sh
  gzip -t "$state/code.tgz.part"
  mv "$state/code.tgz.part" "$state/code.tgz"
  sha256sum "$state/database.sql.gz" "$state/code.tgz" >"$state/backup.sha256"
  printf '%s\n' "$package_hash" >"$state/package.sha256"
  touch "$state/backup.ok"
else
  mark VERIFY_BACKUP
  sha256sum -c "$state/backup.sha256" >/dev/null
fi
mark SQL_CONFIG
if [[ $(sql "SELECT config_value FROM ydy_config WHERE config_key='ossHost';") != http://10.28.45.75 ]]; then
  sql "UPDATE zhct_zhangjiagang.ydy_config SET config_value = 'http://10.28.45.75' WHERE config_key = 'ossHost';" >/dev/null
fi
[[ $(sql "SELECT config_value FROM ydy_config WHERE config_key='ossHost';") == http://10.28.45.75 ]] || fail OSS_READBACK_FAILED
mark SQL_COLUMN
if [[ $(column_count) == 0 ]]; then
  # Limit metadata-lock waits; never retry DDL automatically after failure.
  sql "SET SESSION lock_wait_timeout=30; ALTER TABLE zhct_zhangjiagang.ydy_meal_order ADD COLUMN \`bank_user_name\` varchar(255) NOT NULL DEFAULT '' COMMENT '张家港银行核销返回的消费人姓名' AFTER \`pay_method\`;" >/dev/null
fi
verify_column
printf 'SQL_VERIFIED\n'
if [[ ! -e $state/deploy.ok ]]; then
  mark DEPLOY
  [[ $(sha256sum "$deploy" | awk '{print $1}') == "$script_hash" ]] || fail DEPLOY_SCRIPT_HASH_MISMATCH
  [[ $(sha256sum "$pkg" | awk '{print $1}') == "$package_hash" ]] || fail PACKAGE_HASH_MISMATCH
  # A started marker deliberately survives failures/connection loss.
  touch "$state/deploy.started"
  cd "$dir"
  if timeout 1800 ./deploy_zhct.sh 220 </dev/null >"$state/deploy.log" 2>&1; then
    [[ $(sha256sum "$pkg" | awk '{print $1}') == "$package_hash" ]] || fail PACKAGE_CHANGED_DURING_DEPLOY
    touch "$state/deploy.ok"
  else
    fail DEPLOY_FAILED_ENGINEER_REQUIRED
  fi
else
  printf 'DEPLOY_ALREADY_EXECUTED\n'
fi
mark HTTP_CHECK
http=$(curl --noproxy '*' -s -o /dev/null -w '%{http_code}' --connect-timeout 10 --max-time 30 http://10.28.45.75/) || fail HTTP_CONNECTION_FAILED
[[ $http == 200 ]] || fail "HTTP_STATUS_$http"
# This is a homepage check, not a bank transaction or authenticated UI test.
printf 'TECHNICAL_UPGRADE_OK\nBUSINESS_SMOKE_PENDING\n'
