#!/usr/bin/env bash
# =============================================================================
# onx-backup-restore — Restore an account backup produced by onx-backup-run
#
# Purpose:
#   Verifies the archive checksum, extracts to a sandbox, reads the manifest,
#   then rsyncs home, imports SQL dumps, restores Maildir, and inserts missing
#   DNS records — all gated by the caller-supplied restore_options flags.
#
# Input (stdin JSON):
#   {
#     "account_id":  1,
#     "username":    "onx_acme01",
#     "backup_path": "/var/backups/onoxsoft/onx_acme01-20260514.tar.gz",
#     "restore_options": {
#       "home": true,
#       "dbs":  true,
#       "mail": true,
#       "dns":  true
#     }
#   }
#
# Output (stdout JSON):
#   {
#     "restored": true,
#     "items": {
#       "home":  N_files,
#       "dbs":   N_rows_estimated,
#       "mail":  N_messages,
#       "dns":   N_records
#     },
#     "duration_seconds": N
#   }
#
# Exit codes: 0=ok 1=invalid 2=preflight 3=exec 4=rolled-back 5=rollback-fail
#
# Deployed to: /usr/local/onoxsoft/bin/onx-backup-restore
# =============================================================================

set -euo pipefail

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

readonly BACKUP_ROOT_ALLOWED="/var/backups"
readonly VMAIL_ROOT="/var/vmail"

require_root

# ── Dependencies ─────────────────────────────────────────────────────────────
if [[ "${MOCK_MODE}" != "1" ]]; then
    command -v tar       >/dev/null 2>&1 || onx_die 2 "tar not found"
    command -v rsync     >/dev/null 2>&1 || onx_die 2 "rsync not found"
    command -v sha256sum >/dev/null 2>&1 || onx_die 2 "sha256sum not found"
    command -v mysql     >/dev/null 2>&1 || onx_die 2 "mysql not found"
fi

onx_json_input

ACCOUNT_ID=$(onx_json_field "account_id" "0")
USERNAME=$(onx_json_field   "username")

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

BACKUP_PATH=$(onx_json_field "backup_path")
OPTS_JSON=$(printf '%s' "${INPUT}" | jq -c '.restore_options // {}')

RESTORE_HOME=$(onx_json_get_bool "${OPTS_JSON}" "home" "true")
RESTORE_DBS=$(onx_json_get_bool  "${OPTS_JSON}" "dbs"  "true")
RESTORE_MAIL=$(onx_json_get_bool "${OPTS_JSON}" "mail" "true")
RESTORE_DNS=$(onx_json_get_bool  "${OPTS_JSON}" "dns"  "true")
# v2 manifest — mail metadata (forwarder/alias), FTP, SSL, cron geri yükleme.
# Hepsi default true; eski v1 arşivlerde ilgili dosya/dizin yoksa sessizce atlanır.
RESTORE_MAIL_META=$(onx_json_get_bool "${OPTS_JSON}" "mail_meta" "true")
RESTORE_FTP=$(onx_json_get_bool  "${OPTS_JSON}" "ftp"  "true")
RESTORE_SSL=$(onx_json_get_bool  "${OPTS_JSON}" "ssl"  "true")
RESTORE_CRON=$(onx_json_get_bool "${OPTS_JSON}" "cron" "true")

# ── Validation ───────────────────────────────────────────────────────────────
[[ "${ACCOUNT_ID}" =~ ^[0-9]+$ ]] && [[ "${ACCOUNT_ID}" -gt 0 ]] \
    || onx_die 1 "account_id must be positive integer"
onx_validate_username "${USERNAME}"
[[ -n "${BACKUP_PATH}" ]] || onx_die 1 "backup_path is required"

# Validate backup_path is under /var/backups/
BACKUP_REAL="$(realpath -m "${BACKUP_PATH}" 2>/dev/null || printf '%s' "${BACKUP_PATH}")"
case "${BACKUP_REAL}" in
    ${BACKUP_ROOT_ALLOWED}|${BACKUP_ROOT_ALLOWED}/*) : ;;
    *) onx_die 1 "backup_path '${BACKUP_PATH}' must be under ${BACKUP_ROOT_ALLOWED}/" ;;
esac

[[ -f "${BACKUP_REAL}" ]] || onx_die 2 "backup file not found: ${BACKUP_REAL}"

# ── Verify checksum ──────────────────────────────────────────────────────────
SHA_FILE="${BACKUP_REAL}.sha256"
if [[ -f "${SHA_FILE}" ]]; then
    # Validate the recorded checksum matches the file
    EXPECTED_SHA=$(awk '{print $1}' "${SHA_FILE}")
    ACTUAL_SHA=$(sha256sum "${BACKUP_REAL}" | awk '{print $1}')
    if [[ "${EXPECTED_SHA}" != "${ACTUAL_SHA}" ]]; then
        onx_die 2 "checksum mismatch: expected=${EXPECTED_SHA} actual=${ACTUAL_SHA}"
    fi
    onx_log "checksum verified: ${ACTUAL_SHA}"
else
    onx_log "WARNING: no .sha256 sidecar for ${BACKUP_REAL} — restoring without verification"
fi

# ── Working directory ────────────────────────────────────────────────────────
WORK_DIR="/tmp/onx-restore-${USERNAME}-$$"
mkdir -p "${WORK_DIR}" || onx_die 3 "cannot create work dir: ${WORK_DIR}"
chmod 0700 "${WORK_DIR}"

trap 'onx_rollback_run' ERR
onx_rollback_register "rm -rf '${WORK_DIR}' 2>/dev/null || true"

START_TS=$(date +%s)
onx_log "backup-restore start: account=${ACCOUNT_ID} user=${USERNAME} src=${BACKUP_REAL}"

# ── Extract archive ──────────────────────────────────────────────────────────
if [[ "${MOCK_MODE}" == "1" ]]; then
    # Mock: synthesise a manifest + home.tar.gz so subsequent steps don't crash
    printf 'MOCK home.tar.gz\n' > "${WORK_DIR}/home.tar.gz"
    jq -n \
        --arg user "${USERNAME}" \
        --argjson aid "${ACCOUNT_ID}" \
        '{manifest_version:"1.0",username:$user,account_id:$aid,included:{home:true,mail:true,dns:true,databases:["mockdb1"]}}' \
        > "${WORK_DIR}/manifest.json"
    printf '{"domains":[],"records":[]}\n' > "${WORK_DIR}/dns.json"
    printf '{"forwarders":[],"aliases":[],"autoresponders":[]}\n' > "${WORK_DIR}/mail-meta.json"
    mkdir -p "${WORK_DIR}/databases"
    printf -- '-- mock dump\n' > "${WORK_DIR}/databases/mockdb1.sql"
else
    tar -xzf "${BACKUP_REAL}" -C "${WORK_DIR}" 2>/dev/null \
        || onx_die 3 "extract failed: ${BACKUP_REAL}"
fi

MANIFEST_FILE="${WORK_DIR}/manifest.json"
[[ -f "${MANIFEST_FILE}" ]] || onx_die 3 "manifest.json missing in archive"

MANIFEST_VER=$(jq -r '.manifest_version // ""' "${MANIFEST_FILE}")
MANIFEST_USER=$(jq -r '.username // ""' "${MANIFEST_FILE}")
MANIFEST_HOME=$(jq -r '.home // ""' "${MANIFEST_FILE}")

[[ -z "${MANIFEST_VER}" ]] && onx_die 3 "manifest is missing manifest_version"

# Sanity: username in manifest must match target (or be empty for snapshot use)
if [[ -n "${MANIFEST_USER}" && "${MANIFEST_USER}" != "${USERNAME}" ]]; then
    onx_log "WARNING: manifest username='${MANIFEST_USER}' != target='${USERNAME}' — proceeding"
fi

# ── Stats accumulators ───────────────────────────────────────────────────────
RESTORED_HOME=0
RESTORED_DBS=0
RESTORED_MAIL=0
RESTORED_DNS=0
RESTORED_MAIL_META=0
RESTORED_FTP=0
RESTORED_SSL=0
RESTORED_CRON=0

# ── 1. Home directory restore ────────────────────────────────────────────────
# ONOXSOFT default home_base = /home/users (config/onoxsoft.php). Legacy /home/<u>
# kabul edilir. Tercih sırası: getent passwd > /home/users > /home (compat fallback).
TARGET_HOME=$(getent passwd "${USERNAME}" 2>/dev/null | cut -d: -f6)
if [[ -z "${TARGET_HOME}" ]]; then
    if [[ -d "/home/users/${USERNAME}" ]]; then
        TARGET_HOME="/home/users/${USERNAME}"
    else
        TARGET_HOME="${USER_HOME}"
    fi
fi

if [[ "${RESTORE_HOME}" == "true" && -f "${WORK_DIR}/home.tar.gz" ]]; then
    HOME_EXTRACT="${WORK_DIR}/home-extract"
    mkdir -p "${HOME_EXTRACT}"

    if [[ "${MOCK_MODE}" == "1" ]]; then
        RESTORED_HOME=$((RANDOM % 500 + 100))
    else
        tar -xzf "${WORK_DIR}/home.tar.gz" -C "${HOME_EXTRACT}" 2>/dev/null \
            || onx_die 3 "home.tar.gz extract failed"

        # Locate the extracted home dir (it carries the original basename)
        EXTRACTED_HOME="${HOME_EXTRACT}/$(basename "${MANIFEST_HOME:-${USERNAME}}")"
        if [[ ! -d "${EXTRACTED_HOME}" ]]; then
            EXTRACTED_HOME=$(find "${HOME_EXTRACT}" -mindepth 1 -maxdepth 1 -type d | head -n 1)
        fi
        [[ -d "${EXTRACTED_HOME}" ]] || onx_die 3 "could not locate extracted home dir"

        mkdir -p "${TARGET_HOME}"
        # rsync with -a preserves perms, ownership, timestamps; trailing slash
        # so contents (not the dir itself) land in TARGET_HOME.
        RSYNC_OUT=$(rsync -a --stats "${EXTRACTED_HOME}/" "${TARGET_HOME}/" 2>&1) \
            || onx_die 3 "rsync home failed"
        RESTORED_HOME=$(echo "${RSYNC_OUT}" | awk -F': ' '/Number of regular files transferred/{print $2}' | tr -d ',')
        RESTORED_HOME="${RESTORED_HOME:-0}"

        # Re-apply ownership (rsync -a preserved it, but the user might not exist
        # locally with the original uid — best-effort chown if id known)
        if id "${USERNAME}" >/dev/null 2>&1; then
            chown -R "${USERNAME}:${USERNAME}" "${TARGET_HOME}" 2>/dev/null || true
        fi
    fi
    onx_log "home restored: ${RESTORED_HOME} files into ${TARGET_HOME}"
fi

# ── 2. MySQL databases restore ───────────────────────────────────────────────
if [[ "${RESTORE_DBS}" == "true" && -d "${WORK_DIR}/databases" ]]; then
    DB_FILES=("${WORK_DIR}"/databases/*.sql)
    if [[ -e "${DB_FILES[0]}" ]]; then
        for DUMP in "${DB_FILES[@]}"; do
            [[ -f "${DUMP}" ]] || continue
            DB_NAME="$(basename "${DUMP}" .sql)"
            # Defence: only restore DBs that match our user prefix
            [[ "${DB_NAME}" =~ ^${USERNAME}_[a-z0-9_]+$ ]] || {
                onx_log "skipping db dump with mismatched prefix: ${DB_NAME}"
                continue
            }

            if [[ "${MOCK_MODE}" == "1" ]]; then
                RESTORED_DBS=$(( RESTORED_DBS + (RANDOM % 1000 + 50) ))
                continue
            fi

            # Drop & recreate before import so we have a clean slate
            mysql_exec "" "CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\`;" \
                || onx_die 3 "CREATE DATABASE ${DB_NAME} failed"

            # Pipe dump into mysql — rough row count is the number of INSERT lines
            INSERT_LINES=$(grep -c '^INSERT ' "${DUMP}" 2>/dev/null || echo 0)
            mysql --defaults-extra-file="${_MYCNF_TMP:-/dev/null}" --batch "${DB_NAME}" < "${DUMP}" \
                || onx_die 3 "mysql import failed for ${DB_NAME}"
            RESTORED_DBS=$(( RESTORED_DBS + INSERT_LINES ))
            onx_log "db restored: ${DB_NAME} (~${INSERT_LINES} inserts)"
        done
    fi
fi

# ── 3. Mail restore (Maildir) ────────────────────────────────────────────────
if [[ "${RESTORE_MAIL}" == "true" ]]; then
    # The home.tar.gz already contains Maildir under home/Maildir if include_mail
    # was true at backup time. We additionally support a sibling vmail/ directory
    # for accounts whose Maildir was virtual (Postfix+Dovecot SQL backend).
    if [[ "${MOCK_MODE}" == "1" ]]; then
        RESTORED_MAIL=$((RANDOM % 1000 + 100))
    else
        # Count messages restored under TARGET_HOME/Maildir (cur+new+tmp)
        if [[ -d "${TARGET_HOME}/Maildir" ]]; then
            RESTORED_MAIL=$(find "${TARGET_HOME}/Maildir" \( -path '*/cur/*' -o -path '*/new/*' \) -type f 2>/dev/null | wc -l)
        fi

        # Optional vmail tree restore from extracted backup (older format)
        if [[ -d "${WORK_DIR}/vmail" ]]; then
            for DOMAIN_DIR in "${WORK_DIR}"/vmail/*/; do
                [[ -d "${DOMAIN_DIR}" ]] || continue
                DOMAIN_NAME="$(basename "${DOMAIN_DIR}")"
                onx_validate_domain "${DOMAIN_NAME}" >/dev/null 2>&1 || continue
                rsync -a "${DOMAIN_DIR}" "${VMAIL_ROOT}/${DOMAIN_NAME}/" 2>/dev/null || true
                MSG_COUNT=$(find "${VMAIL_ROOT}/${DOMAIN_NAME}" \( -path '*/cur/*' -o -path '*/new/*' \) -type f 2>/dev/null | wc -l)
                RESTORED_MAIL=$(( RESTORED_MAIL + MSG_COUNT ))
            done
        fi
    fi
    onx_log "mail restored: ${RESTORED_MAIL} messages"
fi

# ── 4. DNS restore ───────────────────────────────────────────────────────────
if [[ "${RESTORE_DNS}" == "true" && -f "${WORK_DIR}/dns.json" ]]; then
    if [[ "${MOCK_MODE}" == "1" ]]; then
        RESTORED_DNS=$((RANDOM % 50 + 5))
    else
        # Insert only records that don't already exist (by domain+name+type+content).
        # We use the PowerDNS schema; some columns are nullable so we coalesce.
        RECORDS=$(jq -c '.records[]?' "${WORK_DIR}/dns.json" 2>/dev/null || true)
        while IFS= read -r REC; do
            [[ -z "${REC}" ]] && continue
            DOMAIN_ID=$(echo "${REC}" | jq -r '.domain_id // ""')
            REC_NAME=$(echo "${REC}" | jq -r '.name // ""')
            REC_TYPE=$(echo "${REC}" | jq -r '.type // ""')
            REC_CONTENT=$(echo "${REC}" | jq -r '.content // ""')
            REC_TTL=$(echo "${REC}" | jq -r '.ttl // 3600')
            REC_PRIO=$(echo "${REC}" | jq -r '.prio // 0')

            [[ -z "${DOMAIN_ID}" || -z "${REC_NAME}" ]] && continue

            # Backslash ÖNCE, sonra tek tırnak (NO_BACKSLASH_ESCAPES kapalı → sonu
            # '\' ile biten content kapanış tırnağını kaçırıp SQL'den çıkmasın).
            REC_NAME_ESC="${REC_NAME//\\/\\\\}";       REC_NAME_ESC="${REC_NAME_ESC//\'/\\\'}"
            REC_TYPE_ESC="${REC_TYPE//\\/\\\\}";       REC_TYPE_ESC="${REC_TYPE_ESC//\'/\\\'}"
            REC_CONTENT_ESC="${REC_CONTENT//\\/\\\\}"; REC_CONTENT_ESC="${REC_CONTENT_ESC//\'/\\\'}"
            # ttl/prio sayısal interpolasyon → guard (kötü niyetli arşiv inject etmesin).
            [[ "${REC_TTL}"  =~ ^[0-9]+$ ]] || REC_TTL=3600
            [[ "${REC_PRIO}" =~ ^[0-9]+$ ]] || REC_PRIO=0

            mysql --defaults-extra-file="${_MYCNF_TMP:-/dev/null}" --batch "${ONX_PDNS_DB}" -e \
                "INSERT IGNORE INTO records (domain_id, name, type, content, ttl, prio, disabled) \
                 VALUES (${DOMAIN_ID}, '${REC_NAME_ESC}', '${REC_TYPE_ESC}', '${REC_CONTENT_ESC}', ${REC_TTL}, ${REC_PRIO}, 0);" \
                2>/dev/null && RESTORED_DNS=$(( RESTORED_DNS + 1 )) || true
        done <<< "${RECORDS}"
    fi
    onx_log "dns restored: ${RESTORED_DNS} records"
fi

# ── 5. Mail metadata restore (forwarders + aliases) ──────────────────────────
# Eski restore yalnız Maildir mesajlarını sayıyordu; mail-meta.json'daki
# forwarder/alias kayıtları DB'ye HİÇ geri yazılmıyordu (yedeklenip restore
# edilmeyen veri). Burada mail DB'ye INSERT IGNORE ile ekleriz. Autoresponder
# body'si çok-satırlı/serbest metin olabildiği için Faz 1'de kapsam dışı.
if [[ "${RESTORE_MAIL_META}" == "true" && -f "${WORK_DIR}/mail-meta.json" ]]; then
    if [[ "${MOCK_MODE}" == "1" ]]; then
        RESTORED_MAIL_META=$((RANDOM % 10))
    else
        while IFS= read -r ROW; do
            [[ -z "${ROW}" ]] && continue
            MM_SRC=$(printf '%s' "${ROW}" | jq -r '.source // ""')
            MM_DST=$(printf '%s' "${ROW}" | jq -r '.destination // ""')
            [[ -z "${MM_SRC}" || -z "${MM_DST}" ]] && continue
            MM_SRC_E="${MM_SRC//\\/\\\\}"; MM_SRC_E="${MM_SRC_E//\'/\\\'}"
            MM_DST_E="${MM_DST//\\/\\\\}"; MM_DST_E="${MM_DST_E//\'/\\\'}"
            mysql_exec "${ONX_MAIL_DB}" \
                "INSERT IGNORE INTO forwarders (source, destination) VALUES ('${MM_SRC_E}','${MM_DST_E}');" \
                2>/dev/null && RESTORED_MAIL_META=$(( RESTORED_MAIL_META + 1 )) || true
        done < <(jq -c '.forwarders[]?' "${WORK_DIR}/mail-meta.json" 2>/dev/null)

        while IFS= read -r ROW; do
            [[ -z "${ROW}" ]] && continue
            MM_SRC=$(printf '%s' "${ROW}" | jq -r '.source // ""')
            MM_DST=$(printf '%s' "${ROW}" | jq -r '.destination // ""')
            [[ -z "${MM_SRC}" || -z "${MM_DST}" ]] && continue
            MM_SRC_E="${MM_SRC//\\/\\\\}"; MM_SRC_E="${MM_SRC_E//\'/\\\'}"
            MM_DST_E="${MM_DST//\\/\\\\}"; MM_DST_E="${MM_DST_E//\'/\\\'}"
            mysql_exec "${ONX_MAIL_DB}" \
                "INSERT IGNORE INTO virtual_aliases (source, destination) VALUES ('${MM_SRC_E}','${MM_DST_E}');" \
                2>/dev/null && RESTORED_MAIL_META=$(( RESTORED_MAIL_META + 1 )) || true
        done < <(jq -c '.aliases[]?' "${WORK_DIR}/mail-meta.json" 2>/dev/null)
    fi
    onx_log "mail metadata restored: ${RESTORED_MAIL_META} entries"
fi

# ── 6. FTP accounts restore (panel ftp_users) ────────────────────────────────
# ftp_users satırlarını (password_hash dahil) panel DB'ye geri yaz. pure-ftpd
# mysql backend bu tabloyu okuyorsa login anında çalışır; puredb backend için
# panel-side resync gerekebilir (Faz sonrası).
if [[ "${RESTORE_FTP}" == "true" && -f "${WORK_DIR}/ftp.json" ]]; then
    if [[ "${MOCK_MODE}" == "1" ]]; then
        RESTORED_FTP=$((RANDOM % 5))
    else
        while IFS= read -r ROW; do
            [[ -z "${ROW}" ]] && continue
            F_USER=$(printf '%s' "${ROW}" | jq -r '.user // ""')
            [[ -z "${F_USER}" ]] && continue
            F_HASH=$(printf '%s' "${ROW}" | jq -r '.password_hash // ""')
            F_UID=$(printf  '%s' "${ROW}" | jq -r '.uid // "0"')
            F_GID=$(printf  '%s' "${ROW}" | jq -r '.gid // "0"')
            F_DIR=$(printf  '%s' "${ROW}" | jq -r '.dir // ""')
            F_QS=$(printf   '%s' "${ROW}" | jq -r '.quota_size // "-1"')
            F_QF=$(printf   '%s' "${ROW}" | jq -r '.quota_files // "-1"')
            F_UL=$(printf   '%s' "${ROW}" | jq -r '.ul_bandwidth // "0"')
            F_DL=$(printf   '%s' "${ROW}" | jq -r '.dl_bandwidth // "0"')
            F_IP=$(printf   '%s' "${ROW}" | jq -r '.ip_access // "*"')
            F_LOG=$(printf  '%s' "${ROW}" | jq -r '.logical_name // ""')
            F_MAIN=$(printf '%s' "${ROW}" | jq -r 'if (.is_main_account=="1" or .is_main_account==true) then 1 else 0 end')
            # Numeric guards
            [[ "${F_UID}" =~ ^[0-9]+$ ]]   || F_UID=0
            [[ "${F_GID}" =~ ^[0-9]+$ ]]   || F_GID=0
            [[ "${F_QS}"  =~ ^-?[0-9]+$ ]] || F_QS=-1
            [[ "${F_QF}"  =~ ^-?[0-9]+$ ]] || F_QF=-1
            [[ "${F_UL}"  =~ ^[0-9]+$ ]]   || F_UL=0
            [[ "${F_DL}"  =~ ^[0-9]+$ ]]   || F_DL=0
            # Escape single quotes in string fields
            # Backslash ÖNCE, sonra tek tırnak (NO_BACKSLASH_ESCAPES kapalı → backslash
            # escape karakteri; sonu '\' ile biten değer kapanış tırnağını kaçırmasın).
            F_USER_E="${F_USER//\\/\\\\}"; F_USER_E="${F_USER_E//\'/\\\'}"
            F_HASH_E="${F_HASH//\\/\\\\}"; F_HASH_E="${F_HASH_E//\'/\\\'}"
            F_DIR_E="${F_DIR//\\/\\\\}";   F_DIR_E="${F_DIR_E//\'/\\\'}"
            F_IP_E="${F_IP//\\/\\\\}";     F_IP_E="${F_IP_E//\'/\\\'}"
            F_LOG_E="${F_LOG//\\/\\\\}";   F_LOG_E="${F_LOG_E//\'/\\\'}"
            # Yalnız temel migration kolonları (is_default/backend v88'de, sunucuda
            # henüz olmayabilir). is_main_account temelde var.
            mysql_exec "${ONX_PANEL_DB}" \
                "INSERT INTO ftp_users (account_id, user, password_hash, uid, gid, dir, quota_size, quota_files, ul_bandwidth, dl_bandwidth, ip_access, logical_name, is_main_account, status, created_at, updated_at) VALUES (${ACCOUNT_ID}, '${F_USER_E}', '${F_HASH_E}', ${F_UID}, ${F_GID}, '${F_DIR_E}', ${F_QS}, ${F_QF}, ${F_UL}, ${F_DL}, '${F_IP_E}', '${F_LOG_E}', ${F_MAIN}, 'active', NOW(), NOW()) ON DUPLICATE KEY UPDATE password_hash=VALUES(password_hash), dir=VALUES(dir), uid=VALUES(uid), gid=VALUES(gid), status='active', updated_at=NOW();" \
                2>/dev/null && RESTORED_FTP=$(( RESTORED_FTP + 1 )) || true
        done < <(jq -c '.accounts[]?' "${WORK_DIR}/ftp.json" 2>/dev/null)
    fi
    onx_log "ftp accounts restored: ${RESTORED_FTP}"
fi

# ── 7. SSL certificates restore (on-disk cert/key + reload) ──────────────────
# Yedeklenen cert/key dosyalarını standart konuma geri koy + aktif customer
# webserver'ı reload et. Vhost config'leri standart yolu referans aldığından
# dosyaları yerine koymak yeterli (vhost'a dokunmuyoruz — driver'a göre kırılgan).
if [[ "${RESTORE_SSL}" == "true" && -d "${WORK_DIR}/ssl" ]]; then
    if [[ "${MOCK_MODE}" == "1" ]]; then
        RESTORED_SSL=$((RANDOM % 3))
    else
        for DOM_DIR in "${WORK_DIR}"/ssl/*/; do
            [[ -d "${DOM_DIR}" ]] || continue
            S_DOM="$(basename "${DOM_DIR}")"
            onx_validate_domain "${S_DOM}" >/dev/null 2>&1 || continue
            if [[ -f "${DOM_DIR}privkey.pem" || -f "${DOM_DIR}fullchain.pem" ]]; then
                S_DEST="/etc/letsencrypt/live/${S_DOM}"
            else
                S_DEST="/etc/onoxsoft/ssl/manual/${S_DOM}"
            fi
            mkdir -p "${S_DEST}"
            cp -rLp "${DOM_DIR}." "${S_DEST}/" 2>/dev/null || true
            [[ -f "${S_DEST}/privkey.pem" ]] && chmod 600 "${S_DEST}/privkey.pem" 2>/dev/null || true
            chmod 600 "${S_DEST}"/*.key 2>/dev/null || true
            RESTORED_SSL=$(( RESTORED_SSL + 1 ))
        done
        # Aktif customer webserver'ı reload et (yeni cert'leri okusun). Best-effort.
        onx_ws_modsec_reload >/dev/null 2>&1 || true
    fi
    onx_log "ssl certs restored: ${RESTORED_SSL} domain(s)"
fi

# ── 8. Cron restore (/etc/cron.d file + panel cron_jobs) ─────────────────────
# Fonksiyonel: rendered cron.d dosyasını verbatim geri yaz (crond hemen okur).
# Panel bookkeeping: cron_jobs satırlarını geri ekle (command base64'ten çözülür).
# cron-rewrap cron'u (5dk) systemd-run slice wrap'ini idempotent yeniden uygular.
if [[ "${RESTORE_CRON}" == "true" ]]; then
    if [[ "${MOCK_MODE}" == "1" ]]; then
        RESTORED_CRON=$((RANDOM % 5))
    else
        if [[ -f "${WORK_DIR}/cron/cron.d" ]]; then
            install -m 0644 -o root -g root "${WORK_DIR}/cron/cron.d" "/etc/cron.d/onoxsoft-${USERNAME}" 2>/dev/null \
                || cp -f "${WORK_DIR}/cron/cron.d" "/etc/cron.d/onoxsoft-${USERNAME}" 2>/dev/null || true
        fi
        if [[ -f "${WORK_DIR}/cron.json" ]]; then
            while IFS= read -r ROW; do
                [[ -z "${ROW}" ]] && continue
                C_EXPR=$(printf '%s' "${ROW}" | jq -r '.expression // ""')
                [[ -z "${C_EXPR}" ]] && continue
                C_CMD=$(printf '%s' "${ROW}" | jq -r '(.command_b64 // "") | @base64d')
                C_LBL=$(printf '%s' "${ROW}" | jq -r '.label // ""')
                C_MAIL=$(printf '%s' "${ROW}" | jq -r '.email_output // ""')
                C_ACT=$(printf '%s' "${ROW}" | jq -r 'if (.is_active=="0" or .is_active==false) then 0 else 1 end')
                # Backslash ÖNCE, sonra tek tırnak — MySQL string literal güvenliği.
                C_EXPR_E="${C_EXPR//\\/\\\\}"; C_EXPR_E="${C_EXPR_E//\'/\\\'}"
                C_CMD_E="${C_CMD//\\/\\\\}";   C_CMD_E="${C_CMD_E//\'/\\\'}"
                C_LBL_E="${C_LBL//\\/\\\\}";   C_LBL_E="${C_LBL_E//\'/\\\'}"
                C_MAIL_E="${C_MAIL//\\/\\\\}"; C_MAIL_E="${C_MAIL_E//\'/\\\'}"
                mysql_exec "${ONX_PANEL_DB}" \
                    "INSERT INTO cron_jobs (account_id, label, expression, command, email_output, is_active, created_at, updated_at) VALUES (${ACCOUNT_ID}, NULLIF('${C_LBL_E}',''), '${C_EXPR_E}', '${C_CMD_E}', NULLIF('${C_MAIL_E}',''), ${C_ACT}, NOW(), NOW());" \
                    2>/dev/null && RESTORED_CRON=$(( RESTORED_CRON + 1 )) || true
            done < <(jq -c '.jobs[]?' "${WORK_DIR}/cron.json" 2>/dev/null)
        fi
    fi
    onx_log "cron restored: ${RESTORED_CRON} job(s)"
fi

# ── Cleanup ──────────────────────────────────────────────────────────────────
rm -rf "${WORK_DIR}"

END_TS=$(date +%s)
DURATION=$(( END_TS - START_TS ))

_ONX_ROLLBACK_STACK=()
trap - ERR

onx_audit "onx-backup" "restore account=${ACCOUNT_ID} user=${USERNAME} src=${BACKUP_REAL} duration=${DURATION}s"

jq -nc \
    --arg restored "true" \
    --argjson home "${RESTORED_HOME}" \
    --argjson dbs "${RESTORED_DBS}" \
    --argjson mail "${RESTORED_MAIL}" \
    --argjson dns "${RESTORED_DNS}" \
    --argjson mail_meta "${RESTORED_MAIL_META}" \
    --argjson ftp "${RESTORED_FTP}" \
    --argjson ssl "${RESTORED_SSL}" \
    --argjson cron "${RESTORED_CRON}" \
    --argjson dur "${DURATION}" \
    '{
        restored: true,
        items: {
            home: $home,
            dbs:  $dbs,
            mail: $mail,
            dns:  $dns,
            mail_meta: $mail_meta,
            ftp:  $ftp,
            ssl:  $ssl,
            cron: $cron
        },
        duration_seconds: $dur
    }'
