#!/bin/bash
# pg_rollback.sh — 回滾 target 機器的 postgresql.conf 到最近一次 backup
#
# 用法：
#   pg_rollback.sh TARGET
#
# 流程：
#   1. SPEC 收集
#   2. Pre-flight（PG running + Game Server stopped）
#   3. 取 conf 路徑
#   4. SSH 過去：找最近 backup → 記 OLD（restart 前 SHOW 17 param） → cp → restart → 記 NEW（SHOW）
#   5. 解析 OLD / NEW，per-param diff 寫 audit（status=rolled_back，共用 batch_id）
#      - 沒變動的 param 不寫 audit
#      - 「(rollback)」summary entry 不寫，純 multi-row 顯示實際變更

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./_lib_pg_apply.sh
source "${SCRIPT_DIR}/_lib_pg_apply.sh"

emit_json() {
    local status="$1" message="$2"
    shift 2
    local extra=""
    while [[ $# -gt 0 ]]; do
        local k="$1" v="$2"; shift 2
        local v_esc="${v//\\/\\\\}"
        v_esc="${v_esc//\"/\\\"}"
        extra+=",\"$k\":\"$v_esc\""
    done
    printf '{"status":"%s","message":"%s"%s}\n' "$status" "$message" "$extra"
}

TARGET="${1:-}"
[[ -z "$TARGET" ]] && { echo "Usage: $0 TARGET" >&2; exit 1; }

# 1. SPEC
spec_data=$(pgapply_collect_spec "$TARGET")
[[ -z "$spec_data" ]] && { emit_json "fail" "SSH 失敗"; exit 1; }
NPROC=$(echo "$spec_data" | awk -F= '/^NPROC=/ {print $2}')
MEM_BYTES=$(echo "$spec_data" | awk -F= '/^MEM_BYTES=/ {print $2}')
PG_VERSION=$(echo "$spec_data" | awk -F= '/^PG_VERSION=/ {print $2}')
RAM_GB=$(( MEM_BYTES / 1073741824 ))

# 2. Pre-flight
if ! pf_msg=$(pgapply_preflight "$TARGET" 2>&1); then
    emit_json "fail" "preflight failed" detail "$pf_msg"
    exit 2
fi

# 3. conf path
CONF_PATH=$(pgapply_get_conf_path "$TARGET")
[[ -z "$CONF_PATH" ]] && { emit_json "fail" "取不到 config_file"; exit 3; }

APPLIED_BY="${SUDO_USER:-${USER:-unknown}}"
BATCH_ID=$(pgapply_new_batch_id)

# 把 whitelist 字串化成 CSV（避免空格被 SSH env var 切斷成多個 args）
PARAM_LIST_CSV=$(IFS=','; echo "${PGAPPLY_WHITELIST[*]}")

# 4. SSH 過去：找 backup → SHOW OLD → cp → restart → SHOW NEW
remote_result=$(ssh "${PGAPPLY_SSH_OPTS[@]}" "$TARGET" \
    CONF_PATH="$CONF_PATH" PARAM_LIST="$PARAM_LIST_CSV" \
    bash 2>&1 <<'REMOTE' || true
set +e

# 找最近一個 apply 留的 backup（filename TS 排序，避開 mtime 不可靠）
BACKUP_DIR="$(dirname "$CONF_PATH")/conf_backups"
LATEST_BACKUP=$(sudo find "$BACKUP_DIR" -maxdepth 1 -type f -name "$(basename "$CONF_PATH").bak.[0-9]*" 2>/dev/null | sort -r | head -1)
if [[ -z "$LATEST_BACKUP" ]]; then
    echo "FAIL_NO_BACKUP"
    exit 4
fi

if ! sudo test -s "$LATEST_BACKUP"; then
    echo "FAIL_BACKUP_EMPTY:$LATEST_BACKUP"
    exit 5
fi

# 把 CSV 轉回 array
IFS=',' read -ra PARAMS_ARR <<< "$PARAM_LIST"

# 在還原前 SHOW 一輪當前值，作為 OLD baseline
echo "OLD_BEGIN"
for p in "${PARAMS_ARR[@]}"; do
    v=$(sudo -n -u postgres psql -AtX -c "SHOW $p" 2>/dev/null)
    echo "OLD|$p|$v"
done
echo "OLD_END"

# 在還原前先 backup 當前狀態
sudo mkdir -p "$BACKUP_DIR" 2>/dev/null
sudo chown postgres:postgres "$BACKUP_DIR" 2>/dev/null
sudo chmod 750 "$BACKUP_DIR" 2>/dev/null
PRE_ROLLBACK_BACKUP="${BACKUP_DIR}/$(basename "$CONF_PATH").bak.pre_rollback_$(date '+%Y%m%d-%H%M%S')"
sudo cp -p "$CONF_PATH" "$PRE_ROLLBACK_BACKUP" 2>/dev/null

# Restore
if ! sudo cp -p "$LATEST_BACKUP" "$CONF_PATH" 2>/dev/null; then
    echo "FAIL_RESTORE"
    exit 6
fi

# Restart
PGCLUSTER_VER=$(echo "$CONF_PATH" | sed -nE 's|^/etc/postgresql/([0-9]+)/.*$|\1|p')
PGCLUSTER_NAME=$(echo "$CONF_PATH" | sed -nE 's|^/etc/postgresql/[0-9]+/([^/]+)/.*$|\1|p')
PG_UNIT="postgresql@${PGCLUSTER_VER:-16}-${PGCLUSTER_NAME:-main}.service"
PG_BIN=$(ls /usr/lib/postgresql/*/bin/pg_ctl 2>/dev/null | tail -1)
[[ -z "$PG_BIN" ]] && PG_BIN=/usr/bin/pg_ctl

wait_pg_ready() {
    local i
    for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
        sudo -u postgres pg_isready >/dev/null 2>&1 && return 0
        sleep 2
    done
    return 1
}

do_pg_restart() {
    local out
    out=$(sudo -n systemctl restart "$PG_UNIT" 2>&1)
    if wait_pg_ready; then echo "$out"; return 0; fi
    if pgrep -f "postgres -D /var/lib/postgresql/${PGCLUSTER_VER:-16}" >/dev/null 2>&1; then
        sudo -n -u postgres "$PG_BIN" -D "/var/lib/postgresql/${PGCLUSTER_VER:-16}/main" stop -m fast >/dev/null 2>&1
        sleep 3
    fi
    sudo -n systemctl reset-failed "$PG_UNIT" 2>/dev/null
    out="${out}\n--- after rescue ---\n$(sudo -n systemctl start "$PG_UNIT" 2>&1)"
    if wait_pg_ready; then echo "$out"; return 0; fi
    echo "$out"
    return 1
}

RESTART_OUT=$(do_pg_restart)
if [[ $? -ne 0 ]]; then
    # restart 失敗——把 pre_rollback 撤回去再 restart 救火
    sudo cp -p "$PRE_ROLLBACK_BACKUP" "$CONF_PATH" 2>/dev/null
    do_pg_restart >/dev/null 2>&1 || true
    echo "FAIL_RESTART:${RESTART_OUT}"
    exit 7
fi

# Restart 後 SHOW 一輪，作為 NEW
echo "NEW_BEGIN"
for p in "${PARAMS_ARR[@]}"; do
    v=$(sudo -n -u postgres psql -AtX -c "SHOW $p" 2>/dev/null)
    echo "NEW|$p|$v"
done
echo "NEW_END"

echo "OK|RESTORED_FROM=${LATEST_BACKUP}|PRE_ROLLBACK=${PRE_ROLLBACK_BACKUP}"
REMOTE
)

# 5. 解析 OLD / NEW，per-param diff 寫 audit
case "$remote_result" in
    *"OK|"*)
        RESTORED_FROM=$(echo "$remote_result" | sed -n 's/.*RESTORED_FROM=\([^|]*\).*/\1/p')
        PRE_ROLLBACK=$(echo "$remote_result" | sed -n 's/.*PRE_ROLLBACK=\([^|]*\).*/\1/p')

        # rollback 後白名單參數指紋（不算整檔，避免 Ops 編輯非白名單參數誤判）
        ROLLED_SHA=$(pgapply_compute_managed_fingerprint "$TARGET")
        # 找近期 audit 是否有匹配此指紋的歷史 → 標 verified；不匹配 → 警告
        SHA_VERIFY_NOTE=""
        if [[ -n "$ROLLED_SHA" ]]; then
            conn_args=$(_pgapply_tools_db_args 2>/dev/null)
            if [[ -n "$conn_args" ]]; then
                t_esc="${TARGET//\'/\'\'}"
                MATCH_ID=$(psql $conn_args -AtX -c \
                    "SELECT id FROM public.pg_apply_audit WHERE target='$t_esc' AND status='success' AND (conf_sha256='$ROLLED_SHA' OR backup_sha256='$ROLLED_SHA') ORDER BY id DESC LIMIT 1" 2>/dev/null)
                if [[ -n "$MATCH_ID" ]]; then
                    SHA_VERIFY_NOTE="verified|matched_audit_id=$MATCH_ID"
                else
                    SHA_VERIFY_NOTE="unverified|對不到任何工具留下的 SHA（第一次 rollback 或檔案曾被工具外修改）"
                fi
            fi
        fi

        declare -A OLD_VALS NEW_VALS
        while IFS= read -r line; do
            case "$line" in
                "OLD|"*)
                    rest="${line#OLD|}"
                    p="${rest%%|*}"
                    v="${rest#*|}"
                    OLD_VALS[$p]="$v"
                    ;;
                "NEW|"*)
                    rest="${line#NEW|}"
                    p="${rest%%|*}"
                    v="${rest#*|}"
                    NEW_VALS[$p]="$v"
                    ;;
            esac
        done <<< "$remote_result"

        # per-param diff
        CHANGES=0
        for p in "${!OLD_VALS[@]}"; do
            old="${OLD_VALS[$p]}"
            new="${NEW_VALS[$p]:-}"
            if [[ "$old" != "$new" ]]; then
                # 找最近一筆 (TARGET, p) 的 success audit，作為 reverted_audit_id
                reverted_id=$(pgapply_find_success_audit_id "$TARGET" "$p")
                pgapply_log_audit \
                    target="$TARGET" cpu="$NPROC" ram_gb="$RAM_GB" pg_version="$PG_VERSION" \
                    param_name="$p" \
                    old_value="$old" new_value="$new" \
                    applied_by="$APPLIED_BY" status="rolled_back" \
                    backup_file="$PRE_ROLLBACK" batch_id="$BATCH_ID" \
                    reverted_audit_id="$reverted_id" \
                    sha_verify="$SHA_VERIFY_NOTE"
                CHANGES=$((CHANGES+1))
            fi
        done

        emit_json "ok" "rolled back" \
            restored_from "$RESTORED_FROM" pre_rollback_backup "$PRE_ROLLBACK" changes "$CHANGES" \
            sha_verify "$SHA_VERIFY_NOTE"
        exit 0 ;;
    FAIL_*)
        # rollback 失敗，記 1 筆 fail audit（不知道哪些 param 變動，就記成 (rollback)）
        pgapply_log_audit \
            target="$TARGET" cpu="$NPROC" ram_gb="$RAM_GB" pg_version="$PG_VERSION" \
            param_name="(rollback)" \
            applied_by="$APPLIED_BY" status="fail" \
            error_msg="$remote_result" batch_id="$BATCH_ID"

        emit_json "fail" "rollback failed" detail "$remote_result"
        exit 8 ;;
    *)
        emit_json "fail" "unexpected result" detail "$remote_result"
        exit 9 ;;
esac
