#!/usr/bin/env bash
# onx-fpm-pool-switch-version — Move a user's FPM pool from one PHP version to another.
# Also updates the vhost's SetHandler line to point to the new socket.
#
# Input (stdin JSON):
#   username      string  Linux username (onx_xxx)
#   domain        string  Domain name (used to find vhost file)
#   old_version   string  Current PHP version ("8.1")
#   new_version   string  Target PHP version ("8.2")
#   vhost_file    string  vhost config filename (e.g. "onx_xxx-example.com.conf")
#
# Output (stdout JSON):
#   {"switched":true, "old_version":..., "new_version":..., "socket":...}
#
# Exit codes: 0=ok 1=invalid-input 2=preflight-fail 3=exec-fail 4=rolled-back
#
# Deployed to: /usr/local/onoxsoft/bin/onx-fpm-pool-switch-version

set -euo pipefail

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=_lib/common.sh
source "${SCRIPT_DIR}/_lib/common.sh"

VHOST_DIR="/etc/httpd/conf.d/sites"

# ── Read & parse stdin ───────────────────────────────────────────────────────
INPUT=$(cat)

onx_require_json "${INPUT}"

USERNAME=$(onx_json_get "${INPUT}" "username")
DOMAIN=$(onx_json_get "${INPUT}" "domain")
OLD_VERSION=$(onx_json_get "${INPUT}" "old_version")
NEW_VERSION=$(onx_json_get "${INPUT}" "new_version")
VHOST_FILE=$(onx_json_get "${INPUT}" "vhost_file" "")

# ── Input validation ─────────────────────────────────────────────────────────
onx_validate_username "${USERNAME}"

# Runtime detect home directory (handles /home/users/<user> vs /home/<user>)
USER_HOME=$(onx_resolve_home "${USERNAME}" 2>/dev/null || echo "/home/${USERNAME}")

onx_validate_domain "${DOMAIN}"
[[ -z "${OLD_VERSION}" ]] && onx_die 1 "old_version is required"
[[ -z "${NEW_VERSION}" ]] && onx_die 1 "new_version is required"
[[ "${OLD_VERSION}" == "${NEW_VERSION}" ]] && onx_die 1 "old_version and new_version are the same"
[[ "${OLD_VERSION}" =~ ^[0-9]\.[0-9]$ ]] || onx_die 1 "old_version must be x.y format"
[[ "${NEW_VERSION}" =~ ^[0-9]\.[0-9]$ ]] || onx_die 1 "new_version must be x.y format"

OLD_NODOT="${OLD_VERSION//./}"
NEW_NODOT="${NEW_VERSION//./}"

OLD_POOL_PATH="/etc/opt/remi/php${OLD_NODOT}/php-fpm.d/${USERNAME}.conf"
NEW_POOL_DIR="/etc/opt/remi/php${NEW_NODOT}/php-fpm.d"
NEW_POOL_PATH="${NEW_POOL_DIR}/${USERNAME}.conf"
NEW_SOCKET="/var/opt/remi/php${NEW_NODOT}/run/php-fpm/${USERNAME}.sock"
NEW_FPM_SERVICE="php${NEW_NODOT}-php-fpm"
OLD_FPM_SERVICE="php${OLD_NODOT}-php-fpm"

# Vhost file path
if [[ -z "${VHOST_FILE}" ]]; then
  VHOST_FILE="${USERNAME}-${DOMAIN}.conf"
fi
VHOST_PATH="${VHOST_DIR}/${VHOST_FILE}"

# ── Preflight ────────────────────────────────────────────────────────────────
[[ -d "${NEW_POOL_DIR}" ]] || onx_die 2 "php${NEW_NODOT} pool dir not found: ${NEW_POOL_DIR}"
[[ -f "${VHOST_PATH}" ]]   || onx_die 2 "vhost file not found: ${VHOST_PATH}"
command -v apachectl  >/dev/null 2>&1 || onx_die 2 "apachectl not found"
command -v systemctl  >/dev/null 2>&1 || onx_die 2 "systemctl not found"

# Detect user's actual primary group — onx-user-add `onoxsoft-users` shared
# group atıyor; per-user `${USERNAME}` group YOK. Pool template ${USERNAME}
# yazarsak FPM "cannot get gid" + exit 78 verir.
PRIMARY_GROUP=$(id -gn "${USERNAME}" 2>/dev/null || true)
[[ -z "${PRIMARY_GROUP}" ]] && onx_die 2 "cannot detect primary group for ${USERNAME}"

# ── Copy old pool config to new version (preserve settings) ─────────────────
if [[ -f "${OLD_POOL_PATH}" ]]; then
  # Copy and fix the listen path in the new pool
  cp "${OLD_POOL_PATH}" "${NEW_POOL_PATH}"
  sed -i "s|php${OLD_NODOT}/run/php-fpm|php${NEW_NODOT}/run/php-fpm|g" "${NEW_POOL_PATH}"
  # Defensive: eski pool eğer hatalı `group = ${USERNAME}` içeriyorsa düzelt
  sed -i "s|^group = ${USERNAME}$|group = ${PRIMARY_GROUP}|" "${NEW_POOL_PATH}"
  sed -i "s|^listen.group = ${USERNAME}$|listen.group = ${PRIMARY_GROUP}|" "${NEW_POOL_PATH}"
  onx_log "Copied pool config from php${OLD_NODOT} to php${NEW_NODOT} (group=${PRIMARY_GROUP})"
else
  # No old pool — write a minimal pool config using the user's primary group
  onx_log "Old pool config not found; writing minimal pool config"
  cat > "${NEW_POOL_PATH}" <<POOL
[${USERNAME}]
user = ${USERNAME}
group = ${PRIMARY_GROUP}
listen = /var/opt/remi/php${NEW_NODOT}/run/php-fpm/${USERNAME}.sock
listen.owner = apache
listen.group = ${PRIMARY_GROUP}
listen.mode = 0660
pm = ondemand
pm.max_children = 10
pm.process_idle_timeout = 30s
pm.max_requests = 500
php_admin_value[memory_limit] = 256M
php_admin_value[open_basedir] = /home/${USERNAME}:/tmp:/var/lib/php/session
php_admin_value[disable_functions] = exec,system,passthru,shell_exec,proc_open,popen
php_admin_value[date.timezone] = Europe/Istanbul
POOL
fi

# ── Update vhost SetHandler to new socket ───────────────────────────────────
VHOST_BACKUP="${VHOST_PATH}.bak.$$"
cp "${VHOST_PATH}" "${VHOST_BACKUP}"

sed -i "s|php${OLD_NODOT}/run/php-fpm/${USERNAME}.sock|php${NEW_NODOT}/run/php-fpm/${USERNAME}.sock|g" "${VHOST_PATH}"

# ── FPM config validate BEFORE attempting start ──────────────────────────────
# php-fpm -t exit !=0 → pool config geçersiz; gerçek hata mesajını yakala.
FPM_BINARY="/opt/remi/php${NEW_NODOT}/root/usr/sbin/php-fpm"
if [[ -x "${FPM_BINARY}" ]]; then
  FPM_TEST_OUT=$("${FPM_BINARY}" -t 2>&1 || true)
  if echo "${FPM_TEST_OUT}" | grep -qiE 'error|fatal|invalid'; then
    mv "${VHOST_BACKUP}" "${VHOST_PATH}"
    rm -f "${NEW_POOL_PATH}"
    FIRST_ERR=$(echo "${FPM_TEST_OUT}" | grep -iE 'error|fatal|invalid' | head -1)
    onx_die 4 "FPM config test (php${NEW_NODOT}) başarısız: ${FIRST_ERR}"
  fi
fi

# ── Detect panel's own FPM service to avoid killing current HTTP request ─────
# Panel kendi php82-php-fpm üzerinde çalışıyor (configurable via db.env veya
# ONX_PANEL_PHP env var). Eğer NEW service panel'in kendisi ise senkron reload
# panel'in mevcut isteğini (bu switch POST'u) öldürür → 503. Async reload kullan
# + socket appear'a kadar bekle.
PANEL_PHP_VERSION="${ONX_PANEL_PHP:-82}"
PANEL_FPM_SERVICE="php${PANEL_PHP_VERSION}-php-fpm"
NEW_SOCKET_PATH="/var/opt/remi/php${NEW_NODOT}/run/php-fpm/${USERNAME}.sock"

reload_panel_safe() {
  local svc="$1"
  if [[ "${svc}" == "${PANEL_FPM_SERVICE}" ]]; then
    # Panel kendi servisinde — graceful background reload (--no-block: systemd
    # job queue'ya alır, hemen döner, current request'in tamamlanmasına izin verir).
    onx_log "NEW (${svc}) panel'in kendi servisi — async reload"
    systemctl reload "${svc}" --no-block 2>/dev/null || true
    # Socket appearance için kısa bekleme (gerçek reload arka planda devam eder)
    local waited=0
    while [[ ! -S "${NEW_SOCKET_PATH}" && "${waited}" -lt 10 ]]; do
      sleep 1
      waited=$((waited + 1))
    done
    if [[ ! -S "${NEW_SOCKET_PATH}" ]]; then
      return 1
    fi
    return 0
  else
    # Other services — synchronous reload OK (panel etkilenmez)
    systemctl reload "${svc}" 2>&1 && return 0
    systemctl restart "${svc}" 2>&1
  fi
}

# ── Reload new FPM (panel-safe) ──────────────────────────────────────────────
if ! reload_panel_safe "${NEW_FPM_SERVICE}"; then
  mv "${VHOST_BACKUP}" "${VHOST_PATH}"
  rm -f "${NEW_POOL_PATH}"
  JOURNAL_TAIL=$(journalctl -u "${NEW_FPM_SERVICE}" --no-pager -n 5 2>/dev/null | tail -3 | tr '\n' ' ' || true)
  onx_die 4 "Failed to start ${NEW_FPM_SERVICE}; changes rolled back. Journal: ${JOURNAL_TAIL}"
fi

# ── Apache configtest + reload ───────────────────────────────────────────────
if ! apachectl configtest 2>/dev/null; then
  onx_log "configtest failed — rolling back vhost"
  mv "${VHOST_BACKUP}" "${VHOST_PATH}"
  rm -f "${NEW_POOL_PATH}"
  onx_die 4 "apachectl configtest failed; vhost rolled back"
fi
rm -f "${VHOST_BACKUP}"

systemctl reload httpd || onx_die 3 "systemctl reload httpd failed"

# ── Remove old pool config (optional, best-effort) ───────────────────────────
# NOT: OLD_FPM_SERVICE reload YAPMA — panel kendi PHP-FPM'ini kullanıyor
# (örn php82-php-fpm), eğer OLD == panel service ise reload mevcut HTTP
# request'i (kullanıcının "PHP versiyon değiştir" POST'unu) öldürüyor → 503.
# OLD service stale pool config'i RAM'de tutsa da trafik gelmez (vhost
# SetHandler artık NEW socket'e bakıyor); harmless leak. Bir sonraki natural
# FPM reload'da temizlenir.
if [[ -f "${OLD_POOL_PATH}" ]]; then
  rm -f "${OLD_POOL_PATH}"
  onx_log "removed ${OLD_POOL_PATH} (OLD FPM service reload skipped to avoid panel disruption)"
fi

# ── v86.2 — Slice rebind (interim Option B) ──────────────────────────────────
# New PHP version → new FPM master → fresh workers under system.slice; rebind
# them to the customer cgroup slice. Best-effort, non-fatal.
SLICE_REBIND_BIN="${SCRIPT_DIR}/onx-fpm-slice-rebind"
[[ -x "${SLICE_REBIND_BIN}" ]] || SLICE_REBIND_BIN="/usr/local/onoxsoft/bin/onx-fpm-slice-rebind"
REBIND_TRIGGERED="false"
if [[ -x "${SLICE_REBIND_BIN}" ]]; then
  if printf '{"username":"%s"}' "${USERNAME}" | timeout 5 "${SLICE_REBIND_BIN}" >/dev/null 2>&1; then
    REBIND_TRIGGERED="true"
  else
    onx_log "fpm-pool-switch-version: slice-rebind invocation failed (non-fatal) for ${USERNAME}"
  fi
fi

# ── Success ──────────────────────────────────────────────────────────────────
onx_json_out \
  "switched"         "true" \
  "old_version"      "${OLD_VERSION}" \
  "new_version"      "${NEW_VERSION}" \
  "socket"           "${NEW_SOCKET}" \
  "slice_rebind_ran" "${REBIND_TRIGGERED}"
