#!/usr/bin/env bash
# =============================================================================
# onx-user-suspend — Suspend a hosting account (v89 cPanel-style rewrite)
#
# Purpose:
#   Locks Linux password, sets shell to /sbin/nologin, and deploys per-driver
#   rewrite markers that intercept ALL HTTP requests on the user's docroots and
#   serve a branded suspended.html page (cPanel-style).
#
#   v89 değişikliği — Apache "mv vhost.conf vhost.conf.suspended" PATERNİ KALDIRILDI:
#     - Eski davranış sadece Apache'de işe yarıyor, ziyaretçi 404 alıyor.
#     - Yeni davranış: vhost AKTİF kalır, .htaccess + Nginx include + OLS marker +
#       Caddy marker dosyalarıyla tüm istekler /index.html'ye yönlendirilir.
#     - onx-default-page-deploy sysapi'si index.html'e markalı "askıya alındı"
#       şablonunu basar (suspended.html.stub).
#     - Hangi web server aktifse onu dispatcher seçer; driver-agnostic.
#
# Input (stdin JSON):
#   {
#     "username": "onx_xxxx",       -- required
#     "reason":   "non-payment"     -- optional, default "suspended by administrator"
#   }
#
# Output (stdout JSON):
#   {
#     "username":     "onx_xxxx",
#     "status":       "suspended",
#     "suspended_at": "<ISO8601>",
#     "reason":       "...",
#     "method":       "rewrite",
#     "drivers":      ["apache","nginx","ols","caddy"]
#   }
#
# Exit codes: 0=ok 1=invalid-input 2=preflight-fail 3=exec-fail
#             4=rolled-back 5=rollback-failed
#
# Sudoers entry needed:
#   apache ALL=(root) NOPASSWD: /usr/local/onoxsoft/bin/onx-user-suspend
#   Defaults!/usr/local/onoxsoft/bin/onx-user-suspend !requiretty
#   Defaults!/usr/local/onoxsoft/bin/onx-user-suspend log_output, log_input
#
# Deployed to: /usr/local/onoxsoft/bin/onx-user-suspend
# =============================================================================

set -euo pipefail

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

# ── Constants ─────────────────────────────────────────────────────────────────
META_DIR=".onox"
# v88 Agent 5 — Suspend öncesi shadow hash yedeği (root-only)
SHADOW_BACKUP_DIR="/var/onox/suspended-passwords"

# v89 driver marker konumları (cPanel rewrite pattern)
NGINX_INC_DIR="/etc/nginx/onoxsoft-suspend.d"
OLS_VHOST_BASE="/usr/local/lsws/conf/vhosts"
CADDY_SUSPEND_DIR="/etc/caddy/onoxsoft-suspend.d"
SUSPEND_MARKER="# v89: ONOXSOFT suspended rewrite — DO NOT EDIT"

# ── Dependencies ──────────────────────────────────────────────────────────────
command -v jq      >/dev/null 2>&1 || { printf '{"error":"jq required"}\n' >&2; exit 2; }
command -v usermod >/dev/null 2>&1 || { printf '{"error":"usermod required"}\n' >&2; exit 2; }
require_root

# ── Read & parse stdin ────────────────────────────────────────────────────────
INPUT=$(cat)
onx_require_json "${INPUT}"

USERNAME=$(onx_json_get "${INPUT}" "username")
REASON=$(onx_json_get   "${INPUT}" "reason" "suspended by administrator")

# ── 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}")

[[ -z "${REASON}" ]] && onx_die 1 "reason is required"

# ── Preflight ─────────────────────────────────────────────────────────────────
# IDEMPOTENT: user yoksa (provision'sız orphan) panel-level suspend için
# success dön — DB durumu zaten suspended olarak işaretlenir.
USER_EXISTS=0
if id "${USERNAME}" &>/dev/null; then
    USER_EXISTS=1
fi

# Eğer Linux user yoksa: clean DB-only suspend
if [[ "${USER_EXISTS}" -eq 0 ]]; then
    onx_log "user does not exist — db-only suspend"
    onx_json_out \
        "username"      "${USERNAME}" \
        "status"        "suspended" \
        "suspended_at"  "$(date -u +%FT%TZ)" \
        "reason"        "${REASON}" \
        "method"        "db-only" \
        "note"          "user_not_present_db_only"
    exit 0
fi

trap 'onx_rollback_run' ERR

# ── v88 Agent 5 — Backup mevcut shadow hash (unsuspend için restore) ──────────
# usermod -L parolayı "!" ile prefix'leyerek invalidate eder; orijinal hash
# unsuspend sırasında geri yüklenmezse kullanıcı kendi parolasını bilemez.
# Bu blok shadow line'ın TAMAMINI /var/onox/suspended-passwords/<user>.shadow
# dosyasına kopyalar (root-only 0600).
mkdir -p "${SHADOW_BACKUP_DIR}"
chmod 700 "${SHADOW_BACKUP_DIR}"
SHADOW_BACKUP_FILE="${SHADOW_BACKUP_DIR}/${USERNAME}.shadow"
if getent shadow "${USERNAME}" >/dev/null 2>&1; then
    getent shadow "${USERNAME}" | head -1 > "${SHADOW_BACKUP_FILE}"
    chmod 600 "${SHADOW_BACKUP_FILE}"
    chown root:root "${SHADOW_BACKUP_FILE}" 2>/dev/null || true
    onx_log "shadow hash backed up: ${SHADOW_BACKUP_FILE}"
    onx_rollback_register "rm -f '${SHADOW_BACKUP_FILE}' 2>/dev/null || true"
else
    onx_log "WARN: getent shadow boş, backup atlandı: ${USERNAME}"
fi

# ── Lock password ─────────────────────────────────────────────────────────────
usermod -L "${USERNAME}"
onx_rollback_register "usermod -U '${USERNAME}' 2>/dev/null || true"
onx_log "password locked: ${USERNAME}"

# ── Disable shell ─────────────────────────────────────────────────────────────
PREV_SHELL=$(getent passwd "${USERNAME}" | cut -d: -f7)
usermod -s /sbin/nologin "${USERNAME}"
onx_rollback_register "usermod -s '${PREV_SHELL}' '${USERNAME}' 2>/dev/null || true"
onx_log "shell set to /sbin/nologin: ${USERNAME}"

# ── v89: Discover all docroots for this user ─────────────────────────────────
# Apache .htaccess yöntemi PER-DOCROOT işliyor (vhost'lar shared kalıyor),
# bu yüzden hem primary public_html'i hem de varsa addon domain docroot'larını
# bulup hepsine markerı kuruyoruz. Glob "find" ile çıkar:
#   <home>/public_html, <home>/domains/*/public_html, <home>/<addon>/public_html
DOCROOTS=()
[[ -d "${USER_HOME}/public_html" ]] && DOCROOTS+=("${USER_HOME}/public_html")
while IFS= read -r d; do
    [[ -n "$d" && -d "$d" ]] || continue
    # primary'yi tekrar ekleme
    skip=0
    for existing in "${DOCROOTS[@]+"${DOCROOTS[@]}"}"; do
        [[ "$existing" == "$d" ]] && skip=1 && break
    done
    [[ "$skip" -eq 0 ]] && DOCROOTS+=("$d")
done < <(find "${USER_HOME}" -mindepth 2 -maxdepth 4 -type d -name 'public_html' 2>/dev/null || true)

onx_log "discovered ${#DOCROOTS[@]} docroot(s) for ${USERNAME}"

# ── v89: Apache/LiteSpeed .htaccess rewrite — per-docroot ────────────────────
# Tüm istekleri /index.html'ye 503 ile yönlendir.
# /__onx-health bypass'ı health-check probe'lar için.
# Mevcut .htaccess varsa .user-bak'a kopyala, başına bizim block'u prepend et.
HTACCESS_WRITTEN=()
for DOCROOT in "${DOCROOTS[@]+"${DOCROOTS[@]}"}"; do
    [[ "${DOCROOT}" == *".."* ]] && { onx_log "WARN docroot has '..': ${DOCROOT}"; continue; }
    case "${DOCROOT}" in
        /home/*|/var/www/*) : ;;
        *) onx_log "WARN refusing docroot outside /home or /var/www: ${DOCROOT}"; continue ;;
    esac

    SNIPPET="${DOCROOT}/.htaccess.onx-suspended"
    cat > "${SNIPPET}" <<'EOF'
# v89: ONOXSOFT suspended rewrite — DO NOT EDIT
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/index\.html$
RewriteCond %{REQUEST_URI} !^/__onx-health$
RewriteRule ^(.*)$ /index.html [L]
ErrorDocument 503 /index.html
EOF
    chmod 644 "${SNIPPET}" 2>/dev/null || true

    if [[ -f "${DOCROOT}/.htaccess" ]]; then
        if ! grep -q "ONOXSOFT suspended rewrite" "${DOCROOT}/.htaccess" 2>/dev/null; then
            cp -p "${DOCROOT}/.htaccess" "${DOCROOT}/.htaccess.onx-user-backup"
            cat "${SNIPPET}" "${DOCROOT}/.htaccess.onx-user-backup" > "${DOCROOT}/.htaccess"
            HTACCESS_WRITTEN+=("${DOCROOT}/.htaccess")
            onx_log "htaccess prepended (user backup saved): ${DOCROOT}/.htaccess"
        else
            onx_log "htaccess already has suspended block: ${DOCROOT}/.htaccess"
        fi
    else
        cp "${SNIPPET}" "${DOCROOT}/.htaccess"
        HTACCESS_WRITTEN+=("${DOCROOT}/.htaccess")
        onx_log "htaccess written fresh: ${DOCROOT}/.htaccess"
    fi
    # Restore user-bak on rollback (best effort)
    onx_rollback_register "[[ -f '${DOCROOT}/.htaccess.onx-user-backup' ]] && mv -f '${DOCROOT}/.htaccess.onx-user-backup' '${DOCROOT}/.htaccess' || rm -f '${DOCROOT}/.htaccess'; rm -f '${SNIPPET}'"
done

# ── v89: Nginx per-user include (driver-agnostic vhost picks it up) ──────────
# Nginx vhost şablonu opsiyonel olarak şu satırı içerebilir:
#   include /etc/nginx/onoxsoft-suspend.d/<USERNAME>.conf*;
# Bu dosya server { ... } bağlamı içinde olduğu için yalnızca `location` direktifi
# içerebilir. `try_files /index.html =503` askıdaki index.html'i 503 ile sunar.
mkdir -p "${NGINX_INC_DIR}"
NGINX_INC_FILE="${NGINX_INC_DIR}/${USERNAME}.conf"
# v3.38: Marker dosyası SADECE `set $onx_suspended 1` içerir.
# Vhost template'i (vhost-nginx.conf.stub) bu değişkeni okuyup if ($onx_suspended = 1) { return 503 } yapar.
# Eskiden `location /` blokları çakışıyor + duplicate location nginx fail veriyordu.
cat > "${NGINX_INC_FILE}" <<EOF
# v3.38: ONOXSOFT suspended for ${USERNAME} (auto-generated)
# Vhost template'i bu değişkeni okur ve 503 + suspended page döndürür.
set \$onx_suspended 1;
EOF
chmod 644 "${NGINX_INC_FILE}" 2>/dev/null || true
onx_rollback_register "rm -f '${NGINX_INC_FILE}'"
onx_log "nginx include written: ${NGINX_INC_FILE}"

if systemctl is-active --quiet nginx 2>/dev/null; then
    systemctl reload nginx 2>/dev/null || onx_log "WARN nginx reload failed (continuing)"
fi

# ── v3.39: OLS per-vhost marker file (path mismatch fix) ─────────────────────
# OLS vhost dizinleri "<USERNAME>-<DOMAIN>" formatında (örn onx_65qyec-bisaglik.com).
# Eski script ${OLS_VHOST_BASE}/${USERNAME} prefix kullanıyordu — yanlış,
# o dizin asla yaratılmaz. CUSTOMER HER DOMAIN'i için ayrı vhost dizini var,
# her birine ayrı marker yazılmalı. Glob ile tüm match'leri yakalıyoruz.
# Vhost template'i: RewriteCond /usr/local/lsws/conf/vhosts/<user>-<domain>/.onx-suspended -f
OLS_MARKER_COUNT=0
shopt -s nullglob
for ols_vhost in "${OLS_VHOST_BASE}/${USERNAME}-"*/; do
    if [[ -d "${ols_vhost}" ]]; then
        touch "${ols_vhost}/.onx-suspended"
        chmod 644 "${ols_vhost}/.onx-suspended" 2>/dev/null || true
        onx_rollback_register "rm -f '${ols_vhost}/.onx-suspended'"
        OLS_MARKER_COUNT=$((OLS_MARKER_COUNT + 1))
    fi
done
shopt -u nullglob
onx_log "OLS markers written: ${OLS_MARKER_COUNT} vhost(s) for ${USERNAME}"

# ── v89: Caddy per-site marker file ──────────────────────────────────────────
# Caddy vhost şablonu (Caddyfile import) marker dosyasını kontrol eder.
mkdir -p "${CADDY_SUSPEND_DIR}"
chmod 755 "${CADDY_SUSPEND_DIR}" 2>/dev/null || true
# v3.38: Caddy marker artık full snippet (Caddyfile.stub'da `import ... ${USERNAME}.caddy*` ile load edilir)
# Glob `*` sayesinde Caddy 2.6+ dosya yoksa silent skip eder, hata vermez.
# Snippet site block içine yerleştiğinde handle/rewrite 503 yapar.
CADDY_MARKER_FILE="${CADDY_SUSPEND_DIR}/${USERNAME}.caddy"
cat > "${CADDY_MARKER_FILE}" <<EOF
# v3.38: ONOXSOFT suspended snippet for ${USERNAME} (auto-generated)
# Caddyfile site block içine import edilir.
@onx_suspended_${USERNAME} not path /__onx-health
handle @onx_suspended_${USERNAME} {
    root * /home/users/${USERNAME}/public_html
    rewrite * /index.html
    file_server
    header * Content-Type "text/html; charset=utf-8"
}
EOF
chmod 644 "${CADDY_MARKER_FILE}" 2>/dev/null || true
onx_rollback_register "rm -f '${CADDY_MARKER_FILE}'"
onx_log "Caddy marker snippet written: ${CADDY_MARKER_FILE}"

if systemctl is-active --quiet caddy 2>/dev/null; then
    systemctl reload caddy 2>/dev/null || onx_log "WARN caddy reload failed (continuing)"
fi

# ── v89: Apache reload (best effort — vhost'lar değişmedi, .htaccess runtime) ─
# Apache .htaccess'i her istekte yeniden okur (AllowOverride All); reload zorunlu
# değil ama önbellek edinme amacıyla çağırırız. httpd çalışmıyorsa atla.
if systemctl is-active --quiet httpd 2>/dev/null; then
    systemctl reload httpd 2>/dev/null || onx_log "WARN httpd reload failed (continuing)"
fi
# LSWS API restart (OLS marker dosyasını okuması için)
if systemctl is-active --quiet lsws 2>/dev/null; then
    # OLS soft restart marker dosyalarını re-read için yeterli
    /usr/local/lsws/bin/lswsctrl reload 2>/dev/null || \
        systemctl reload lsws 2>/dev/null || \
        onx_log "WARN lsws reload failed (continuing)"
fi

# ── Write suspension.json ─────────────────────────────────────────────────────
SUSPENDED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
mkdir -p "${USER_HOME}/${META_DIR}" 2>/dev/null || true

printf '{"username":"%s","status":"suspended","suspended_at":"%s","reason":"%s","method":"rewrite"}\n' \
    "${USERNAME}" "${SUSPENDED_AT}" "$(printf '%s' "${REASON}" | sed 's/"/\\"/g')" \
    > "${USER_HOME}/${META_DIR}/suspension.json"
chmod 600 "${USER_HOME}/${META_DIR}/suspension.json" 2>/dev/null || true
onx_log "suspension.json written"

# ── v87+v89: Deploy "suspended" branded placeholder to primary docroot ────────
# PHP-side AccountSuspended listener tüm domain docroot'larını dolaşır; bu blok
# sadece primary docroot için emniyet ağı (panel queue gecikirse de görünür).
# v89: artık ZORUNLU — rewrite hedefi olan /index.html bu sayfayı içermeli.
if [[ -x "${SCRIPT_DIR}/onx-default-page-deploy" && -d "${USER_HOME}/public_html" ]]; then
    DEPLOY_PAYLOAD=$(jq -nc \
        --arg u "${USERNAME}" \
        --arg d "" \
        --arg r "${USER_HOME}/public_html" \
        --arg sa "${SUSPENDED_AT}" \
        --arg rsn "${REASON}" \
        '{
            username: $u,
            domain: $d,
            state: "suspended",
            docroot: $r,
            lang: "tr",
            suspended_at: $sa,
            reason: $rsn
        }')
    echo "${DEPLOY_PAYLOAD}" | "${SCRIPT_DIR}/onx-default-page-deploy" >/dev/null 2>&1 || \
        onx_log "WARN: default-page-deploy suspended skipped"
fi

# ── Output ────────────────────────────────────────────────────────────────────
onx_json_out \
    "username"     "${USERNAME}" \
    "status"       "suspended" \
    "suspended_at" "${SUSPENDED_AT}" \
    "reason"       "${REASON}" \
    "method"       "rewrite" \
    "docroots"     "${#DOCROOTS[@]}"
