#!/usr/bin/env bash
set -Eeuo pipefail

# wild-app-backup - Generic backup script for wild-cloud apps
# Usage: wild-app-backup <app-name> [--all]

# --- Initialize Wild Cloud environment --------------------------------------- 
if [ -z "${WC_ROOT:-}" ]; then
    echo "WC_ROOT is not set." >&2
    exit 1
else
    source "${WC_ROOT}/scripts/common.sh"
    init_wild_env
fi

# --- Configuration ------------------------------------------------------------
get_staging_dir() {
    if wild-config cloud.backup.staging --check; then
        wild-config cloud.backup.staging
    else
        echo "Staging directory is not set. Configure 'cloud.backup.staging' in config.yaml." >&2
        exit 1
    fi
}

# --- Helpers ------------------------------------------------------------------
require_k8s() {
    if ! command -v kubectl >/dev/null 2>&1; then
        echo "kubectl not found." >&2
        exit 1
    fi
}

require_yq() {
    if ! command -v yq >/dev/null 2>&1; then
        echo "yq not found. Required for parsing manifest.yaml files." >&2
        exit 1
    fi
}

get_timestamp() {
    date -u +'%Y%m%dT%H%M%SZ'
}

# --- App Discovery ------------------------------------------------------------
discover_database_deps() {
    local app_name="$1"
    local manifest_file="${WC_HOME}/apps/${app_name}/manifest.yaml"
    
    if [[ -f "$manifest_file" ]]; then
        yq eval '.requires[].name' "$manifest_file" 2>/dev/null | grep -E '^(postgres|mysql|redis)$' || true
    fi
}

discover_app_pvcs() {
    local app_name="$1"
    kubectl get pvc -n "$app_name" -l "app=$app_name" --no-headers -o custom-columns=":metadata.name" 2>/dev/null || true
}

get_app_pods() {
    local app_name="$1"
    kubectl get pods -n "$app_name" -l "app=$app_name" \
        -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}' 2>/dev/null | \
        tr ' ' '\n' | head -1 || true
}

discover_pvc_mount_paths() {
    local app_name="$1" pvc_name="$2"
    
    # Find the volume name that uses this PVC
    local volume_name
    volume_name=$(kubectl get deploy -n "$app_name" -l "app=$app_name" \
        -o jsonpath='{.items[*].spec.template.spec.volumes[?(@.persistentVolumeClaim.claimName=="'$pvc_name'")].name}' 2>/dev/null | awk 'NR==1{print; exit}')
    
    if [[ -n "$volume_name" ]]; then
        # Find the mount path for this volume (get first mount path)
        local mount_path
        mount_path=$(kubectl get deploy -n "$app_name" -l "app=$app_name" \
            -o jsonpath='{.items[*].spec.template.spec.containers[*].volumeMounts[?(@.name=="'$volume_name'")].mountPath}' 2>/dev/null | \
            tr ' ' '\n' | head -1)
        
        if [[ -n "$mount_path" ]]; then
            echo "$mount_path"
            return 0
        fi
    fi
    
    # No mount path found
    return 1
}

# --- Database Backup Functions -----------------------------------------------
backup_postgres_database() {
    local app_name="$1"
    local backup_dir="$2"
    local timestamp="$3"
    local db_name="${app_name}"
    
    local pg_ns="postgres"
    local pg_deploy="postgres-deployment"
    local db_superuser="postgres"
    
    echo "Backing up PostgreSQL database '$db_name'..." >&2
    
    # Check if postgres is available
    if ! kubectl get pods -n "$pg_ns" >/dev/null 2>&1; then
        echo "PostgreSQL namespace '$pg_ns' not accessible. Skipping database backup." >&2
        return 1
    fi
    
    local db_dump="${backup_dir}/database_${timestamp}.dump"
    local db_globals="${backup_dir}/globals_${timestamp}.sql"
    
    # Database dump (custom format, compressed)
    if ! kubectl exec -n "$pg_ns" deploy/"$pg_deploy" -- bash -lc \
        "pg_dump -U ${db_superuser} -Fc -Z 9 ${db_name}" > "$db_dump"
    then
        echo "Database dump failed for '$app_name'." >&2
        return 1
    fi
    
    # Verify dump integrity
    # if ! kubectl exec -i -n "$pg_ns" deploy/"$pg_deploy" -- bash -lc "pg_restore -l >/dev/null" < "$db_dump"; then
    #     echo "Database dump integrity check failed for '$app_name'." >&2
    #     return 1
    # fi
    
    # Dump globals (roles, permissions)
    if ! kubectl exec -n "$pg_ns" deploy/"$pg_deploy" -- bash -lc \
        "pg_dumpall -U ${db_superuser} -g" > "$db_globals"
    then
        echo "Globals dump failed for '$app_name'." >&2
        return 1
    fi
    
    echo "  Database dump: $db_dump" >&2
    echo "  Globals dump: $db_globals" >&2
    
    # Return paths for manifest generation
    echo "$db_dump $db_globals"
}

backup_mysql_database() {
    local app_name="$1"
    local backup_dir="$2" 
    local timestamp="$3"
    local db_name="${app_name}"
    
    local mysql_ns="mysql"
    local mysql_deploy="mysql-deployment"
    local mysql_user="root"
    
    echo "Backing up MySQL database '$db_name'..." >&2
    
    if ! kubectl get pods -n "$mysql_ns" >/dev/null 2>&1; then
        echo "MySQL namespace '$mysql_ns' not accessible. Skipping database backup." >&2
        return 1
    fi
    
    local db_dump="${backup_dir}/database_${timestamp}.sql"
    
    # Get MySQL root password from secret
    local mysql_password
    if mysql_password=$(kubectl get secret -n "$mysql_ns" mysql-secret -o jsonpath='{.data.password}' 2>/dev/null | base64 -d); then
        # MySQL dump with password
        if ! kubectl exec -n "$mysql_ns" deploy/"$mysql_deploy" -- bash -c \
            "mysqldump -u${mysql_user} -p'${mysql_password}' --single-transaction --routines --triggers ${db_name}" > "$db_dump"
        then
            echo "MySQL dump failed for '$app_name'." >&2
            return 1
        fi
    else
        echo "Could not retrieve MySQL password. Skipping database backup." >&2
        return 1
    fi
    
    echo "  Database dump: $db_dump" >&2
    echo "$db_dump"
}

# --- PVC Backup Functions ----------------------------------------------------
backup_pvc() {
    local app_name="$1"
    local pvc_name="$2" 
    local backup_dir="$3"
    local timestamp="$4"
    
    echo "Backing up PVC '$pvc_name' from namespace '$app_name'..." >&2
    
    # Get a running pod that actually uses this specific PVC
    local app_pod
    # First try to find a pod that has this exact PVC volume mounted
    local pvc_volume_id=$(kubectl get pvc -n "$app_name" "$pvc_name" -o jsonpath='{.spec.volumeName}' 2>/dev/null)
    if [[ -n "$pvc_volume_id" ]]; then
        # Look for a pod that has a mount from this specific volume
        app_pod=$(kubectl get pods -n "$app_name" -l "app=$app_name" -o json 2>/dev/null | \
            jq -r '.items[] | select(.status.phase=="Running") | select(.spec.volumes[]?.persistentVolumeClaim.claimName=="'$pvc_name'") | .metadata.name' | head -1)
    fi
    
    # Fallback to any running pod
    if [[ -z "$app_pod" ]]; then
        app_pod=$(get_app_pods "$app_name")
    fi
    
    if [[ -z "$app_pod" ]]; then
        echo "No running pods found for app '$app_name'. Skipping PVC backup." >&2
        return 1
    fi
    
    echo "Using pod '$app_pod' for PVC backup" >&2
    
    # Discover mount path for this PVC
    local mount_path
    mount_path=$(discover_pvc_mount_paths "$app_name" "$pvc_name" | awk 'NR==1{print; exit}')
    
    if [[ -z "$mount_path" ]]; then
        echo "Could not determine mount path for PVC '$pvc_name'. Trying to detect..." >&2
        # Try to find any volume mount that might be the PVC by looking at df output
        mount_path=$(kubectl exec -n "$app_name" "$app_pod" -- sh -c "df | grep longhorn | awk '{print \$6}' | head -1" 2>/dev/null)
        if [[ -z "$mount_path" ]]; then
            mount_path="/data"  # Final fallback
        fi
        echo "Using detected/fallback mount path: $mount_path" >&2
    fi
    
    local pvc_backup_dir="${backup_dir}/${pvc_name}"
    mkdir -p "$pvc_backup_dir"
    
    # Stream tar directly from pod to staging directory for restic deduplication
    local parent_dir=$(dirname "$mount_path")
    local dir_name=$(basename "$mount_path")
    
    echo "  Streaming PVC data directly to staging..." >&2
    if kubectl exec -n "$app_name" "$app_pod" -- tar -C "$parent_dir" -cf - "$dir_name" | tar -xf - -C "$pvc_backup_dir" 2>/dev/null; then
        echo "  PVC data streamed successfully" >&2
    else
        echo "PVC backup failed for '$pvc_name' in '$app_name'." >&2
        return 1
    fi
    
    echo "  PVC backup directory: $pvc_backup_dir" >&2
    echo "$pvc_backup_dir"
}

# --- Main Backup Function ----------------------------------------------------
backup_app() {
    local app_name="$1"
    local staging_dir="$2"
    
    echo "=========================================="
    echo "Starting backup of app: $app_name"
    echo "=========================================="
    
    local timestamp
    timestamp=$(get_timestamp)
    
    local backup_dir="${staging_dir}/apps/${app_name}"
    
    # Clean up any existing backup files for this app
    if [[ -d "$backup_dir" ]]; then
        echo "Cleaning up existing backup files for '$app_name'..." >&2
        rm -rf "$backup_dir"
    fi
    mkdir -p "$backup_dir"
    
    local backup_files=()
    
    # Check if app has custom backup script first
    local custom_backup_script="${WC_HOME}/apps/${app_name}/backup.sh"
    if [[ -x "$custom_backup_script" ]]; then
        echo "Found custom backup script for '$app_name'. Running..."
        "$custom_backup_script"
        echo "Custom backup completed for '$app_name'."
        return 0
    fi
    
    # Generic backup based on manifest discovery
    local database_deps
    database_deps=$(discover_database_deps "$app_name")
    
    local pvcs
    pvcs=$(discover_app_pvcs "$app_name")
    
    if [[ -z "$database_deps" && -z "$pvcs" ]]; then
        echo "No databases or PVCs found for app '$app_name'. Nothing to backup." >&2
        return 0
    fi
    
    # Backup databases
    for db_type in $database_deps; do
        case "$db_type" in
            postgres)
                if db_files=$(backup_postgres_database "$app_name" "$backup_dir" "$timestamp"); then
                    read -ra db_file_array <<< "$db_files"
                    backup_files+=("${db_file_array[@]}")
                fi
                ;;
            mysql)
                if db_files=$(backup_mysql_database "$app_name" "$backup_dir" "$timestamp"); then
                    backup_files+=("$db_files")
                fi
                ;;
            redis)
                echo "Redis backup not implemented yet. Skipping."
                ;;
        esac
    done
    
    # Backup PVCs
    for pvc in $pvcs; do
        if pvc_file=$(backup_pvc "$app_name" "$pvc" "$backup_dir" "$timestamp"); then
            backup_files+=("$pvc_file")
        fi
    done
    
    # Summary
    if [[ ${#backup_files[@]} -gt 0 ]]; then
        echo "----------------------------------------"
        echo "Backup completed for '$app_name'"
        echo "Files backed up:"
        printf '  - %s\n' "${backup_files[@]}"
        echo "----------------------------------------"
    else
        echo "No files were successfully backed up for '$app_name'." >&2
        return 1
    fi
}

# --- Main Script Logic -------------------------------------------------------
main() {
    
    if [[ $# -eq 0 || "$1" == "--help" || "$1" == "-h" ]]; then
        echo "Usage: $0 <app-name> [app-name2...] | --all"
        echo "       $0 --list    # List available apps"
        exit 1
    fi

    require_k8s
    require_yq
    
    local staging_dir
    staging_dir=$(get_staging_dir)
    mkdir -p "$staging_dir"
    echo "Staging backups at: $staging_dir"

    if [[ "$1" == "--list" ]]; then
        echo "Available apps:"
        find "${WC_HOME}/apps" -maxdepth 1 -type d -not -path "${WC_HOME}/apps" -exec basename {} \; | sort
        exit 0
    fi

    if [[ "$1" == "--all" ]]; then
        echo "Backing up all apps..."
        local apps
        mapfile -t apps < <(find "${WC_HOME}/apps" -maxdepth 1 -type d -not -path "${WC_HOME}/apps" -exec basename {} \;)
        for app in "${apps[@]}"; do
            if ! backup_app "$app" "$staging_dir"; then
                echo "Backup failed for '$app', continuing with next app..." >&2
            fi
        done
    else
        # Backup specific apps
        local failed_apps=()
        for app in "$@"; do
            if ! backup_app "$app" "$staging_dir"; then
                failed_apps+=("$app")
            fi
        done
        
        if [[ ${#failed_apps[@]} -gt 0 ]]; then
            echo "The following app backups failed: ${failed_apps[*]}" >&2
            exit 1
        fi
    fi
    
    echo "All backups completed successfully."
}

main "$@"