#!/usr/bin/env bash
# gniza4cp/lib/restore.sh — Full account, files, database, mailbox, server restores

# Helper: build rsync download command args for SSH mode
_rsync_download() {
    local remote_path="$1"
    local local_path="$2"
    local rsync_ssh; rsync_ssh=$(build_rsync_ssh_cmd)
    if _is_password_mode; then
        export SSHPASS="$REMOTE_PASSWORD"
        sshpass -e rsync -aHAX --numeric-ids --rsync-path="rsync --fake-super" \
            -e "$rsync_ssh" \
            "${REMOTE_USER}@${REMOTE_HOST}:${remote_path}" \
            "$local_path"
    else
        rsync -aHAX --numeric-ids --rsync-path="rsync --fake-super" \
            -e "$rsync_ssh" \
            "${REMOTE_USER}@${REMOTE_HOST}:${remote_path}" \
            "$local_path"
    fi
}

# Helper: build --exclude args from comma-separated exclude string.
# Outputs one arg per line (--exclude then pattern).
_build_exclude_args() {
    local exclude="$1"
    local -a args=()
    if [[ -n "$exclude" ]]; then
        IFS=',' read -ra patterns <<< "$exclude"
        for p in "${patterns[@]}"; do
            p="${p## }"; p="${p%% }"
            [[ -n "$p" ]] && args+=(--exclude "$p")
        done
    fi
    printf '%s\n' "${args[@]}"
}

# Helper: detect pkgacct base (old vs new format) for rclone or SSH
_detect_pkgacct_base() {
    local snap_path="$1"
    local snap_subpath="${2:-}"

    if _is_rclone_mode; then
        if rclone_exists "${snap_subpath}/pkgacct/"; then
            echo "${snap_subpath}/pkgacct"
        else
            echo "$snap_subpath"
        fi
    else
        if remote_exec "test -d '$snap_path/pkgacct'" 2>/dev/null; then
            echo "$snap_path/pkgacct"
        else
            echo "$snap_path"
        fi
    fi
}

restore_full_account() {
    local user="$1"
    local timestamp="${2:-}"
    local terminate="${3:-}"
    local exclude="${4:-}"
    local temp_dir="${TEMP_DIR:-$DEFAULT_TEMP_DIR}"

    # Resolve timestamp
    local ts; ts=$(resolve_snapshot_timestamp "$user" "$timestamp") || return 1
    local snap_dir; snap_dir=$(get_snapshot_dir "$user")
    local snap_path="$snap_dir/$ts"

    log_info "Restoring full account: $user from snapshot $ts"

    local restore_dir="$temp_dir/restore-$user"
    mkdir -p "$restore_dir" || {
        log_error "Failed to create restore temp directory"
        return 1
    }

    # Download pkgacct data from remote
    log_info "Downloading pkgacct data for $user..."
    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}"
        local pkgacct_sub; pkgacct_sub=$(_detect_pkgacct_base "" "$snap_subpath")
        if [[ "$pkgacct_sub" == *"/pkgacct" ]]; then
            rclone_from_remote "${pkgacct_sub}" "$restore_dir/$user" || {
                log_error "Failed to download pkgacct data for $user"
                rm -rf "$restore_dir"
                return 1
            }
        else
            # Download snapshot root excluding homedir
            rclone_from_remote "${snap_subpath}" "$restore_dir/$user" || {
                log_error "Failed to download pkgacct data for $user"
                rm -rf "$restore_dir"
                return 1
            }
            rm -rf "$restore_dir/$user/homedir"
        fi
    else
        # Detect old format (pkgacct/ subdir) vs new format (content at root)
        local rsync_ssh; rsync_ssh=$(build_rsync_ssh_cmd)
        if remote_exec "test -d '$snap_path/pkgacct'" 2>/dev/null; then
            # Old format: pkgacct data in subdir
            if ! rsync -aHAX --numeric-ids --rsync-path="rsync --fake-super" \
                -e "$rsync_ssh" \
                "${REMOTE_USER}@${REMOTE_HOST}:${snap_path}/pkgacct/" \
                "$restore_dir/$user/"; then
                log_error "Failed to download pkgacct data for $user"
                rm -rf "$restore_dir"
                return 1
            fi
        else
            # New format: pkgacct content at snapshot root, exclude homedir/
            if ! rsync -aHAX --numeric-ids --rsync-path="rsync --fake-super" --exclude=/homedir/ \
                -e "$rsync_ssh" \
                "${REMOTE_USER}@${REMOTE_HOST}:${snap_path}/" \
                "$restore_dir/$user/"; then
                log_error "Failed to download pkgacct data for $user"
                rm -rf "$restore_dir"
                return 1
            fi
        fi
    fi

    # Gunzip SQL files
    log_info "Decompressing SQL files..."
    local mysql_dir="$restore_dir/$user/mysql"
    if [[ -d "$mysql_dir" ]]; then
        find "$mysql_dir" -name "*.sql.gz" -exec gunzip -f {} \;
    fi

    # Terminate existing account if requested
    if [[ "$terminate" == "true" ]] && account_exists "$user"; then
        log_info "Terminating existing account $user before restore..."
        local removeacct_output
        if ! removeacct_output=$(whmapi1 removeacct user="$user" 2>&1); then
            log_error "Failed to terminate account $user"
            log_debug "removeacct output: $removeacct_output"
            rm -rf "$restore_dir"
            return 1
        fi
        log_debug "removeacct output: $removeacct_output"
        # Check whmapi1 result field for failure
        if echo "$removeacct_output" | grep -q 'result: 0'; then
            local reason
            reason=$(echo "$removeacct_output" | grep 'reason:' | sed 's/.*reason: //')
            log_error "Failed to terminate account $user: $reason"
            rm -rf "$restore_dir"
            return 1
        fi
    fi

    # Run restorepkg (--force to merge into existing account if present)
    log_info "Running restorepkg for $user..."
    local -a restorepkg_args=()
    account_exists "$user" && restorepkg_args+=(--force)
    if ! /scripts/restorepkg "${restorepkg_args[@]}" "$restore_dir/$user"; then
        log_error "restorepkg failed for $user"
        rm -rf "$restore_dir"
        return 1
    fi

    # Build exclude args for homedir phase
    local -a exclude_args=()
    if [[ -n "$exclude" ]]; then
        while IFS= read -r arg; do
            exclude_args+=("$arg")
        done < <(_build_exclude_args "$exclude")
    fi

    # Restore homedir
    local homedir; homedir=$(get_account_homedir "$user")
    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}"
        if rclone_exists "${snap_subpath}/homedir/"; then
            log_info "Restoring homedir for $user..."
            if [[ ${#exclude_args[@]} -gt 0 ]]; then
                if ! rclone_from_remote_filtered "${snap_subpath}/homedir" "$homedir" "${exclude_args[@]}"; then
                    log_error "Failed to restore homedir for $user"
                    rm -rf "$restore_dir"
                    return 1
                fi
            else
                if ! rclone_from_remote "${snap_subpath}/homedir" "$homedir"; then
                    log_error "Failed to restore homedir for $user"
                    rm -rf "$restore_dir"
                    return 1
                fi
            fi
            log_info "Fixing ownership for $homedir..."
            chown -R "$user":"$user" "$homedir"
        fi
    elif remote_exec "test -d '$snap_path/homedir'" 2>/dev/null; then
        log_info "Restoring homedir for $user..."
        local rsync_ssh; rsync_ssh=$(build_rsync_ssh_cmd)
        if ! rsync -aHAX --numeric-ids --rsync-path="rsync --fake-super" \
            "${exclude_args[@]}" \
            -e "$rsync_ssh" \
            "${REMOTE_USER}@${REMOTE_HOST}:${snap_path}/homedir/" \
            "$homedir/"; then
            log_error "Failed to restore homedir for $user"
            rm -rf "$restore_dir"
            return 1
        fi

        # Fix ownership
        log_info "Fixing ownership for $homedir..."
        chown -R "$user":"$user" "$homedir"
    fi

    rm -rf "$restore_dir"
    log_info "Full account restore completed for $user"
    return 0
}

restore_files() {
    local user="$1"
    local subpath="${2:-}"
    local timestamp="${3:-}"
    local exclude="${4:-}"

    local ts; ts=$(resolve_snapshot_timestamp "$user" "$timestamp") || return 1

    local homedir; homedir=$(get_account_homedir "$user")
    local local_dest="$homedir/"

    if [[ -n "$subpath" ]]; then
        local_dest="${homedir}/${subpath}"
        mkdir -p "$(dirname "$local_dest")"
    fi

    log_info "Restoring files for $user: ${subpath:-entire homedir} from snapshot $ts"

    # Build exclude args
    local -a exclude_args=()
    if [[ -n "$exclude" ]]; then
        while IFS= read -r arg; do
            exclude_args+=("$arg")
        done < <(_build_exclude_args "$exclude")
    fi

    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}"
        local remote_sub="${snap_subpath}/homedir"
        [[ -n "$subpath" ]] && remote_sub="${snap_subpath}/homedir/${subpath}"

        if [[ ${#exclude_args[@]} -gt 0 ]]; then
            if ! rclone_from_remote_filtered "$remote_sub" "$local_dest" "${exclude_args[@]}"; then
                log_error "Failed to restore files for $user"
                return 1
            fi
        else
            if ! rclone_from_remote "$remote_sub" "$local_dest"; then
                log_error "Failed to restore files for $user"
                return 1
            fi
        fi
    else
        local snap_dir; snap_dir=$(get_snapshot_dir "$user")
        local snap_path="$snap_dir/$ts"
        local rsync_ssh; rsync_ssh=$(build_rsync_ssh_cmd)

        local remote_source="${snap_path}/homedir/"
        [[ -n "$subpath" ]] && remote_source="${snap_path}/homedir/${subpath}"

        if ! rsync -aHAX --numeric-ids --rsync-path="rsync --fake-super" \
            "${exclude_args[@]}" \
            -e "$rsync_ssh" \
            "${REMOTE_USER}@${REMOTE_HOST}:${remote_source}" \
            "$local_dest"; then
            log_error "Failed to restore files for $user"
            return 1
        fi
    fi

    # Fix ownership
    chown -R "$user":"$user" "$local_dest"

    log_info "File restore completed for $user"
    return 0
}

restore_database() {
    local user="$1"
    local dbname="$2"
    local timestamp="${3:-}"
    local temp_dir="${TEMP_DIR:-$DEFAULT_TEMP_DIR}"

    local ts; ts=$(resolve_snapshot_timestamp "$user" "$timestamp") || return 1
    local snap_dir; snap_dir=$(get_snapshot_dir "$user")
    local snap_path="$snap_dir/$ts"
    local rsync_ssh; rsync_ssh=$(build_rsync_ssh_cmd)

    local restore_dir="$temp_dir/restore-db-$user"
    mkdir -p "$restore_dir" || {
        log_error "Failed to create restore temp directory"
        return 1
    }

    log_info "Restoring database $dbname for $user from snapshot $ts"

    local local_sql="$restore_dir/${dbname}.sql.gz"

    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}"
        local pkgacct_sub; pkgacct_sub=$(_detect_pkgacct_base "" "$snap_subpath")
        if ! rclone_from_remote "${pkgacct_sub}/mysql/${dbname}.sql.gz" "$restore_dir"; then
            log_error "SQL dump not found for database: $dbname"
            rm -rf "$restore_dir"
            return 1
        fi
    else
        # Detect old format (pkgacct/ subdir) vs new format (content at root)
        local pkgacct_base="$snap_path"
        if remote_exec "test -d '$snap_path/pkgacct'" 2>/dev/null; then
            pkgacct_base="$snap_path/pkgacct"
        fi

        local remote_sql="${pkgacct_base}/mysql/${dbname}.sql.gz"

        if ! rsync -aHAX --rsync-path="rsync --fake-super" \
            -e "$rsync_ssh" \
            "${REMOTE_USER}@${REMOTE_HOST}:${remote_sql}" \
            "$local_sql" 2>/dev/null; then
            log_error "SQL dump not found for database: $dbname"
            rm -rf "$restore_dir"
            return 1
        fi
    fi

    # Decompress
    gunzip -f "$local_sql" || {
        log_error "Failed to decompress SQL file"
        rm -rf "$restore_dir"
        return 1
    }

    # Download mysql.sql (grants) if it exists
    local local_grants="$restore_dir/mysql.sql"
    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}"
        local pkgacct_sub; pkgacct_sub=$(_detect_pkgacct_base "" "$snap_subpath")
        rclone_from_remote "${pkgacct_sub}/mysql.sql" "$restore_dir" 2>/dev/null || true
    else
        local remote_grants="${pkgacct_base}/mysql.sql"
        rsync -aHAX --rsync-path="rsync --fake-super" -e "$rsync_ssh" \
            "${REMOTE_USER}@${REMOTE_HOST}:${remote_grants}" \
            "$local_grants" 2>/dev/null
    fi

    # Get cPanel DB prefix for this account
    local db_prefix
    db_prefix=$(uapi --user="$user" Mysql get_restrictions 2>/dev/null | grep -oP '^\s+prefix:\s+\K\S+') || db_prefix=""

    # Create database via cPanel UAPI
    log_info "Creating database $dbname via UAPI..."
    if [[ -n "$db_prefix" && "$dbname" != "${db_prefix}"* ]]; then
        log_error "Database $dbname does not match cPanel prefix '${db_prefix}' — cannot create via UAPI. Rename the database to start with '${db_prefix}'"
        rm -rf "$restore_dir"
        return 1
    fi
    local uapi_out; uapi_out=$(uapi --user="$user" Mysql create_database name="$dbname" 2>&1) || true
    if echo "$uapi_out" | grep -q 'status: 1'; then
        log_info "Database $dbname created via UAPI"
    elif echo "$uapi_out" | grep -qi 'already exists'; then
        log_debug "Database $dbname already exists"
    else
        log_error "UAPI create_database failed for $dbname: $uapi_out"
        rm -rf "$restore_dir"
        return 1
    fi

    # Import SQL dump — no cPanel API exists for SQL import; mysql binary is required
    log_info "Importing SQL dump for $dbname..."
    if ! mysql --defaults-file=/root/.my.cnf "$dbname" < "$restore_dir/${dbname}.sql"; then
        log_error "Failed to import SQL dump for $dbname"
        rm -rf "$restore_dir"
        return 1
    fi

    # Restore database users and privileges via UAPI
    if [[ -f "$local_grants" ]]; then
        log_info "Restoring database users and privileges via UAPI..."
        # Build grep pattern that matches both escaped (\_) and unescaped (_) underscores
        local dbname_grep; dbname_grep="${dbname//_/\\\\?_}"
        local grant_users; grant_users=$(grep -iE "\`${dbname_grep}\`" "$local_grants" | grep -oP "TO\s+'?\K[^'@]+(?='@)" | sort -u) || true
        if [[ -n "$grant_users" ]]; then
            while IFS= read -r dbuser; do
                [[ -z "$dbuser" ]] && continue
                if [[ -n "$db_prefix" && "$dbuser" != "${db_prefix}"* ]]; then
                    log_warn "Skipping DB user $dbuser — does not match cPanel prefix '${db_prefix}'"
                    continue
                fi
                # Create user via UAPI
                local cu_out; cu_out=$(uapi --user="$user" Mysql create_user name="$dbuser" password="$(openssl rand -base64 24)" 2>&1) || true
                if echo "$cu_out" | grep -q 'status: 1'; then
                    log_debug "Created DB user $dbuser via UAPI"
                elif echo "$cu_out" | grep -qi 'exists'; then
                    log_debug "DB user $dbuser already exists"
                else
                    log_error "UAPI create_user failed for $dbuser: $cu_out"
                    rm -rf "$restore_dir"
                    return 1
                fi
                # Grant privileges via UAPI
                local sp_out; sp_out=$(uapi --user="$user" Mysql set_privileges_on_database user="$dbuser" database="$dbname" privileges=ALL 2>&1) || true
                if ! echo "$sp_out" | grep -q 'status: 1'; then
                    log_error "UAPI set_privileges_on_database failed for $dbuser on $dbname: $sp_out"
                    rm -rf "$restore_dir"
                    return 1
                fi
                log_debug "Registered DB user $dbuser for $dbname via UAPI"
            done <<< "$grant_users"
        fi
    fi

    rm -rf "$restore_dir"
    log_info "Database restore completed for $dbname"
    return 0
}

restore_all_databases() {
    local user="$1"
    local timestamp="${2:-}"

    log_info "Restoring all databases for $user"

    local db_list; db_list=$(list_snapshot_databases "$user" "$timestamp")
    if [[ -z "$db_list" ]]; then
        log_warn "No databases found in snapshot for $user"
        return 0
    fi

    local total=0 succeeded=0 failed=0
    while IFS= read -r dbname; do
        [[ -z "$dbname" ]] && continue
        ((total++)) || true

        log_info "--- Restoring database $dbname ($total) ---"
        if restore_database "$user" "$dbname" "$timestamp"; then
            ((succeeded++)) || true
        else
            ((failed++)) || true
            log_error "Failed to restore database: $dbname"
        fi
    done <<< "$db_list"

    log_info "Restored $succeeded/$total databases for $user"
    (( failed > 0 )) && return 1
    return 0
}

restore_mailbox() {
    local user="$1"
    local email="$2"
    local timestamp="${3:-}"

    # Parse email into domain and mailbox user
    local mailbox_user="${email%%@*}"
    local domain="${email##*@}"

    if [[ -z "$mailbox_user" || -z "$domain" || "$mailbox_user" == "$email" ]]; then
        log_error "Invalid email format: $email (expected user@domain)"
        return 1
    fi

    log_info "Restoring mailbox $email for account $user"

    # Ensure mailbox account exists in cPanel via UAPI
    local pop_out; pop_out=$(uapi --user="$user" Email add_pop \
        email="$mailbox_user" domain="$domain" \
        password="$(openssl rand -base64 24)" quota=0 2>&1) || true
    if echo "$pop_out" | grep -q 'status: 1'; then
        log_info "Created mailbox $email via UAPI"
    elif echo "$pop_out" | grep -qi 'exists'; then
        log_debug "Mailbox $email already exists"
    else
        log_error "UAPI Email add_pop failed for $email: $pop_out"
        return 1
    fi

    # Restore mailbox data files
    local mail_subpath="mail/${domain}/${mailbox_user}/"
    restore_files "$user" "$mail_subpath" "$timestamp"
}

restore_all_mailboxes() {
    local user="$1"
    local timestamp="${2:-}"

    log_info "Restoring all mailboxes for $user"

    local mailbox_list; mailbox_list=$(list_snapshot_mailboxes "$user" "$timestamp")
    if [[ -z "$mailbox_list" ]]; then
        log_warn "No mailboxes found in snapshot for $user"
        return 0
    fi

    local total=0 succeeded=0 failed=0
    while IFS= read -r email; do
        [[ -z "$email" ]] && continue
        ((total++)) || true

        log_info "--- Restoring mailbox $email ($total) ---"
        if restore_mailbox "$user" "$email" "$timestamp"; then
            ((succeeded++)) || true
        else
            ((failed++)) || true
            log_error "Failed to restore mailbox: $email"
        fi
    done <<< "$mailbox_list"

    log_info "Restored $succeeded/$total mailboxes for $user"
    (( failed > 0 )) && return 1
    return 0
}

# List database names available in a snapshot's mysql/ directory.
# Outputs one database name per line (without .sql.gz extension).
list_snapshot_databases() {
    local user="$1"
    local timestamp="${2:-}"

    local ts; ts=$(resolve_snapshot_timestamp "$user" "$timestamp") || return 1

    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}"
        local pkgacct_sub; pkgacct_sub=$(_detect_pkgacct_base "" "$snap_subpath")
        rclone_list_files "${pkgacct_sub}/mysql" 2>/dev/null | while IFS= read -r f; do
            [[ "$f" == *.sql.gz ]] || continue
            echo "${f%.sql.gz}"
        done
        return
    fi

    local snap_dir; snap_dir=$(get_snapshot_dir "$user")
    local snap_path="$snap_dir/$ts"

    # Detect old format vs new format
    local pkgacct_base="$snap_path"
    if remote_exec "test -d '$snap_path/pkgacct'" 2>/dev/null; then
        pkgacct_base="$snap_path/pkgacct"
    fi

    local mysql_dir="${pkgacct_base}/mysql"
    remote_exec "ls -1 '$mysql_dir'/*.sql.gz 2>/dev/null" 2>/dev/null | while IFS= read -r f; do
        local base; base=$(basename "$f" .sql.gz)
        echo "$base"
    done
}

# List mailbox addresses available in a snapshot's homedir/mail/ directory.
# Outputs one email address per line (user@domain format).
list_snapshot_mailboxes() {
    local user="$1"
    local timestamp="${2:-}"

    local ts; ts=$(resolve_snapshot_timestamp "$user" "$timestamp") || return 1

    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}"
        local domains; domains=$(rclone_list_dirs "${snap_subpath}/homedir/mail" 2>/dev/null) || true
        [[ -z "$domains" ]] && return 0
        while IFS= read -r domain; do
            [[ -z "$domain" || "$domain" == .* ]] && continue
            local users; users=$(rclone_list_dirs "${snap_subpath}/homedir/mail/${domain}" 2>/dev/null) || true
            [[ -z "$users" ]] && continue
            while IFS= read -r mailbox_user; do
                [[ -z "$mailbox_user" || "$mailbox_user" == .* ]] && continue
                echo "${mailbox_user}@${domain}"
            done <<< "$users"
        done <<< "$domains"
        return
    fi

    local snap_dir; snap_dir=$(get_snapshot_dir "$user")
    local snap_path="$snap_dir/$ts"

    local mail_dir="${snap_path}/homedir/mail"
    remote_exec "find '$mail_dir' -mindepth 2 -maxdepth 2 -type d 2>/dev/null" 2>/dev/null | while IFS= read -r d; do
        local mailbox_user; mailbox_user=$(basename "$d")
        local domain; domain=$(basename "$(dirname "$d")")
        # Skip Maildir system directories (not real domains/users)
        [[ "$domain" == .* || "$mailbox_user" == .* ]] && continue
        echo "${mailbox_user}@${domain}"
    done
}

# List files/directories in a snapshot's homedir/ directory.
# Outputs one entry per line. Directories end with '/'.
# Optional subpath argument for navigating subdirectories.
list_snapshot_files() {
    local user="$1"
    local timestamp="${2:-}"
    local subpath="${3:-}"

    # Guard against path traversal
    if [[ "$subpath" == *".."* ]]; then
        log_error "Path traversal not allowed: $subpath"
        return 1
    fi

    local ts; ts=$(resolve_snapshot_timestamp "$user" "$timestamp") || return 1

    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}/homedir"
        [[ -n "$subpath" ]] && snap_subpath="${snap_subpath}/${subpath}"
        rclone_list_files "$snap_subpath" 2>/dev/null || true
        return
    fi

    local snap_dir; snap_dir=$(get_snapshot_dir "$user")
    local snap_path="$snap_dir/$ts"

    local target_dir="${snap_path}/homedir"
    if [[ -n "$subpath" ]]; then
        target_dir="${target_dir}/${subpath}"
    fi

    remote_exec "ls -1ap '$target_dir' 2>/dev/null" 2>/dev/null | while IFS= read -r entry; do
        # Filter out . and ..
        [[ "$entry" == "." || "$entry" == ".." || "$entry" == "./" || "$entry" == "../" ]] && continue
        echo "$entry"
    done
}

# List database users found in a snapshot's mysql.sql grant file.
# Outputs one username per line.
list_snapshot_dbusers() {
    local user="$1"
    local timestamp="${2:-}"

    local ts; ts=$(resolve_snapshot_timestamp "$user" "$timestamp") || return 1

    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}"
        local pkgacct_sub; pkgacct_sub=$(_detect_pkgacct_base "" "$snap_subpath")
        local grants_content; grants_content=$(rclone_cat "${pkgacct_sub}/mysql.sql" 2>/dev/null) || true
        [[ -z "$grants_content" ]] && return 0
        echo "$grants_content" | grep -oP "TO\s+'?\K[^'@]+" | sort -u | while IFS= read -r dbuser; do
            [[ "$dbuser" == "$user" ]] && continue
            [[ -z "$dbuser" ]] && continue
            echo "$dbuser"
        done
        return
    fi

    local snap_dir; snap_dir=$(get_snapshot_dir "$user")
    local snap_path="$snap_dir/$ts"

    local pkgacct_base="$snap_path"
    if remote_exec "test -d '$snap_path/pkgacct'" 2>/dev/null; then
        pkgacct_base="$snap_path/pkgacct"
    fi

    local grants_file="${pkgacct_base}/mysql.sql"
    remote_exec "grep -oP \"TO\\\\s+'?\\\\K[^'@]+\" '$grants_file' 2>/dev/null | sort -u" 2>/dev/null | while IFS= read -r dbuser; do
        # Skip the account's default DB user (same as cPanel username)
        [[ "$dbuser" == "$user" ]] && continue
        [[ -z "$dbuser" ]] && continue
        echo "$dbuser"
    done
}

# List cron job entries found in a snapshot's cron/ directory.
# Outputs one cron line per line (skips comments, empty lines, env vars).
list_snapshot_cron() {
    local user="$1"
    local timestamp="${2:-}"

    local ts; ts=$(resolve_snapshot_timestamp "$user" "$timestamp") || return 1

    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}"
        local pkgacct_sub; pkgacct_sub=$(_detect_pkgacct_base "" "$snap_subpath")
        local files; files=$(rclone_list_files "${pkgacct_sub}/cron" 2>/dev/null) || true
        [[ -z "$files" ]] && return 0
        local first_file; first_file=$(echo "$files" | head -1)
        [[ -z "$first_file" ]] && return 0
        rclone_cat "${pkgacct_sub}/cron/${first_file}" 2>/dev/null | grep -vE '^\s*#|^\s*$|^\s*[A-Za-z_]+=' || true
        return
    fi

    local snap_dir; snap_dir=$(get_snapshot_dir "$user")
    local snap_path="$snap_dir/$ts"

    local pkgacct_base="$snap_path"
    if remote_exec "test -d '$snap_path/pkgacct'" 2>/dev/null; then
        pkgacct_base="$snap_path/pkgacct"
    fi

    local cron_dir="${pkgacct_base}/cron"
    remote_exec "f=\$(find '$cron_dir' -type f 2>/dev/null | head -1); [ -f \"\$f\" ] && grep -vE '^\s*#|^\s*$|^\s*[A-Za-z_]+=' \"\$f\" || true" 2>/dev/null
}

# List DNS zone names found in a snapshot's dnszones/ directory.
# Outputs one domain name per line.
list_snapshot_dns() {
    local user="$1"
    local timestamp="${2:-}"

    local ts; ts=$(resolve_snapshot_timestamp "$user" "$timestamp") || return 1

    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}"
        local pkgacct_sub; pkgacct_sub=$(_detect_pkgacct_base "" "$snap_subpath")
        rclone_list_files "${pkgacct_sub}/dnszones" 2>/dev/null | while IFS= read -r f; do
            [[ "$f" == *.db ]] || continue
            echo "${f%.db}"
        done
        return
    fi

    local snap_dir; snap_dir=$(get_snapshot_dir "$user")
    local snap_path="$snap_dir/$ts"

    local pkgacct_base="$snap_path"
    if remote_exec "test -d '$snap_path/pkgacct'" 2>/dev/null; then
        pkgacct_base="$snap_path/pkgacct"
    fi

    local zones_dir="${pkgacct_base}/dnszones"
    remote_exec "ls -1 '$zones_dir'/*.db 2>/dev/null" 2>/dev/null | while IFS= read -r f; do
        local base; base=$(basename "$f" .db)
        echo "$base"
    done
}

# List SSL certificate domains found in a snapshot's ssl/ directory.
# Outputs one cert filename (without extension) per line.
list_snapshot_ssl() {
    local user="$1"
    local timestamp="${2:-}"

    local ts; ts=$(resolve_snapshot_timestamp "$user" "$timestamp") || return 1

    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}"
        local pkgacct_sub; pkgacct_sub=$(_detect_pkgacct_base "" "$snap_subpath")
        {
            rclone_list_files "${pkgacct_sub}/ssl/certs" 2>/dev/null || true
            rclone_list_files "${pkgacct_sub}/ssl/installed/certs" 2>/dev/null || true
            rclone_list_files "${pkgacct_sub}/sslcerts" 2>/dev/null || true
            rclone_list_files "${snap_subpath}/homedir/ssl/certs" 2>/dev/null || true
        } | while IFS= read -r f; do
            [[ "$f" == *.crt ]] || continue
            echo "${f%.crt}"
        done | sort -u
        return
    fi

    local snap_dir; snap_dir=$(get_snapshot_dir "$user")
    local snap_path="$snap_dir/$ts"

    local pkgacct_base="$snap_path"
    if remote_exec "test -d '$snap_path/pkgacct'" 2>/dev/null; then
        pkgacct_base="$snap_path/pkgacct"
    fi

    local homedir_base="${snap_path}/homedir"
    remote_exec "ls -1 '$pkgacct_base/ssl/certs/'*.crt '$pkgacct_base/ssl/installed/certs/'*.crt '$pkgacct_base/sslcerts/'*.crt '$homedir_base/ssl/certs/'*.crt 2>/dev/null || true" 2>/dev/null | while IFS= read -r f; do
        [[ -z "$f" ]] && continue
        basename "$f" .crt
    done | sort -u
}

restore_cron() {
    local user="$1"
    local timestamp="${2:-}"
    local temp_dir="${TEMP_DIR:-$DEFAULT_TEMP_DIR}"

    local ts; ts=$(resolve_snapshot_timestamp "$user" "$timestamp") || return 1
    local snap_dir; snap_dir=$(get_snapshot_dir "$user")
    local snap_path="$snap_dir/$ts"
    local rsync_ssh; rsync_ssh=$(build_rsync_ssh_cmd)

    # Detect old format vs new format
    local pkgacct_base="$snap_path"
    if remote_exec "test -d '$snap_path/pkgacct'" 2>/dev/null; then
        pkgacct_base="$snap_path/pkgacct"
    fi

    local restore_dir="$temp_dir/restore-cron-$user"
    mkdir -p "$restore_dir" || {
        log_error "Failed to create restore temp directory"
        return 1
    }

    log_info "Restoring cron jobs for $user from snapshot $ts"

    # Download cron/ directory from snapshot
    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}"
        local pkgacct_sub; pkgacct_sub=$(_detect_pkgacct_base "" "$snap_subpath")
        if ! rclone_from_remote "${pkgacct_sub}/cron" "$restore_dir/cron"; then
            log_error "No cron data found in snapshot for $user"
            rm -rf "$restore_dir"
            return 1
        fi
    else
        if ! rsync -aHAX --rsync-path="rsync --fake-super" \
            -e "$rsync_ssh" \
            "${REMOTE_USER}@${REMOTE_HOST}:${pkgacct_base}/cron/" \
            "$restore_dir/cron/" 2>/dev/null; then
            log_error "No cron data found in snapshot for $user"
            rm -rf "$restore_dir"
            return 1
        fi
    fi

    # Find the crontab file
    local crontab_file
    crontab_file=$(find "$restore_dir/cron" -type f | head -1)
    if [[ -z "$crontab_file" ]]; then
        log_warn "Cron directory exists but no crontab file found for $user"
        rm -rf "$restore_dir"
        return 0
    fi

    # Restore cron jobs via cpapi2 (registers with cPanel's cron manager)
    log_info "Restoring cron jobs via cPanel API..."

    # Remove existing cron entries via cpapi2
    local existing_lines; existing_lines=$(cpapi2 --user="$user" Cron listcron 2>&1)
    if [[ $? -ne 0 ]]; then
        log_error "cpapi2 Cron listcron failed for $user: $existing_lines"
        rm -rf "$restore_dir"
        return 1
    fi

    local linekeys; linekeys=$(echo "$existing_lines" | grep -oP 'linekey:\s*\K\S+') || true
    if [[ -n "$linekeys" ]]; then
        while IFS= read -r linekey; do
            [[ -z "$linekey" ]] && continue
            cpapi2 --user="$user" Cron remove_line linekey="$linekey" 2>/dev/null || true
        done <<< "$linekeys"
    fi

    # Parse each cron line and add via cpapi2
    while IFS= read -r line; do
        # Skip empty lines, comments, and environment variables (KEY=value)
        [[ -z "$line" || "$line" =~ ^[[:space:]]*# || "$line" =~ ^[[:space:]]*[A-Za-z_]+= ]] && continue

        # Split into 5 cron fields + command
        local minute hour day month weekday command
        read -r minute hour day month weekday command <<< "$line"
        [[ -z "$command" ]] && continue

        local add_out; add_out=$(cpapi2 --user="$user" Cron add_line \
            minute="$minute" hour="$hour" day="$day" \
            month="$month" weekday="$weekday" command="$command" 2>&1) || true
        if ! echo "$add_out" | grep -q 'status: 1'; then
            log_error "cpapi2 Cron add_line failed for '$line': $add_out"
            rm -rf "$restore_dir"
            return 1
        fi
    done < "$crontab_file"

    rm -rf "$restore_dir"
    log_info "Cron restore completed for $user"
    return 0
}

restore_dbusers() {
    local user="$1"
    local specific_dbuser="${2:-}"
    local timestamp="${3:-}"
    local temp_dir="${TEMP_DIR:-$DEFAULT_TEMP_DIR}"

    local ts; ts=$(resolve_snapshot_timestamp "$user" "$timestamp") || return 1
    local snap_dir; snap_dir=$(get_snapshot_dir "$user")
    local snap_path="$snap_dir/$ts"
    local rsync_ssh; rsync_ssh=$(build_rsync_ssh_cmd)

    # Detect old format vs new format
    local pkgacct_base="$snap_path"
    if remote_exec "test -d '$snap_path/pkgacct'" 2>/dev/null; then
        pkgacct_base="$snap_path/pkgacct"
    fi

    local restore_dir="$temp_dir/restore-dbusers-$user"
    mkdir -p "$restore_dir" || {
        log_error "Failed to create restore temp directory"
        return 1
    }

    log_info "Restoring DB users & grants for $user from snapshot $ts"

    # Download mysql.sql (grant statements)
    local local_grants="$restore_dir/mysql.sql"

    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}"
        local pkgacct_sub; pkgacct_sub=$(_detect_pkgacct_base "" "$snap_subpath")
        if ! rclone_from_remote "${pkgacct_sub}/mysql.sql" "$restore_dir"; then
            log_error "No mysql.sql (grants) found in snapshot for $user"
            rm -rf "$restore_dir"
            return 1
        fi
    else
        local remote_grants="${pkgacct_base}/mysql.sql"
        if ! rsync -aHAX --rsync-path="rsync --fake-super" \
            -e "$rsync_ssh" \
            "${REMOTE_USER}@${REMOTE_HOST}:${remote_grants}" \
            "$local_grants" 2>/dev/null; then
            log_error "No mysql.sql (grants) found in snapshot for $user"
            rm -rf "$restore_dir"
            return 1
        fi
    fi

    # Get cPanel DB prefix for this account
    local db_prefix
    db_prefix=$(uapi --user="$user" Mysql get_restrictions 2>/dev/null | grep -oP '^\s+prefix:\s+\K\S+') || db_prefix=""

    log_info "Registering DB users & privileges (cPanel prefix: ${db_prefix:-none})..."

    # Extract unique db users from GRANT statements
    local grant_users; grant_users=$(grep -oP "TO\s+'?\K[^'@]+(?='@)" "$local_grants" | sort -u) || true

    if [[ -n "$grant_users" ]]; then
        while IFS= read -r dbuser; do
            [[ -z "$dbuser" ]] && continue

            # Skip account default DB user — it always exists with the account
            [[ "$dbuser" == "$user" ]] && continue

            # If a specific DB user was requested, skip all others
            if [[ -n "$specific_dbuser" && "$dbuser" != "$specific_dbuser" ]]; then
                continue
            fi

            if [[ -n "$db_prefix" && "$dbuser" != "${db_prefix}"* ]]; then
                log_warn "Skipping DB user $dbuser — does not match cPanel prefix '${db_prefix}'"
                continue
            fi

            # Create user via UAPI
            local uapi_out; uapi_out=$(uapi --user="$user" Mysql create_user name="$dbuser" password="$(openssl rand -base64 24)" 2>&1) || true
            if echo "$uapi_out" | grep -q 'status: 1'; then
                log_debug "Created DB user $dbuser via UAPI"
            elif echo "$uapi_out" | grep -qi 'exists'; then
                log_debug "DB user $dbuser already exists"
            else
                log_error "UAPI create_user failed for $dbuser: $uapi_out"
                rm -rf "$restore_dir"
                return 1
            fi

            # Extract databases this user has grants on and set privileges via UAPI
            # Strip backslash escapes (GRANT syntax uses \_ for literal underscores)
            local dbs; dbs=$(grep -i "TO '$dbuser'@" "$local_grants" | grep -oP "ON\s+\`\K[^\`]+" | sed 's/\\//g' | sort -u) || true
            if [[ -n "$dbs" ]]; then
                while IFS= read -r dbname; do
                    [[ -z "$dbname" || "$dbname" == "*" ]] && continue
                    if [[ -n "$db_prefix" && "$dbname" != "${db_prefix}"* ]]; then
                        log_warn "Skipping privileges for $dbuser on $dbname — database does not match cPanel prefix"
                        continue
                    fi
                    local sp_out; sp_out=$(uapi --user="$user" Mysql set_privileges_on_database user="$dbuser" database="$dbname" privileges=ALL 2>&1) || true
                    if ! echo "$sp_out" | grep -q 'status: 1'; then
                        log_error "UAPI set_privileges_on_database failed for $dbuser on $dbname: $sp_out"
                        rm -rf "$restore_dir"
                        return 1
                    fi
                    log_debug "Set privileges for $dbuser on $dbname via UAPI"
                done <<< "$dbs"
            fi
        done <<< "$grant_users"
    fi

    rm -rf "$restore_dir"
    log_info "DB users & grants restore completed for $user"
    return 0
}

restore_cpconfig() {
    local user="$1"
    local timestamp="${2:-}"
    local temp_dir="${TEMP_DIR:-$DEFAULT_TEMP_DIR}"

    local ts; ts=$(resolve_snapshot_timestamp "$user" "$timestamp") || return 1
    local snap_dir; snap_dir=$(get_snapshot_dir "$user")
    local snap_path="$snap_dir/$ts"
    local rsync_ssh; rsync_ssh=$(build_rsync_ssh_cmd)

    # Detect old format vs new format
    local pkgacct_base="$snap_path"
    if remote_exec "test -d '$snap_path/pkgacct'" 2>/dev/null; then
        pkgacct_base="$snap_path/pkgacct"
    fi

    local restore_dir="$temp_dir/restore-cpconfig-$user"
    mkdir -p "$restore_dir" || {
        log_error "Failed to create restore temp directory"
        return 1
    }

    log_info "Restoring panel config for $user from snapshot $ts"

    # Download cp/ directory
    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}"
        local pkgacct_sub; pkgacct_sub=$(_detect_pkgacct_base "" "$snap_subpath")
        if ! rclone_from_remote "${pkgacct_sub}/cp" "$restore_dir/cp"; then
            log_error "No cPanel config data found in snapshot for $user"
            rm -rf "$restore_dir"
            return 1
        fi
    else
        if ! rsync -aHAX --rsync-path="rsync --fake-super" \
            -e "$rsync_ssh" \
            "${REMOTE_USER}@${REMOTE_HOST}:${pkgacct_base}/cp/" \
            "$restore_dir/cp/" 2>/dev/null; then
            log_error "No cPanel config data found in snapshot for $user"
            rm -rf "$restore_dir"
            return 1
        fi
    fi

    # Copy to cPanel user config location
    local cp_user_file="$restore_dir/cp/$user"
    if [[ -f "$cp_user_file" ]]; then
        cp -f "$cp_user_file" "/var/cpanel/users/$user" || {
            log_error "Failed to copy cPanel config for $user"
            rm -rf "$restore_dir"
            return 1
        }
    else
        log_warn "No user config file found in cp/ directory for $user"
    fi

    # Rebuild cPanel caches
    log_info "Rebuilding cPanel user domain caches..."
    /usr/local/cpanel/scripts/updateuserdomains 2>/dev/null || log_warn "updateuserdomains returned non-zero"

    rm -rf "$restore_dir"
    log_info "Panel config restore completed for $user"
    return 0
}

restore_domains() {
    local user="$1"
    local specific_domain="${2:-}"
    local timestamp="${3:-}"
    local temp_dir="${TEMP_DIR:-$DEFAULT_TEMP_DIR}"

    local ts; ts=$(resolve_snapshot_timestamp "$user" "$timestamp") || return 1
    local snap_dir; snap_dir=$(get_snapshot_dir "$user")
    local snap_path="$snap_dir/$ts"
    local rsync_ssh; rsync_ssh=$(build_rsync_ssh_cmd)

    # Detect old format vs new format
    local pkgacct_base="$snap_path"
    if remote_exec "test -d '$snap_path/pkgacct'" 2>/dev/null; then
        pkgacct_base="$snap_path/pkgacct"
    fi

    local restore_dir="$temp_dir/restore-domains-$user"
    mkdir -p "$restore_dir" || {
        log_error "Failed to create restore temp directory"
        return 1
    }

    log_info "Restoring domains & DNS for $user from snapshot $ts"

    # Download userdata/, addons, dnszones/
    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}"
        local pkgacct_sub; pkgacct_sub=$(_detect_pkgacct_base "" "$snap_subpath")
        rclone_from_remote "${pkgacct_sub}/userdata" "$restore_dir/userdata" 2>/dev/null || log_warn "No userdata found in snapshot"
        rclone_from_remote "${pkgacct_sub}/addons" "$restore_dir" 2>/dev/null || log_debug "No addons file in snapshot"
        rclone_from_remote "${pkgacct_sub}/dnszones" "$restore_dir/dnszones" 2>/dev/null || log_warn "No DNS zones found in snapshot"
    else
        rsync -aHAX --rsync-path="rsync --fake-super" \
            -e "$rsync_ssh" \
            "${REMOTE_USER}@${REMOTE_HOST}:${pkgacct_base}/userdata/" \
            "$restore_dir/userdata/" 2>/dev/null || log_warn "No userdata found in snapshot"

        rsync -aHAX --rsync-path="rsync --fake-super" \
            -e "$rsync_ssh" \
            "${REMOTE_USER}@${REMOTE_HOST}:${pkgacct_base}/addons" \
            "$restore_dir/addons" 2>/dev/null || log_debug "No addons file in snapshot"

        rsync -aHAX --rsync-path="rsync --fake-super" \
            -e "$rsync_ssh" \
            "${REMOTE_USER}@${REMOTE_HOST}:${pkgacct_base}/dnszones/" \
            "$restore_dir/dnszones/" 2>/dev/null || log_warn "No DNS zones found in snapshot"
    fi

    # Restore userdata
    if [[ -d "$restore_dir/userdata" ]]; then
        log_info "Restoring userdata for $user..."
        mkdir -p "/var/cpanel/userdata/$user"
        cp -af "$restore_dir/userdata/"* "/var/cpanel/userdata/$user/" || {
            log_error "Failed to restore userdata for $user"
            rm -rf "$restore_dir"
            return 1
        }
    fi

    # Rebuild cPanel caches and Apache config
    log_info "Rebuilding cPanel domain caches..."
    /usr/local/cpanel/scripts/updateuserdomains 2>/dev/null || log_warn "updateuserdomains returned non-zero"
    /usr/local/cpanel/scripts/rebuildhttpdconf 2>/dev/null || log_warn "rebuildhttpdconf returned non-zero"

    # Restore DNS zones via whmapi1
    if [[ -d "$restore_dir/dnszones" ]]; then
        log_info "Restoring DNS zones via whmapi1..."
        for zone_file in "$restore_dir/dnszones"/*.db; do
            [[ -f "$zone_file" ]] || continue
            local zone_name; zone_name=$(basename "$zone_file" .db)
            [[ -z "$zone_name" ]] && continue

            # If a specific domain was requested, skip all others
            if [[ -n "$specific_domain" && "$zone_name" != "$specific_domain" ]]; then
                continue
            fi

            # Extract main IP from the zone's A record for adddns
            local zone_ip; zone_ip=$(grep -m1 -P "^${zone_name}\.\s+\d+\s+(IN\s+)?A\s+" "$zone_file" \
                | grep -oP 'A\s+\K[0-9.]+') || zone_ip=""

            # Ensure zone exists in cPanel
            local adddns_out; adddns_out=$(whmapi1 adddns domain="$zone_name" ip="$zone_ip" trueowner="$user" 2>&1) || true
            if echo "$adddns_out" | grep -q 'result: 1'; then
                log_debug "Created DNS zone for $zone_name via whmapi1"
            elif echo "$adddns_out" | grep -qi 'already exists\|exists on\|already has\|has dns'; then
                log_debug "DNS zone $zone_name already exists"
            else
                log_warn "whmapi1 adddns failed for $zone_name: $(echo "$adddns_out" | grep -oP 'reason:\s*\K.*' | head -1)"
            fi

            # Parse zone file and add records via whmapi1 addzonerecord
            # Skip SOA, NS, and comment lines — cPanel manages those
            local add_ok=0 add_fail=0
            while IFS= read -r line; do
                [[ -z "$line" || "$line" =~ ^[[:space:]]*\; || "$line" =~ ^\$ ]] && continue
                # Match: name [ttl] [IN] type data
                local rec_name rec_ttl rec_type rec_data
                if [[ "$line" =~ ^([^[:space:]]+)[[:space:]]+([0-9]+)[[:space:]]+(IN[[:space:]]+)?([A-Z]+)[[:space:]]+(.+)$ ]]; then
                    rec_name="${BASH_REMATCH[1]}"
                    rec_ttl="${BASH_REMATCH[2]}"
                    rec_type="${BASH_REMATCH[4]}"
                    rec_data="${BASH_REMATCH[5]}"
                elif [[ "$line" =~ ^([^[:space:]]+)[[:space:]]+(IN[[:space:]]+)?([A-Z]+)[[:space:]]+(.+)$ ]]; then
                    rec_name="${BASH_REMATCH[1]}"
                    rec_ttl="14400"
                    rec_type="${BASH_REMATCH[3]}"
                    rec_data="${BASH_REMATCH[4]}"
                else
                    continue
                fi

                # Skip SOA, NS — cPanel manages these
                [[ "$rec_type" == "SOA" || "$rec_type" == "NS" ]] && continue
                # Strip trailing dot from name
                rec_name="${rec_name%.}"
                # Strip trailing comments and whitespace
                rec_data="${rec_data%%;*}"
                rec_data="${rec_data%"${rec_data##*[![:space:]]}"}"
                # Strip trailing dots from data (FQDN format)
                rec_data="${rec_data%.}"

                local rec_out=""
                case "$rec_type" in
                    A|AAAA)
                        rec_out=$(whmapi1 addzonerecord domain="$zone_name" name="$rec_name" type="$rec_type" address="$rec_data" ttl="$rec_ttl" 2>&1) || true
                        ;;
                    CNAME)
                        rec_out=$(whmapi1 addzonerecord domain="$zone_name" name="$rec_name" type=CNAME cname="$rec_data" ttl="$rec_ttl" 2>&1) || true
                        ;;
                    MX)
                        local mx_prio="${rec_data%%[[:space:]]*}"
                        local mx_host="${rec_data#*[[:space:]]}"
                        mx_host="${mx_host%.}"
                        rec_out=$(whmapi1 addzonerecord domain="$zone_name" name="$rec_name" type=MX exchange="$mx_host" preference="$mx_prio" ttl="$rec_ttl" 2>&1) || true
                        ;;
                    TXT)
                        # Strip surrounding quotes
                        rec_data="${rec_data#\"}"
                        rec_data="${rec_data%\"}"
                        rec_out=$(whmapi1 addzonerecord domain="$zone_name" name="$rec_name" type=TXT txtdata="$rec_data" ttl="$rec_ttl" 2>&1) || true
                        ;;
                    SRV)
                        # SRV: priority weight port target
                        local srv_prio srv_weight srv_port srv_target
                        read -r srv_prio srv_weight srv_port srv_target <<< "$rec_data"
                        srv_target="${srv_target%.}"
                        rec_out=$(whmapi1 addzonerecord domain="$zone_name" name="$rec_name" type=SRV priority="$srv_prio" weight="$srv_weight" port="$srv_port" target="$srv_target" ttl="$rec_ttl" 2>&1) || true
                        ;;
                    CAA)
                        rec_out=$(whmapi1 addzonerecord domain="$zone_name" name="$rec_name" type=CAA record="$rec_data" ttl="$rec_ttl" 2>&1) || true
                        ;;
                    *)
                        log_debug "Skipping unsupported record type $rec_type for $rec_name"
                        continue
                        ;;
                esac
                if echo "$rec_out" | grep -q 'result: 1'; then
                    ((add_ok++)) || true
                else
                    local reason; reason=$(echo "$rec_out" | grep -oP 'reason:\s*\K.*' | head -1)
                    if echo "$reason" | grep -qi 'already has\|duplicate'; then
                        log_debug "Record $rec_name $rec_type already exists in $zone_name"
                    else
                        log_debug "addzonerecord failed for $rec_name $rec_type in $zone_name: $reason"
                        ((add_fail++)) || true
                    fi
                fi
            done < "$zone_file"
            log_info "Restored DNS zone $zone_name via whmapi1 ($add_ok records added, $add_fail failed)"
        done
    fi

    rm -rf "$restore_dir"
    log_info "Domains & DNS restore completed for $user"
    return 0
}

restore_ssl() {
    local user="$1"
    local specific_cert="${2:-}"
    local timestamp="${3:-}"
    local temp_dir="${TEMP_DIR:-$DEFAULT_TEMP_DIR}"

    local ts; ts=$(resolve_snapshot_timestamp "$user" "$timestamp") || return 1
    local snap_dir; snap_dir=$(get_snapshot_dir "$user")
    local snap_path="$snap_dir/$ts"
    local rsync_ssh; rsync_ssh=$(build_rsync_ssh_cmd)

    # Detect old format vs new format
    local pkgacct_base="$snap_path"
    if remote_exec "test -d '$snap_path/pkgacct'" 2>/dev/null; then
        pkgacct_base="$snap_path/pkgacct"
    fi

    local restore_dir="$temp_dir/restore-ssl-$user"
    mkdir -p "$restore_dir" || {
        log_error "Failed to create restore temp directory"
        return 1
    }

    log_info "Restoring SSL certificates for $user from snapshot $ts"

    local found_any=false

    # Download ssl/, sslcerts/, autossl.json
    if _is_rclone_mode; then
        local snap_subpath="accounts/${user}/snapshots/${ts}"
        local pkgacct_sub; pkgacct_sub=$(_detect_pkgacct_base "" "$snap_subpath")
        rclone_from_remote "${pkgacct_sub}/ssl" "$restore_dir/ssl" 2>/dev/null && found_any=true
        rclone_from_remote "${pkgacct_sub}/sslcerts" "$restore_dir/sslcerts" 2>/dev/null && found_any=true
        rclone_from_remote "${pkgacct_sub}/autossl.json" "$restore_dir" 2>/dev/null && found_any=true
        rclone_from_remote "${snap_subpath}/homedir/ssl" "$restore_dir/homedir-ssl" 2>/dev/null && found_any=true
    else
        if rsync -aHAX --rsync-path="rsync --fake-super" \
            -e "$rsync_ssh" \
            "${REMOTE_USER}@${REMOTE_HOST}:${pkgacct_base}/ssl/" \
            "$restore_dir/ssl/" 2>/dev/null; then
            found_any=true
        fi

        if rsync -aHAX --rsync-path="rsync --fake-super" \
            -e "$rsync_ssh" \
            "${REMOTE_USER}@${REMOTE_HOST}:${pkgacct_base}/sslcerts/" \
            "$restore_dir/sslcerts/" 2>/dev/null; then
            found_any=true
        fi

        if rsync -aHAX --rsync-path="rsync --fake-super" \
            -e "$rsync_ssh" \
            "${REMOTE_USER}@${REMOTE_HOST}:${pkgacct_base}/autossl.json" \
            "$restore_dir/autossl.json" 2>/dev/null; then
            found_any=true
        fi

        # Also download homedir/ssl/ (user-managed certs)
        local homedir_base="${snap_path}/homedir"
        if rsync -aHAX --rsync-path="rsync --fake-super" \
            -e "$rsync_ssh" \
            "${REMOTE_USER}@${REMOTE_HOST}:${homedir_base}/ssl/" \
            "$restore_dir/homedir-ssl/" 2>/dev/null; then
            found_any=true
        fi
    fi

    if [[ "$found_any" == "false" ]]; then
        log_error "No SSL data found in snapshot for $user"
        rm -rf "$restore_dir"
        return 1
    fi

    # Install certs on domains via whmapi1 installssl (no file copies)
    log_info "Installing SSL certificates via whmapi1..."
    local installed_count=0

    # Find cert files in ssl/ and sslcerts/ directories
    local cert_dirs=()
    [[ -d "$restore_dir/ssl/certs" ]] && cert_dirs+=("$restore_dir/ssl/certs")
    [[ -d "$restore_dir/sslcerts" ]] && cert_dirs+=("$restore_dir/sslcerts")
    [[ -d "$restore_dir/ssl/installed/certs" ]] && cert_dirs+=("$restore_dir/ssl/installed/certs")
    [[ -d "$restore_dir/homedir-ssl/certs" ]] && cert_dirs+=("$restore_dir/homedir-ssl/certs")

    for cert_search_dir in "${cert_dirs[@]}"; do
        for cert_file in "$cert_search_dir"/*.crt "$cert_search_dir"/*.pem; do
            [[ -f "$cert_file" ]] || continue

            # If a specific cert was requested, skip all others
            if [[ -n "$specific_cert" ]]; then
                local cert_base; cert_base=$(basename "$cert_file" .crt)
                [[ "$cert_base" == "$cert_file" ]] && cert_base=$(basename "$cert_file" .pem)
                if [[ "$cert_base" != "$specific_cert" ]]; then
                    continue
                fi
            fi

            # Extract domain from certificate
            local cert_domain; cert_domain=$(openssl x509 -in "$cert_file" -noout -subject 2>/dev/null \
                | grep -oP 'CN\s*=\s*\K[^\s/]+') || continue
            [[ -z "$cert_domain" ]] && continue

            # Read cert content
            local cert_content; cert_content=$(cat "$cert_file")

            # Find matching key file
            local key_content=""
            local cert_basename; cert_basename=$(basename "$cert_file")
            local key_basename="${cert_basename%.*}.key"
            # Search for key in ssl/keys/ directory
            for key_search_dir in "$restore_dir/ssl/keys" "$restore_dir/ssl/installed/keys" "$restore_dir/homedir-ssl/keys"; do
                if [[ -f "$key_search_dir/$key_basename" ]]; then
                    key_content=$(cat "$key_search_dir/$key_basename")
                    break
                fi
            done
            # If no exact match, try to find any key that matches the cert
            if [[ -z "$key_content" ]]; then
                local cert_modulus; cert_modulus=$(openssl x509 -in "$cert_file" -noout -modulus 2>/dev/null) || continue
                for key_search_dir in "$restore_dir/ssl/keys" "$restore_dir/ssl/installed/keys" "$restore_dir/homedir-ssl/keys"; do
                    [[ -d "$key_search_dir" ]] || continue
                    for key_file in "$key_search_dir"/*.key; do
                        [[ -f "$key_file" ]] || continue
                        local key_modulus; key_modulus=$(openssl rsa -in "$key_file" -noout -modulus 2>/dev/null) || continue
                        if [[ "$cert_modulus" == "$key_modulus" ]]; then
                            key_content=$(cat "$key_file")
                            break 2
                        fi
                    done
                done
            fi

            [[ -z "$key_content" ]] && { log_debug "No matching key found for cert $cert_domain, skipping"; continue; }

            # Find CA bundle if available
            local cab_content=""
            for cab_search_dir in "$restore_dir/ssl/cabundles" "$restore_dir/ssl/installed/cabundles" "$restore_dir/homedir-ssl/cabundles"; do
                local cab_file="$cab_search_dir/${cert_basename%.*}.cabundle"
                if [[ -f "$cab_file" ]]; then
                    cab_content=$(cat "$cab_file")
                    break
                fi
            done

            # Install via whmapi1
            local install_out
            if [[ -n "$cab_content" ]]; then
                install_out=$(whmapi1 installssl domain="$cert_domain" crt="$cert_content" key="$key_content" cab="$cab_content" 2>&1) || true
            else
                install_out=$(whmapi1 installssl domain="$cert_domain" crt="$cert_content" key="$key_content" 2>&1) || true
            fi

            if echo "$install_out" | grep -q 'result: 1'; then
                log_info "Installed SSL cert for $cert_domain via whmapi1"
                ((installed_count++)) || true
            else
                log_warn "whmapi1 installssl failed for $cert_domain: $(echo "$install_out" | grep -oP 'reason:\s*\K.*' | head -1)"
            fi
        done
    done

    if (( installed_count == 0 )); then
        log_warn "No SSL certificates were installed via whmapi1"
    else
        log_info "Installed $installed_count SSL certificate(s) via whmapi1"
    fi

    rm -rf "$restore_dir"
    log_info "SSL restore completed for $user"
    return 0
}

restore_server() {
    local timestamp="${1:-}"

    log_info "Starting full server rebuild..."

    local accounts; accounts=$(list_remote_accounts)
    if [[ -z "$accounts" ]]; then
        log_error "No accounts found on remote for restore"
        return 1
    fi

    local total=0 succeeded=0 failed=0
    local failed_accounts=""

    while IFS= read -r user; do
        [[ -z "$user" ]] && continue
        ((total++)) || true

        log_info "--- Restoring account $user ($total) ---"
        if restore_full_account "$user" "$timestamp" "" ""; then
            ((succeeded++)) || true
        else
            ((failed++)) || true
            failed_accounts+="  - $user"$'\n'
        fi
    done <<< "$accounts"

    echo ""
    echo "============================================"
    echo "Server Restore Summary"
    echo "============================================"
    echo "Total accounts: $total"
    echo "Succeeded:      $succeeded"
    echo "Failed:         $failed"
    if [[ -n "$failed_accounts" ]]; then
        echo ""
        echo "Failed accounts:"
        echo "$failed_accounts"
    fi
    echo "============================================"

    (( failed > 0 )) && return 1
    return 0
}
