#!/bin/bash
# usage_collector.sh — 機器用量採樣（admin_machine_spec.php Phase 2 v1）
#
# 跑在 Tools host 上，cron 每 10 min 一次：
#   1. 解析 /etc/hosts 列出所有 RFC1918 機器
#   2. 對每台 SSH 取 CPU%/RAM/Disk/Load
#   3. INSERT 進 public.machine_usage_sample
#   4. DELETE >90 天舊資料
# 連不上 SSH 的也寫一筆 ssh_ok=false（給後續分析「機器活著沒」）
#
# 假設條件：
#   - 跑這支的 user 對 hosts 中所有機器有 n1admin SSH key 直連權限
#   - tools_db creds 從 ../../../config.php 抓
#
# 使用：
#   sudo -u n1admin bash scripts/bin/machine_spec/usage_collector.sh
#   或交給 cron（/etc/cron.d/tools-usage-collector，post_deploy_init 安裝）

set -u

TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
CONFIG_PHP="$TOOLS_DIR/config.php"
LOG_FILE="$TOOLS_DIR/files/machine_usage_collector.log"

# ── log helper（stderr 同步到 log file，cron 也看得到）─────────────────────
_log() { printf '[%s] %s\n' "$(date '+%F %T')" "$*" | tee -a "$LOG_FILE" >&2; }

# ── 從 config.php 抽 $GLOBALS['<key>'] = '<value>'（不 include PHP，避免 session 副作用）
_cfg_get() {
    local key="$1"
    sed -n "s/.*\$GLOBALS\['${key}'\][[:space:]]*=[[:space:]]*['\"]\([^'\"]*\).*/\1/p" "$CONFIG_PHP" 2>/dev/null | head -1
}

if [[ ! -f "$CONFIG_PHP" ]]; then
    _log "❌ 找不到 config.php: $CONFIG_PHP"
    exit 1
fi

DB_IP="$(_cfg_get tools_db_ip)"
DB_NAME="$(_cfg_get tools_db_name)"
DB_USER="$(_cfg_get tools_db_user)"
DB_PASS="$(_cfg_get tools_db_pass)"
COUNTRY="$(_cfg_get country)"
MACHINE_KIND="$(_cfg_get machine)"
ENV_CODE="n1-$(echo "${COUNTRY}" | tr 'A-Z' 'a-z')"
[[ "$MACHINE_KIND" == *"DEV"* || "$MACHINE_KIND" == *"dev"* ]] && ENV_CODE="${ENV_CODE}-dev"

if [[ -z "$DB_IP" || -z "$DB_PASS" || -z "$DB_NAME" || -z "$DB_USER" ]]; then
    _log "❌ 無法從 config.php 讀取 tools_db_*（ip='$DB_IP' name='$DB_NAME' user='$DB_USER' pass='${DB_PASS:+***}'）"
    exit 1
fi

# ── psql helper ────────────────────────────────────────────────────────────
_psql() {
    PGPASSWORD="$DB_PASS" psql -h "$DB_IP" -U "$DB_USER" -d "$DB_NAME" \
        -AtX -v ON_ERROR_STOP=1 "$@"
}

# ── SSH 採樣命令（remote 端跑，最後一行 stdout 用 MARK| 開頭以避開 MOTD/.bashrc 污染）──
# CPU 直接讀 /proc/stat 兩次（間隔 1 秒），算 delta(idle+iowait)/delta(total)，
# 不依賴 vmstat 欄位數（不同 distro / procps 版本欄數可能不同：少 st / wa 都會
# 讓 awk 的 $15 變空字串，100-空 = 100，造成假 100% — 這是 v1 上線後 N1-EN/TW
# 整片紅標的根因；改純 procfs 後完全擺脫此問題）
# RAM/Disk 同樣只看 procfs / 標準工具的明確欄位（mt/mu, dt/du）
# DISK 看 / mountpoint
read -r -d '' REMOTE_CMD <<'REMOTE_EOF' || true
read _label u1 n1 s1 i1 w1 q1 sq1 st1 g1 gn1 _rest < /proc/stat
sleep 1
read _label u2 n2 s2 i2 w2 q2 sq2 st2 g2 gn2 _rest < /proc/stat
total_d=$(( (u2+n2+s2+i2+w2+q2+sq2+st2+g2+gn2) - (u1+n1+s1+i1+w1+q1+sq1+st1+g1+gn1) ))
idle_d=$(( (i2-i1) + (w2-w1) ))
if [ "$total_d" -gt 0 ]; then
    cpu=$(awk -v id="$idle_d" -v td="$total_d" 'BEGIN { printf "%.1f", 100*(1 - id/td) }')
else
    cpu=""
fi
read mt mu < <(free -m 2>/dev/null | awk '/^Mem:/ {print $2,$3}')
read dt du < <(df -m / 2>/dev/null | awk 'NR==2 {print $2,$3}')
ld=$(awk '{print $1}' /proc/loadavg 2>/dev/null)
# WorldServer 數量（admin_machine_spec.php Phase D，僅 worlddb 機會用到，但所有機都採樣保留）
# 排 grep 自身與 rsync（rsync 同步 WorldServer/ 目錄時 cmdline 會帶 WorldServer 字串）
ws=$(ps -ef | grep WorldServer | grep -E -v 'grep|rsync' | wc -l 2>/dev/null)
echo "MARK|${cpu:-}|${mu:-}|${mt:-}|${du:-}|${dt:-}|${ld:-}|${ws:-}"
REMOTE_EOF

# -n 把 ssh stdin redirect 到 /dev/null：避免在背景平行跑時偷吃 parent shell 的 stdin
# （沒加 -n 的話會把 here-string 的 hosts list 一路啃光，loop 第 9 個 read 就 EOF）
SSH_OPTS=(-n -o BatchMode=yes -o ConnectTimeout=8 -o StrictHostKeyChecking=accept-new -o LogLevel=ERROR)

# ── 解析 /etc/hosts：挑 primary alias，過濾 RFC1918，去重 by IP ──────────
parse_hosts() {
    awk '
    function is_rfc1918(ip,    a) {
        n = split(ip, a, ".")
        if (n != 4) return 0
        if (a[1] == "10") return 1
        if (a[1] == "172" && a[2]+0 >= 16 && a[2]+0 <= 31) return 1
        if (a[1] == "192" && a[2]+0 == 168) return 1
        return 0
    }
    /^[[:space:]]*#/  { next }
    /^[[:space:]]*$/  { next }
    {
        if ($1 !~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) next
        ip = $1
        if (!is_rfc1918(ip)) next
        if (ip in seen) next
        seen[ip] = 1
        # primary alias 優先 DB-class，沒命中就用第一個 alias
        primary = $2
        for (i = 2; i <= NF; i++) {
            if ($i ~ /^(ACCOUNTDB|GAMEDB[0-9]+|WORLDDB[0-9]+|SOCIETYDB[0-9]+|CHATS[0-9]*|RANKS[0-9]*|CTRL[0-9]*|TICKET|LOGIN[0-9]*)$/) {
                primary = $i
                break
            }
        }
        printf "%s|%s\n", ip, primary
    }
    ' /etc/hosts
}

# ── SQL 字串消毒（quote single-quote → 兩個 single-quote）─────────────────
_sql_escape() { echo "$1" | sed "s/'/''/g"; }

# ── 採樣單台 → 印一行 SQL value tuple ────────────────────────────────────
sample_one() {
    local ip="$1" target="$2"
    local out rc data
    # ssh 會把 stdout / stderr 都帶回（MOTD / .bashrc 印出的東西都在），所以
    # 抓 `MARK|` 開頭的那一行才是真正的 metric 輸出，避免 banner 污染
    out=$(timeout 12 ssh "${SSH_OPTS[@]}" "n1admin@${ip}" "$REMOTE_CMD" 2>&1)
    rc=$?
    data=$(printf '%s\n' "$out" | grep -m1 '^MARK|' || true)
    if [[ $rc -ne 0 || -z "$data" ]]; then
        local err
        err=$(printf '%s\n' "$out" | head -1 | head -c 200)
        printf "('%s','%s','%s',false,NULL,NULL,NULL,NULL,NULL,NULL,'%s',NULL)\n" \
            "$ENV_CODE" "$(_sql_escape "$target")" "$ip" "$(_sql_escape "$err")"
        return
    fi
    local _mark cpu ru rt du dt ld ws
    IFS='|' read -r _mark cpu ru rt du dt ld ws <<< "$data"
    # 偵測範圍策略：只有 WORLDDB 機才記實際 WS 數；其餘一律寫 -1（語意：未設定偵測）
    # 0 / 正整數 = 真的需要被注意（閒置 / 建議 / 警示）；-1 = 不在偵測範圍 → view 會過濾掉
    if [[ ! "$target" =~ ^WORLDDB[0-9]+$ ]]; then
        ws="-1"
    fi
    # 把空字串轉成 SQL NULL，數字保留原樣（依靠 PostgreSQL CAST）
    local v_cpu="NULLIF('${cpu//\'/}','')::numeric"
    local v_ru="NULLIF('${ru//\'/}','')::int"
    local v_rt="NULLIF('${rt//\'/}','')::int"
    local v_du="NULLIF('${du//\'/}','')::bigint"
    local v_dt="NULLIF('${dt//\'/}','')::bigint"
    local v_ld="NULLIF('${ld//\'/}','')::numeric"
    local v_ws="NULLIF('${ws//\'/}','')::int"
    printf "('%s','%s','%s',true,%s,%s,%s,%s,%s,%s,NULL,%s)\n" \
        "$ENV_CODE" "$(_sql_escape "$target")" "$ip" \
        "$v_cpu" "$v_ru" "$v_rt" "$v_du" "$v_dt" "$v_ld" "$v_ws"
}

# ── 主流程 ────────────────────────────────────────────────────────────────
START_TS=$(date '+%F %T')
TMP_VALUES=$(mktemp)
trap 'rm -f "$TMP_VALUES"' EXIT

# 並行採樣（最多 8 條 SSH 並行）
export -f sample_one _sql_escape
export ENV_CODE SSH_OPTS REMOTE_CMD
# xargs 的 export-function 在 alpine /bin/sh 不行，用 bash 顯式呼叫
hosts_list=$(parse_hosts)
host_count=$(echo "$hosts_list" | grep -c '|' || true)

if [[ "$host_count" -eq 0 ]]; then
    _log "⚠️  /etc/hosts 沒有可採樣的 RFC1918 機器，跳過"
    exit 0
fi

# 注意：用 here-string + { ... } group，避免 pipe 把 while 推進 subshell（會導致
# `wait` 看不到 & 的子孫、redirect 也只蓋到 while 本身、最終只有第 1 筆 sample 落檔）
{
    while IFS='|' read -r ip target; do
        [[ -z "$ip" || -z "$target" ]] && continue
        sample_one "$ip" "$target" &
        # 限制並行 8
        if (( $(jobs -r | wc -l) >= 8 )); then wait -n; fi
    done <<< "$hosts_list"
    wait
} > "$TMP_VALUES"

sample_count=$(wc -l < "$TMP_VALUES")
if [[ "$sample_count" -eq 0 ]]; then
    _log "⚠️  採樣無結果（hosts 解析 $host_count 但 sample_one 沒輸出？）"
    exit 0
fi

# 一次 INSERT
{
    echo "INSERT INTO public.machine_usage_sample"
    echo "  (env_code, target, target_ip, ssh_ok, cpu_pct, ram_used_mb, ram_total_mb, disk_used_mb, disk_total_mb, load1, err_msg, worldserver_count)"
    echo "VALUES"
    paste -sd ',' "$TMP_VALUES"
    echo ";"
} | _psql >/dev/null 2>>"$LOG_FILE"
ins_rc=$?

# Prune：把當前 env 中不在 hosts 的 IP 清掉（移機後 1 個 cron tick 就乾淨）
# Guards：
#   1. host_count > 0（前面已 exit 0，這裡保險）— 防 parse_hosts 解到 0 筆把整表誤清
#   2. ips_sql 非空 — 防 awk 失敗產出空字串造成 NOT IN () 語法錯
#   3. env_code 過濾 — 即便不同環境共用 DB（目前不會）也不影響其他環境
pruned=0
if [[ "$host_count" -gt 0 && -n "$ENV_CODE" ]]; then
    ips_sql=$(echo "$hosts_list" | awk -F'|' '$1 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ {printf "%s'\''%s'\''", sep, $1; sep=","}')
    if [[ -n "$ips_sql" ]]; then
        pruned=$(_psql -c "DELETE FROM public.machine_usage_sample WHERE env_code='$ENV_CODE' AND target_ip NOT IN ($ips_sql) RETURNING 1" 2>/dev/null | wc -l)
    else
        _log "⚠️  ips_sql 為空（awk 解 hosts_list 失敗？hosts=$host_count），跳過 prune"
    fi
fi

# Cleanup >90 天（每次 cron 都跑，便宜；對 prune 抓不到的歷史紀錄 fallback）
deleted=$(_psql -c "DELETE FROM public.machine_usage_sample WHERE sampled_at < NOW() - INTERVAL '90 days' RETURNING 1" 2>/dev/null | wc -l)

if [[ $ins_rc -eq 0 ]]; then
    _log "✓ env=$ENV_CODE samples=$sample_count prune=$pruned cleanup=$deleted ($START_TS → $(date '+%T'))"
else
    _log "❌ env=$ENV_CODE INSERT 失敗 rc=$ins_rc samples=$sample_count"
    exit 1
fi
