#!/usr/bin/env bash
# gniza4cp/lib/sysbackup.sh — System-level WHM backup: API exports, file staging, snapshot lifecycle

[[ -n "${_GNIZA4CP_SYSBACKUP_LOADED:-}" ]] && return 0
_GNIZA4CP_SYSBACKUP_LOADED=1

# ── Path Helpers ─────────────────────────────────────────────

_system_snap_base() {
    local hostname; hostname=$(hostname -f)
    echo "${REMOTE_BASE}/${hostname}/system/snapshots"
}

# Rclone subpath (no REMOTE_BASE prefix — _rclone_remote_path adds it)
_system_snap_subpath() {
    echo "system/snapshots"
}

# ── Snapshot Lifecycle ───────────────────────────────────────

list_system_snapshots() {
    if _is_rclone_mode; then
        local snap_sub; snap_sub=$(_system_snap_subpath)
        local all_dirs; all_dirs=$(rclone_list_dirs "$snap_sub") || true
        [[ -z "$all_dirs" ]] && return 0

        local completed=""
        while IFS= read -r dir; do
            [[ -z "$dir" ]] && continue
            if rclone_exists "${snap_sub}/${dir}/.complete"; then
                completed+="${dir}"$'\n'
            fi
        done <<< "$all_dirs"

        [[ -n "$completed" ]] && echo "$completed" | sort -r
        return 0
    fi

    local snap_dir; snap_dir=$(_system_snap_base)
    local raw; raw=$(remote_exec "ls -1d '$snap_dir'/[0-9]* 2>/dev/null | grep -v '\\.partial$' | sort -r" 2>/dev/null) || true
    if [[ -n "$raw" ]]; then
        echo "$raw" | xargs -I{} basename {} | sort -r
    fi
}

get_latest_system_snapshot() {
    if _is_rclone_mode; then
        local snap_sub; snap_sub=$(_system_snap_subpath)
        local latest; latest=$(rclone_cat "${snap_sub}/latest.txt" 2>/dev/null) || true
        if [[ -n "$latest" ]]; then
            if rclone_exists "${snap_sub}/${latest}/.complete"; then
                echo "$latest"
                return 0
            fi
        fi
        list_system_snapshots | head -1
        return 0
    fi

    list_system_snapshots | head -1
}

resolve_system_snapshot_timestamp() {
    local requested="$1"

    if [[ -z "$requested" || "$requested" == "LATEST" || "$requested" == "latest" ]]; then
        get_latest_system_snapshot
    elif _is_rclone_mode; then
        local snap_sub; snap_sub=$(_system_snap_subpath)
        if rclone_exists "${snap_sub}/${requested}/.complete"; then
            echo "$requested"
        else
            log_error "System snapshot not found or incomplete: $requested"
            return 1
        fi
    else
        local snap_dir; snap_dir=$(_system_snap_base)
        if remote_exec "test -d '$snap_dir/$requested'" 2>/dev/null; then
            echo "$requested"
        else
            log_error "System snapshot not found: $requested"
            return 1
        fi
    fi
}

clean_partial_system_snapshots() {
    if _is_rclone_mode; then
        local snap_sub; snap_sub=$(_system_snap_subpath)
        local all_dirs; all_dirs=$(rclone_list_dirs "$snap_sub") || true
        [[ -z "$all_dirs" ]] && return 0

        while IFS= read -r dir; do
            [[ -z "$dir" ]] && continue
            if ! rclone_exists "${snap_sub}/${dir}/.complete"; then
                log_info "Purging incomplete system snapshot: $dir"
                rclone_purge "${snap_sub}/${dir}" || {
                    log_warn "Failed to purge incomplete system snapshot: $dir"
                }
            fi
        done <<< "$all_dirs"
        return 0
    fi

    local snap_dir; snap_dir=$(_system_snap_base)
    local partials; partials=$(remote_exec "ls -1d '$snap_dir'/*.partial 2>/dev/null" 2>/dev/null) || true
    if [[ -n "$partials" ]]; then
        log_info "Cleaning partial system snapshots..."
        remote_exec "rm -rf '$snap_dir'/*.partial" || {
            log_warn "Failed to clean partial system snapshots"
        }
    fi
}

finalize_system_snapshot() {
    local ts="$1"

    if _is_rclone_mode; then
        local snap_sub; snap_sub=$(_system_snap_subpath)
        log_info "Finalizing system snapshot: $ts (rclone)"
        rclone_rcat "${snap_sub}/${ts}/.complete" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" || {
            log_error "Failed to create .complete marker for system/$ts"
            return 1
        }
        rclone_rcat "${snap_sub}/latest.txt" "$ts" || {
            log_warn "Failed to update system latest.txt"
            return 1
        }
        log_debug "Updated system latest.txt -> $ts"
        return 0
    fi

    local snap_dir; snap_dir=$(_system_snap_base)
    log_info "Finalizing system snapshot: $ts"
    remote_exec "mv '$snap_dir/${ts}.partial' '$snap_dir/$ts'" || {
        log_error "Failed to finalize system snapshot: $ts"
        return 1
    }

    # Update latest symlink
    local hostname; hostname=$(hostname -f)
    local base="${REMOTE_BASE}/${hostname}/system"
    remote_exec "ln -sfn '$snap_dir/$ts' '$base/latest'" || {
        log_warn "Failed to update system latest symlink"
        return 1
    }
    log_debug "Updated system latest symlink -> $ts"
}

enforce_system_retention() {
    local keep="${RETENTION_COUNT:-$DEFAULT_RETENTION_COUNT}"

    log_debug "Enforcing system retention: keeping $keep snapshots"

    local snapshots; snapshots=$(list_system_snapshots)
    if [[ -z "$snapshots" ]]; then
        log_debug "No system snapshots found, nothing to prune"
        return 0
    fi

    local count=0
    local pruned=0
    while IFS= read -r snap; do
        ((count++)) || true
        if (( count > keep )); then
            log_info "Pruning old system snapshot: $snap"
            if _is_rclone_mode; then
                local snap_sub; snap_sub=$(_system_snap_subpath)
                rclone_purge "${snap_sub}/${snap}" || {
                    log_warn "Failed to purge system snapshot: $snap"
                }
            else
                local snap_dir; snap_dir=$(_system_snap_base)
                remote_exec "rm -rf '$snap_dir/$snap'" || {
                    log_warn "Failed to prune system snapshot: $snap_dir/$snap"
                }
            fi
            ((pruned++)) || true
        fi
    done <<< "$snapshots"

    if (( pruned > 0 )); then
        log_info "Pruned $pruned old system snapshot(s)"
    fi
}

# ── Transfer to Remote ───────────────────────────────────────

transfer_system_backup() {
    local stage_dir="$1"
    local ts="$2"
    local prev_snapshot="${3:-}"

    if _is_rclone_mode; then
        local snap_sub; snap_sub=$(_system_snap_subpath)
        log_info "Transferring system backup (rclone)..."
        rclone_to_remote "$stage_dir" "${snap_sub}/${ts}"
        return
    fi

    local snap_dir; snap_dir=$(_system_snap_base)
    local dest="$snap_dir/${ts}.partial/"
    local link_dest=""

    if [[ -n "$prev_snapshot" ]]; then
        link_dest="$snap_dir/$prev_snapshot"
    fi

    ensure_remote_dir "$dest" || return 1

    log_info "Transferring system backup..."
    rsync_to_remote "$stage_dir" "$dest" "$link_dest"
}

# ── API Export Functions ─────────────────────────────────────

_export_packages() {
    local api_dir="$1"
    log_info "Exporting hosting packages..."
    if ! whmapi1 listpkgs --output=json > "$api_dir/packages.json" 2>/dev/null; then
        log_error "Failed to export packages"
        return 1
    fi
}

_export_tweaksettings() {
    local api_dir="$1"
    log_info "Exporting tweak settings..."
    if ! whmapi1 get_tweaksettings --output=json > "$api_dir/tweaksettings.json" 2>/dev/null; then
        log_error "Failed to export tweak settings"
        return 1
    fi
}

_export_dns_zones() {
    local api_dir="$1"
    log_info "Exporting DNS zones..."

    if ! whmapi1 listzones --output=json > "$api_dir/zones.json" 2>/dev/null; then
        log_error "Failed to export zone list"
        return 1
    fi

    mkdir -p "$api_dir/zones"
    local zones; zones=$(python3 -c "
import sys, json
data = json.load(sys.stdin)
for z in data.get('data', {}).get('zone', []):
    print(z['domain'])
" < "$api_dir/zones.json" 2>/dev/null) || true

    if [[ -z "$zones" ]]; then
        log_warn "No DNS zones found to export"
        return 0
    fi

    local count=0
    while IFS= read -r domain; do
        [[ -z "$domain" ]] && continue
        if whmapi1 dumpzone domain="$domain" --output=json > "$api_dir/zones/${domain}.zone" 2>/dev/null; then
            ((count++)) || true
        else
            log_warn "Failed to dump zone for: $domain"
        fi
    done <<< "$zones"

    log_info "Exported $count DNS zone(s)"
}

_export_ips() {
    local api_dir="$1"
    log_info "Exporting IP configuration..."
    if ! whmapi1 listips --output=json > "$api_dir/ips.json" 2>/dev/null; then
        log_error "Failed to export IP list"
        return 1
    fi
}

_export_php() {
    local api_dir="$1"
    log_info "Exporting PHP configuration..."

    # Combine multiple PHP API calls into one JSON file
    local tmpfile; tmpfile=$(mktemp)
    {
        echo '{'
        echo '"system_default":'
        whmapi1 php_get_system_default_version --output=json 2>/dev/null || echo '{}'
        echo ','
        echo '"installed_versions":'
        whmapi1 php_get_installed_versions --output=json 2>/dev/null || echo '{}'
        echo ','
        echo '"vhost_versions":'
        whmapi1 php_get_vhost_versions --output=json 2>/dev/null || echo '{}'
        echo '}'
    } > "$tmpfile"

    if [[ -s "$tmpfile" ]]; then
        mv "$tmpfile" "$api_dir/php.json"
    else
        rm -f "$tmpfile"
        log_error "Failed to export PHP configuration"
        return 1
    fi
}

# ── File Staging ─────────────────────────────────────────────

# Known system paths to back up
readonly _SYSBACKUP_PATHS=(
    # cPanel core config
    /var/cpanel/packages
    /var/cpanel/features
    /var/cpanel/cpanel.config
    /etc/wwwacct.conf
    /etc/cpanel
    /var/cpanel/ssl
    /var/cpanel/MultiPHP
    # DNS
    /var/named
    /etc/named.conf
    # Exim
    /etc/exim.conf
    /etc/exim.conf.local
    /etc/exim.conf.localopts
    # Mail routing
    /etc/localdomains
    /etc/remotedomains
    /etc/secondarymx
    /etc/valiases
    /etc/vdomainaliases
    /etc/vfilters
    # Apache / EasyApache
    /etc/httpd/conf
    /usr/local/apache/conf
    /etc/httpd/conf.d
    /etc/cpanel/ea4
    # MySQL
    /etc/my.cnf
    /root/.my.cnf
    # CSF firewall
    /etc/csf
    # Root cron & SSH
    /var/spool/cron/root
    /root/.ssh
    # Network
    /etc/ips
    /etc/reservedips
    /etc/reservedipreasons
    /etc/sysconfig/network
    /etc/resolv.conf
    # gniza4cp's own config
    /etc/gniza4cp
)

_stage_files() {
    local stage_dir="$1"
    local files_dir="$stage_dir/files"
    local count=0
    local failed=0

    for src_path in "${_SYSBACKUP_PATHS[@]}"; do
        if [[ ! -e "$src_path" ]]; then
            log_debug "Skipping (not found): $src_path"
            continue
        fi

        # Mirror the source path under files/ (strip leading /)
        local rel_path="${src_path#/}"
        local dest="$files_dir/$rel_path"

        # Create parent directory
        mkdir -p "$(dirname "$dest")"

        if cp -a "$src_path" "$dest" 2>/dev/null; then
            ((count++)) || true
            log_debug "Staged: $src_path"
        else
            ((failed++)) || true
            log_warn "Failed to stage: $src_path"
        fi
    done

    # Also stage ea-php configs if they exist
    local ea_php_dirs; ea_php_dirs=$(ls -d /opt/cpanel/ea-php*/root/etc/ 2>/dev/null) || true
    if [[ -n "$ea_php_dirs" ]]; then
        while IFS= read -r ea_dir; do
            [[ -z "$ea_dir" ]] && continue
            local rel="${ea_dir#/}"
            local dest="$files_dir/$rel"
            mkdir -p "$(dirname "$dest")"
            if cp -a "$ea_dir" "$dest" 2>/dev/null; then
                ((count++)) || true
                log_debug "Staged: $ea_dir"
            else
                ((failed++)) || true
                log_warn "Failed to stage: $ea_dir"
            fi
        done <<< "$ea_php_dirs"
    fi

    log_info "Staged $count system path(s) ($failed failed)"
    return 0
}

# ── Backup Orchestrator ──────────────────────────────────────

run_system_backup() {
    local stage_dir="$1"
    local api_dir="$stage_dir/api"
    local api_failed=0
    local api_succeeded=0

    mkdir -p "$api_dir"
    mkdir -p "$stage_dir/files"

    log_info "=== System Backup: API exports ==="

    # Export each API category — continue on failure
    if _export_packages "$api_dir"; then
        ((api_succeeded++)) || true
    else
        ((api_failed++)) || true
    fi

    if _export_tweaksettings "$api_dir"; then
        ((api_succeeded++)) || true
    else
        ((api_failed++)) || true
    fi

    if _export_dns_zones "$api_dir"; then
        ((api_succeeded++)) || true
    else
        ((api_failed++)) || true
    fi

    if _export_ips "$api_dir"; then
        ((api_succeeded++)) || true
    else
        ((api_failed++)) || true
    fi

    if _export_php "$api_dir"; then
        ((api_succeeded++)) || true
    else
        ((api_failed++)) || true
    fi

    log_info "API exports: $api_succeeded succeeded, $api_failed failed"

    log_info "=== System Backup: File staging ==="
    _stage_files "$stage_dir"

    if (( api_failed > 0 )); then
        log_warn "System backup completed with $api_failed API export failure(s)"
        return 0  # Don't fail the whole backup for partial API failures
    fi

    log_info "System backup staging complete"
    return 0
}

cleanup_system_stage() {
    local temp_dir="${TEMP_DIR:-$DEFAULT_TEMP_DIR}"
    local sys_dir="$temp_dir/system"
    if [[ -d "$sys_dir" ]]; then
        rm -rf "$sys_dir"
        log_debug "Cleaned up system staging directory"
    fi
}
