#!/usr/bin/env bash
# =============================================================================
# onx-account-doctor (v3.35) — Customer account health audit + auto-fix.
#
# DB'de status=active olan tüm hesapları taraması, her biri için doğrulama:
#   1. Linux user var mı (id <username>)
#   2. /home/users/<username>/public_html var mı + permissions OK mu (webserver group)
#   3. FPM pool config var mı (/etc/opt/remi/php{V}/php-fpm.d/{user}.conf)
#   4. FPM socket aktif mi (/var/opt/remi/php{V}/run/php-fpm/{user}.sock)
#   5. Aktif driver vhost'u var mı:
#      - apache: /etc/httpd/conf.d/<user>-<domain>.conf
#      - nginx: /etc/nginx/conf.d/sites/<user>-<domain>.conf
#      - ols: /usr/local/lsws/conf/vhosts/<user>-<domain>/vhconf.conf
#      - caddy: /etc/caddy/sites/<user>-<domain>.caddy
#
# Input (stdin JSON):
#   { "auto_fix": true|false, "username_filter": "onx_xxx" (optional) }
#
# Output:
#   { "ok": true, "checked": N, "healthy": M, "issues": [...], "fixed": K }
#
# Bağımlılık: jq, mysql (root via .env veya defaults-extra-file).
# Sysapi tarafından çağrılır; sysapi audit log'a yazılır.
# =============================================================================

# v3.36 DEFANSIF MODE: set -u kaldırıldı (boş bash array edge case'lerini
# tetikliyordu — `${USER_ISSUES[@]}` boş array bash <4.4'te unbound).
# pipefail kalmalı ama ERR trap ile stderr'e tam stack trace bas.
set -eo pipefail

# ERR trap: hata noktasını stderr'e bas, normalde silent ölüm yerine debug bilgi
trap 'rc=$?; echo "[onx-account-doctor ERROR] exit=$rc line=$LINENO cmd=$BASH_COMMAND" >&2' ERR

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"

# common.sh source defansif
if [[ ! -f "${SCRIPT_DIR}/_lib/common.sh" ]]; then
    echo '{"error":"common.sh bulunamadi: '"${SCRIPT_DIR}/_lib/common.sh"'"}' >&2
    exit 10
fi
source "${SCRIPT_DIR}/_lib/common.sh"

INPUT_RAW="$(cat 2>/dev/null || echo '{}')"
[[ -z "${INPUT_RAW}" ]] && INPUT_RAW='{}'
echo "${INPUT_RAW}" | jq -e 'type == "object"' >/dev/null 2>&1 || INPUT_RAW='{}'

AUTO_FIX=$(onx_json_get   "${INPUT_RAW}" "auto_fix"        "false")
USER_FILTER=$(onx_json_get "${INPUT_RAW}" "username_filter" "")

# Read DB credentials from /opt/onoxsoft/.env
ENV_FILE="/opt/onoxsoft/.env"
if [[ ! -f "${ENV_FILE}" ]]; then
    onx_die 2 "Panel .env bulunamadı: ${ENV_FILE}"
fi
DB_NAME=$(grep -E '^DB_DATABASE=' "${ENV_FILE}" | head -1 | cut -d'=' -f2)
DB_USER=$(grep -E '^DB_USERNAME=' "${ENV_FILE}" | head -1 | cut -d'=' -f2)
DB_PASS=$(grep -E '^DB_PASSWORD=' "${ENV_FILE}" | head -1 | cut -d'=' -f2)
[[ -z "${DB_NAME}" || -z "${DB_USER}" || -z "${DB_PASS}" ]] && \
    onx_die 3 "DB credentials .env'den okunamadı"

# Detect active webserver driver
ACTIVE_DRIVER="apache"
for ws_pair in "httpd:apache" "openlitespeed:ols" "lshttpd:ols" "nginx:nginx" "caddy:caddy"; do
    unit="${ws_pair%%:*}"
    key="${ws_pair##*:}"
    if systemctl is-active --quiet "$unit" 2>/dev/null; then
        ACTIVE_DRIVER="$key"
        break
    fi
done

# Query active accounts
# v3.41: default_php_version kolonu opsiyonel — yoksa SHOW COLUMNS ile detect et,
# yoksa fallback olarak username + sabit "8.2" döndür. Önceki versiyonda kolon
# yoksa query fail ediyordu (Unknown column) ve "Aktif hesap yok" yanıltıcı mesajı.
PHP_VER_COL=$(MYSQL_PWD="${DB_PASS}" mysql -u"${DB_USER}" -N -B "${DB_NAME}" -e \
    "SHOW COLUMNS FROM accounts LIKE '%php_version%';" 2>/dev/null | head -1 | cut -f1 || true)
if [[ -z "${PHP_VER_COL}" ]]; then
    PHP_VER_COL="'8.2'"  # SQL literal fallback
    PHP_VER_SELECT="'8.2'"
else
    PHP_VER_SELECT="IFNULL(${PHP_VER_COL}, '8.2')"
fi

QUERY="SELECT username, ${PHP_VER_SELECT} FROM accounts WHERE status='active'"
if [[ -n "${USER_FILTER}" ]]; then
    QUERY="${QUERY} AND username='${USER_FILTER}'"
fi
QUERY="${QUERY} ORDER BY id;"

# v3.39: pipefail+set-e ile MySQL query başarısız olunca `$(...)` exit nonzero
# → script ÖLÜYORDU. `|| true` ile yumuşat. Sonuç boş ise normal flow devam.
ACCOUNTS=$(MYSQL_PWD="${DB_PASS}" mysql -u"${DB_USER}" -N -B "${DB_NAME}" -e "${QUERY}" 2>/dev/null || true)
if [[ -z "${ACCOUNTS}" ]]; then
    # v3.41: defansif daha açıklayıcı mesaj — kolon var mı, DB erişimi var mı
    DB_TEST=$(MYSQL_PWD="${DB_PASS}" mysql -u"${DB_USER}" -N -B "${DB_NAME}" -e "SELECT COUNT(*) FROM accounts;" 2>/dev/null || echo "0")
    DIAG_MSG="Aktif hesap yok"
    if [[ "${DB_TEST}" == "0" ]] || [[ -z "${DB_TEST}" ]]; then
        DIAG_MSG="DB erişimi yok (credentials sorunu)"
    fi
    json_ok "$(jq -n --arg msg "${DIAG_MSG}" --arg col "${PHP_VER_COL}" \
        '{ok:true, checked:0, healthy:0, issues:[], fixed:0, message:$msg, php_ver_col:$col}')"
    exit 0
fi

# Per-account check
CHECKED=0
HEALTHY=0
FIXED=0
ISSUES_JSON="[]"

while IFS=$'\t' read -r username php_version; do
    [[ -z "${username}" ]] && continue
    CHECKED=$((CHECKED + 1))
    USER_ISSUES=()

    # Normalize php version
    if [[ ! "${php_version}" =~ ^[0-9]+\.[0-9]+$ ]]; then
        php_version="8.2"
    fi
    PHP_NODOT=$(echo "${php_version}" | tr -d '.')

    # 1. Linux user check
    # v3.40: Orphan account fix — DB'de var ama Linux user yok → onx-user-add ile recreate
    if ! id "${username}" >/dev/null 2>&1; then
        USER_ISSUES+=("linux_user_missing")
        if [[ "${AUTO_FIX}" == "true" ]]; then
            # Hesap meta'sını çek (email, package, uid, primary domain)
            ACC_META=$(MYSQL_PWD="${DB_PASS}" mysql -u"${DB_USER}" -N -B "${DB_NAME}" -e \
                "SELECT IFNULL(a.email,''), IFNULL(p.name,'starter'), IFNULL(a.linux_uid,'null'), IFNULL(a.disk_quota_mb*1024*1024,'null') FROM accounts a LEFT JOIN packages p ON p.id=a.package_id WHERE a.username='${username}' LIMIT 1;" 2>/dev/null || true)
            IFS=$'\t' read -r ACC_EMAIL ACC_PKG ACC_UID ACC_QUOTA <<< "${ACC_META}"
            ACC_DOMAIN=$(MYSQL_PWD="${DB_PASS}" mysql -u"${DB_USER}" -N -B "${DB_NAME}" -e \
                "SELECT d.name FROM domains d JOIN accounts a ON a.id=d.account_id WHERE a.username='${username}' AND d.type='main' LIMIT 1;" 2>/dev/null || true)
            UID_JSON="${ACC_UID}"
            QUOTA_JSON="${ACC_QUOTA}"
            [[ -z "${UID_JSON}" || "${UID_JSON}" == "null" ]] && UID_JSON="null"
            [[ -z "${QUOTA_JSON}" || "${QUOTA_JSON}" == "null" ]] && QUOTA_JSON="null"
            REQ=$(printf '{"username":"%s","email":"%s","package":"%s","domain":"%s","uid":%s,"quota_bytes":%s,"home_base":"/home/users"}' \
                "${username}" "${ACC_EMAIL:-noreply@localhost}" "${ACC_PKG:-starter}" "${ACC_DOMAIN}" "${UID_JSON}" "${QUOTA_JSON}")
            if echo "${REQ}" | "${SCRIPT_DIR}/onx-user-add" >/dev/null 2>&1; then
                FIXED=$((FIXED + 1))
                onx_log "doctor auto-fix: Linux user recreate ${username}"
            fi
        fi
    fi

    # 2. Home + public_html
    HOME_DIR="/home/users/${username}"
    PUBLIC_HTML="${HOME_DIR}/public_html"
    if [[ ! -d "${PUBLIC_HTML}" ]]; then
        USER_ISSUES+=("public_html_missing")
        # v3.40: public_html eksikse onx-user-add zaten skel kopyaladığı için (yukarıdaki branch)
        # ek müdahale gerek değil. Eğer linux user var ama public_html yok ise:
        if [[ "${AUTO_FIX}" == "true" ]] && id "${username}" >/dev/null 2>&1; then
            mkdir -p "${PUBLIC_HTML}"
            [[ -d /etc/skel.onoxsoft/public_html ]] && cp -rT /etc/skel.onoxsoft/public_html "${PUBLIC_HTML}" 2>/dev/null
            chown -R "${username}:onoxsoft-users" "${PUBLIC_HTML}" 2>/dev/null || true
            chgrp -R webserver "${PUBLIC_HTML}" 2>/dev/null || true
            chmod 0750 "${PUBLIC_HTML}" 2>/dev/null || true
            FIXED=$((FIXED + 1))
            onx_log "doctor auto-fix: public_html created ${username}"
        fi
    else
        DIR_GROUP=$(stat -c '%G' "${PUBLIC_HTML}" 2>/dev/null || echo "")
        if [[ "${DIR_GROUP}" != "webserver" ]]; then
            USER_ISSUES+=("public_html_group_wrong:${DIR_GROUP}")
            if [[ "${AUTO_FIX}" == "true" ]]; then
                chgrp -R webserver "${PUBLIC_HTML}" 2>/dev/null
                find "${PUBLIC_HTML}" -type d -exec chmod g+rX {} + 2>/dev/null
                find "${PUBLIC_HTML}" -type f -exec chmod g+r  {} + 2>/dev/null
                FIXED=$((FIXED + 1))
            fi
        fi
    fi

    # 3. FPM pool config
    POOL_PATH="/etc/opt/remi/php${PHP_NODOT}/php-fpm.d/${username}.conf"
    if [[ ! -f "${POOL_PATH}" ]]; then
        USER_ISSUES+=("fpm_pool_missing:php${PHP_NODOT}")
        if [[ "${AUTO_FIX}" == "true" ]]; then
            echo "{\"username\":\"${username}\",\"php_version\":\"${php_version}\",\"home_dir\":\"${HOME_DIR}\"}" \
                | "${SCRIPT_DIR}/onx-fpm-pool-create" >/dev/null 2>&1 && FIXED=$((FIXED + 1))
        fi
    fi

    # 4. FPM socket
    SOCK_PATH="/var/opt/remi/php${PHP_NODOT}/run/php-fpm/${username}.sock"
    if [[ -f "${POOL_PATH}" ]] && [[ ! -S "${SOCK_PATH}" ]]; then
        USER_ISSUES+=("fpm_socket_missing")
        if [[ "${AUTO_FIX}" == "true" ]]; then
            systemctl restart php${PHP_NODOT}-php-fpm 2>/dev/null && FIXED=$((FIXED + 1))
        fi
    fi

    # 5. Active driver vhost check (best-effort, sadece main domain)
    # v3.40: vhost eksikse auto_fix'te onx-vhost-add çağır (driver-aware)
    PRIMARY_DOMAIN=$(MYSQL_PWD="${DB_PASS}" mysql -u"${DB_USER}" -N -B "${DB_NAME}" -e \
        "SELECT d.name FROM domains d JOIN accounts a ON a.id=d.account_id WHERE a.username='${username}' AND d.type='main' LIMIT 1;" 2>/dev/null || true)
    if [[ -n "${PRIMARY_DOMAIN}" ]]; then
        VHOST_MISSING=""
        case "${ACTIVE_DRIVER}" in
            apache)
                VHOST="/etc/httpd/conf.d/${username}-${PRIMARY_DOMAIN}.conf"
                [[ -f "${VHOST}" ]] || VHOST_MISSING="apache"
                ;;
            nginx)
                VHOST="/etc/nginx/conf.d/sites/${username}-${PRIMARY_DOMAIN}.conf"
                [[ -f "${VHOST}" ]] || VHOST_MISSING="nginx"
                ;;
            ols)
                VHOST="/usr/local/lsws/conf/vhosts/${username}-${PRIMARY_DOMAIN}/vhconf.conf"
                [[ -f "${VHOST}" ]] || VHOST_MISSING="ols"
                ;;
            caddy)
                VHOST="/etc/caddy/sites/${username}-${PRIMARY_DOMAIN}.caddy"
                [[ -f "${VHOST}" ]] || VHOST_MISSING="caddy"
                ;;
        esac
        if [[ -n "${VHOST_MISSING}" ]]; then
            USER_ISSUES+=("${VHOST_MISSING}_vhost_missing:${PRIMARY_DOMAIN}")
            # v3.40: auto_fix=true ise vhost'u yeniden render et
            if [[ "${AUTO_FIX}" == "true" ]] && id "${username}" >/dev/null 2>&1; then
                # Driver mapping (ols → openlitespeed for onx-vhost-add)
                DRIVER_KEY="${VHOST_MISSING}"
                [[ "${DRIVER_KEY}" == "ols" ]] && DRIVER_KEY="openlitespeed"
                REQ=$(printf '{"server":"%s","username":"%s","domain":"%s","doc_root":"%s","php_version":"%s","ssl_enabled":false}' \
                    "${DRIVER_KEY}" "${username}" "${PRIMARY_DOMAIN}" "${PUBLIC_HTML}" "${php_version}")
                if echo "${REQ}" | "${SCRIPT_DIR}/onx-vhost-add" >/dev/null 2>&1; then
                    FIXED=$((FIXED + 1))
                    onx_log "doctor auto-fix: vhost recreate ${ACTIVE_DRIVER}/${username}/${PRIMARY_DOMAIN}"
                fi
            fi
        fi
    fi

    # Aggregate
    if [[ ${#USER_ISSUES[@]} -eq 0 ]]; then
        HEALTHY=$((HEALTHY + 1))
    else
        ISSUES_JSON=$(echo "${ISSUES_JSON}" | jq \
            --arg user "${username}" \
            --argjson list "$(printf '%s\n' "${USER_ISSUES[@]}" | jq -R . | jq -s .)" \
            '. += [{username: $user, issues: $list}]')
    fi
done <<< "${ACCOUNTS}"

# ── v3.85: Sistem inotify watch limiti kontrolü ─────────────────────────────
# Düşük limit (default ~8192) "inotify watch limit reached" → systemctl/fail2ban/
# dosya-izleyiciler bozulur. auto_fix=true ise kalıcı sysctl drop-in yazılır.
INOTIFY_WATCHES=$(cat /proc/sys/fs/inotify/max_user_watches 2>/dev/null || echo 0)
INOTIFY_INSTANCES=$(cat /proc/sys/fs/inotify/max_user_instances 2>/dev/null || echo 0)
[[ "${INOTIFY_WATCHES}" =~ ^[0-9]+$ ]] || INOTIFY_WATCHES=0
[[ "${INOTIFY_INSTANCES}" =~ ^[0-9]+$ ]] || INOTIFY_INSTANCES=0
INOTIFY_OK=true; INOTIFY_FIXED=false
if [[ "${INOTIFY_WATCHES}" -lt 100000 ]]; then
    INOTIFY_OK=false
    if [[ "${AUTO_FIX}" == "true" ]]; then
        if printf 'fs.inotify.max_user_watches=524288\nfs.inotify.max_user_instances=512\n' \
              > /etc/sysctl.d/99-onoxsoft-inotify.conf 2>/dev/null \
           && sysctl -p /etc/sysctl.d/99-onoxsoft-inotify.conf >/dev/null 2>&1; then
            INOTIFY_FIXED=true; INOTIFY_OK=true; FIXED=$((FIXED + 1))
            INOTIFY_WATCHES=$(cat /proc/sys/fs/inotify/max_user_watches 2>/dev/null || echo 524288)
        fi
    fi
fi
SYSTEM_JSON=$(jq -n \
    --argjson watches "${INOTIFY_WATCHES}" \
    --argjson instances "${INOTIFY_INSTANCES}" \
    --argjson ok "$([[ "${INOTIFY_OK}" == "true" ]] && echo true || echo false)" \
    --argjson fixed "$([[ "${INOTIFY_FIXED}" == "true" ]] && echo true || echo false)" \
    '{inotify: {max_user_watches:$watches, max_user_instances:$instances, ok:$ok, fixed:$fixed}}')

json_ok "$(jq -n \
    --argjson checked "${CHECKED}" \
    --argjson healthy "${HEALTHY}" \
    --argjson fixed "${FIXED}" \
    --argjson issues "${ISSUES_JSON}" \
    --argjson system "${SYSTEM_JSON}" \
    --arg active_driver "${ACTIVE_DRIVER}" \
    --argjson auto_fix "$([[ "${AUTO_FIX}" == "true" ]] && echo true || echo false)" \
    '{ok:true, active_driver:$active_driver, checked:$checked, healthy:$healthy, fixed:$fixed, auto_fix:$auto_fix, system:$system, issues:$issues}')"
