#!/bin/bash
# Set strict error handling but allow individual command failures to be handled
# Note: We'll temporarily disable this for file processing that may encounter missing files
set -euo pipefail

# SSH upload script for packwiz projects
# Intelligently uploads all files referenced in the packwiz index

# Default values
RELEASE_VERSION=""
SSH_HOST=""
SSH_USER=""
SSH_PATH=""
SSH_KEY_FILE=""
PACK_FILE="pack.toml"
DEBUG_MODE=false
PARALLEL_UPLOADS=4
BATCH_SIZE=10

# Print usage information
usage() {
    cat << EOF
Usage: $0 [OPTIONS]

Upload complete packwiz project to remote server via SSH/SCP.

Options:
    -v, --version VERSION    Release version (required)
    -h, --host HOST         SSH hostname (required)
    -u, --user USER         SSH username (required)
    -p, --path PATH         Remote path (required)
    -k, --key-file FILE     SSH private key file (required)
    -f, --pack-file FILE    Pack file (default: pack.toml)
    -d, --debug             Enable debug output
    -j, --parallel N        Number of parallel uploads (default: $PARALLEL_UPLOADS)
    -b, --batch-size N      Files per batch upload (default: $BATCH_SIZE)
    --help                  Show this help message

Examples:
    # Upload packwiz project
    $0 --version "1.7.4" --host "example.com" --user "deploy" --path "/var/www/modpacks" --key-file "~/.ssh/id_rsa"
    
    # Upload with more parallel connections
    $0 --version "1.7.4" --host "example.com" --user "deploy" --path "/var/www/modpacks" --key-file "~/.ssh/id_rsa" --parallel 8
    
    # Upload with debug output
    $0 --version "1.7.4" --host "example.com" --user "deploy" --path "/var/www/modpacks" --key-file "~/.ssh/id_rsa" --debug
EOF
}

# Parse command line arguments
parse_args() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            -v|--version)
                RELEASE_VERSION="$2"
                shift 2
                ;;
            -h|--host)
                SSH_HOST="$2"
                shift 2
                ;;
            -u|--user)
                SSH_USER="$2"
                shift 2
                ;;
            -p|--path)
                SSH_PATH="$2"
                shift 2
                ;;
            -k|--key-file)
                SSH_KEY_FILE="$2"
                shift 2
                ;;
            -f|--pack-file)
                PACK_FILE="$2"
                shift 2
                ;;
            -d|--debug)
                DEBUG_MODE=true
                shift
                ;;
            -j|--parallel)
                PARALLEL_UPLOADS="$2"
                shift 2
                ;;
            -b|--batch-size)
                BATCH_SIZE="$2"
                shift 2
                ;;
            --help)
                usage
                exit 0
                ;;
            *)
                echo "Unknown option: $1" >&2
                usage >&2
                exit 1
                ;;
        esac
    done

    # Validate required arguments
    if [[ -z "$RELEASE_VERSION" || -z "$SSH_HOST" || -z "$SSH_USER" || -z "$SSH_PATH" || -z "$SSH_KEY_FILE" ]]; then
        echo "Error: Missing required arguments" >&2
        usage >&2
        exit 1
    fi

    # Validate files exist
    if [[ ! -f "$PACK_FILE" ]]; then
        echo "Error: Pack file not found: $PACK_FILE" >&2
        exit 1
    fi

    if [[ ! -f "$SSH_KEY_FILE" ]]; then
        echo "Error: SSH key file not found: $SSH_KEY_FILE" >&2
        exit 1
    fi
}

# Debug logging function
debug_log() {
    if [[ "$DEBUG_MODE" == "true" ]]; then
        echo "$@" >&2
    fi
}

# Extract pack name from pack.toml
get_pack_name() {
    if [[ -f "$PACK_FILE" ]]; then
        if command -v yq &> /dev/null; then
            # Use yq for robust TOML parsing
            local name=$(yq eval '.name' "$PACK_FILE" 2>/dev/null)
            if [[ "$name" != "null" && -n "$name" ]]; then
                echo "$name" | tr ' ' '-' | tr '[:upper:]' '[:lower:]'
                return
            fi
        fi
        # Fallback to grep/sed
        grep -E '^name\s*=' "$PACK_FILE" | sed 's/.*=\s*"\([^"]*\)".*/\1/' | tr ' ' '-' | tr '[:upper:]' '[:lower:]'
    else
        echo "unknown-pack"
    fi
}

# Get index file path from pack.toml
get_index_file() {
    if [[ -f "$PACK_FILE" ]]; then
        if command -v yq &> /dev/null; then
            # Use yq for robust TOML parsing
            local index_file=$(yq eval '.index.file' "$PACK_FILE" 2>/dev/null)
            if [[ "$index_file" != "null" && -n "$index_file" ]]; then
                echo "$index_file"
                return
            fi
        fi
        # Fallback to grep/sed
        grep -E '^\s*file\s*=' "$PACK_FILE" | head -1 | sed 's/.*=\s*"\([^"]*\)".*/\1/' || echo "index.toml"
    else
        echo "index.toml"
    fi
}

# Parse index.toml to get all referenced files
get_indexed_files() {
    local index_file="$1"
    
    if [[ ! -f "$index_file" ]]; then
        echo "Warning: Index file not found: $index_file" >&2
        return 0
    fi

    debug_log "Parsing index file: $index_file"
    
    # Extract file paths from the index
    # This handles the TOML format where files are listed as [[files]] entries with file = "path"
    if command -v yq &> /dev/null; then
        # Use yq for robust TOML parsing
        debug_log "Using yq for TOML parsing"
        yq eval '.files[].file' "$index_file" 2>/dev/null || true
    elif command -v toml &> /dev/null; then
        # If toml CLI tool is available, use it for robust parsing
        debug_log "Using toml CLI for parsing"
        toml get "$index_file" files --output-format json | jq -r '.[].file' 2>/dev/null || true
    else
        # Fallback: use grep/sed to extract file paths
        # Look for lines like: file = "path/to/file"
        debug_log "Using grep/sed fallback for parsing"
        grep -E '^\s*file\s*=' "$index_file" | sed 's/.*=\s*"\([^"]*\)".*/\1/' || true
    fi
}

# Get all directories that need to be created
get_required_directories() {
    local files=("$@")
    local dirs=()
    
    for file in "${files[@]}"; do
        local dir=$(dirname "$file")
        if [[ "$dir" != "." && "$dir" != "" ]]; then
            dirs+=("$dir")
        fi
    done
    
    # Remove duplicates and sort
        printf '%s
' "${dirs[@]}" | sort -u
}

# Upload a single file with retry logic
upload_single_file() {
    local file="$1"
    local remote_version_dir="$2"
    local file_index="$3"
    local total_files="$4"
    
    if [[ ! -f "$file" ]]; then
        debug_log "File not found, skipping: $file"
        return 1
    fi
    
    # Ensure the directory exists for this file
    local file_dir=$(dirname "$file")
    if [[ "$file_dir" != "." && "$file_dir" != "" ]]; then
        ssh -i "$SSH_KEY_FILE" "${SSH_USER}@${SSH_HOST}" 
            "mkdir -p '${SSH_PATH}/${remote_version_dir}/${file_dir}'" >/dev/null 2>&1 || true
    fi
    
    # Upload the file with connection reuse
    if scp -i "$SSH_KEY_FILE" -o ControlMaster=auto -o ControlPath=/tmp/ssh-%r@%h:%p -o ControlPersist=300 
        "$file" "${SSH_USER}@${SSH_HOST}:${SSH_PATH}/${remote_version_dir}/${file}" >/dev/null 2>&1; then
        debug_log "Successfully uploaded: $file ($file_index/$total_files)"
        return 0
    else
        debug_log "Failed to upload: $file"
        return 1
    fi
}

# Upload files in batches using rsync for efficiency and idempotency
upload_batch_files() {
    local remote_version_dir="$1"
    shift
    local files=("$@")
    
    if [[ ${#files[@]} -eq 0 ]]; then
        return 0
    fi
    
    # Create a temporary file list for rsync, filtering out non-existent files
    local temp_filelist="/tmp/rsync_files_$$.txt"
    local existing_file_count=0
    
    # Validate files exist before adding to rsync list
    for file in "${files[@]}"; do
        if [[ -f "$file" ]]; then
            echo "$file" >> "$temp_filelist"
            ((existing_file_count++))
        else
            debug_log "Skipping non-existent file in batch: $file"
        fi
    done
    
    if [[ $existing_file_count -eq 0 ]]; then
        debug_log "No existing files found for batch upload"
        rm -f "$temp_filelist"
        return 0
    fi
    
    # Use rsync with file list - much more efficient and idempotent
    if rsync -avz --delete --files-from="$temp_filelist" \
        -e "ssh -i '$SSH_KEY_FILE' -o ControlMaster=auto -o ControlPath=/tmp/ssh-%r@%h:%p -o ControlPersist=300" \
        . "${SSH_USER}@${SSH_HOST}:${SSH_PATH}/${remote_version_dir}/" >/dev/null 2>&1; then
        rm -f "$temp_filelist"
        debug_log "Batch upload successful for $existing_file_count files"
        return 0
    fi
    
    # Cleanup on failure
    debug_log "Batch upload failed for $existing_file_count files"
    rm -f "$temp_filelist"
    return 1
}

# Upload all files using rsync (most efficient approach)
upload_files_with_rsync() {
    local remote_version_dir="$1"
    shift
    local files=("$@")
    
    if [[ ${#files[@]} -eq 0 ]]; then
        debug_log "No files provided to rsync"
        return 0
    fi
    
    # Create a temporary file list for rsync, filtering out non-existent files
    local temp_filelist="/tmp/rsync_all_files_$$.txt"
    local existing_file_count=0
    
    # Validate files exist before adding to rsync list
    for file in "${files[@]}"; do
        if [[ -f "$file" ]]; then
            echo "$file" >> "$temp_filelist"
            ((existing_file_count++))
        else
            debug_log "Skipping non-existent file in rsync: $file"
        fi
    done
    
    if [[ $existing_file_count -eq 0 ]]; then
        debug_log "No existing files found for rsync"
        rm -f "$temp_filelist"
        return 0
    fi
    
    debug_log "Rsync will transfer $existing_file_count files"
    
    # Use rsync with file list - transfers only what's needed, handles interruptions gracefully
    # -a: archive mode (preserves permissions, timestamps, etc.)
    # -v: verbose (only in debug mode)
    # -z: compress during transfer
    # --delete: remove files that no longer exist in source
    # --files-from: read file list from file
    # --progress: show progress (only in debug mode)
    local rsync_opts="-az --delete --files-from=$temp_filelist"
    
    if [[ "$DEBUG_MODE" == "true" ]]; then
        rsync_opts+=" -v --progress"
    fi
    
    if rsync $rsync_opts \
        -e "ssh -i '$SSH_KEY_FILE' -o ControlMaster=auto -o ControlPath=/tmp/ssh-%r@%h:%p -o ControlPersist=300" \
        . "${SSH_USER}@${SSH_HOST}:${SSH_PATH}/${remote_version_dir}/" 2>&1; then
        rm -f "$temp_filelist"
        debug_log "Rsync completed successfully for $existing_file_count files"
        return 0
    else
        local rsync_exit_code=$?
        debug_log "Rsync failed with exit code: $rsync_exit_code"
        rm -f "$temp_filelist"
        return 1
    fi
}

# Get all directories that need to be created

# Upload files intelligently
upload_packwiz_project() {
    local pack_name="$1"
    local index_file="$2"
    
    # Create remote directory names
    local remote_version_dir="${pack_name}-${RELEASE_VERSION}"
    local remote_latest_dir="${pack_name}-latest"
    
    echo "Creating remote directory: ${SSH_HOST}:${SSH_PATH}/${remote_version_dir}"
    debug_log "Testing SSH connection..."
    if ! ssh -i "$SSH_KEY_FILE" "${SSH_USER}@${SSH_HOST}" "echo 'SSH connection test successful'" >/dev/null 2>&1; then
        echo "Error: SSH connection failed" >&2
        exit 1
    fi
    debug_log "SSH connection test successful"
    
    debug_log "Creating base remote directory..."
    if ! ssh -i "$SSH_KEY_FILE" "${SSH_USER}@${SSH_HOST}" \
        "mkdir -p '${SSH_PATH}/${remote_version_dir}'" >/dev/null 2>&1; then
        echo "Error: Failed to create base directory" >&2
        exit 1
    fi
    
    # Always upload core packwiz files
    echo "Uploading core packwiz files..."
    debug_log "  Uploading: $PACK_FILE"
    if ! scp -i "$SSH_KEY_FILE" "$PACK_FILE" "${SSH_USER}@${SSH_HOST}:${SSH_PATH}/${remote_version_dir}/" >/dev/null 2>&1; then
        echo "Error: Failed to upload $PACK_FILE" >&2
        exit 1
    fi
    
    if [[ -f "$index_file" ]]; then
        debug_log "  Uploading: $index_file"
        if ! scp -i "$SSH_KEY_FILE" "$index_file" "${SSH_USER}@${SSH_HOST}:${SSH_PATH}/${remote_version_dir}/" >/dev/null 2>&1; then
            echo "Error: Failed to upload $index_file" >&2
            exit 1
        fi
    fi
    
    # Get all files referenced in the index
    echo "Analyzing packwiz index for required files..."
    local indexed_files=()
    
    # Temporarily disable strict error handling for file collection
    set +e
    while IFS= read -r file; do
        if [[ -n "$file" ]]; then
            indexed_files+=("$file")
        fi
    done < <(get_indexed_files "$index_file")
    # Re-enable strict error handling
    set -e
    
    echo "Found ${#indexed_files[@]} files in index"
    
    if [[ ${#indexed_files[@]} -gt 0 ]]; then
        # Get all required directories
        debug_log "Creating required directory structure..."
        local required_dirs=()
        
        # Temporarily disable strict error handling for directory processing
        set +e
        while IFS= read -r dir; do
            if [[ -n "$dir" ]]; then
                required_dirs+=("$dir")
            fi
        done < <(get_required_directories "${indexed_files[@]}")
        set -e
        
        # Create all required directories on remote
        debug_log "Pre-creating common directory structure..."
        set +e  # Temporarily disable strict error handling for directory creation
        for dir in "${required_dirs[@]}"; do
            ssh -i "$SSH_KEY_FILE" "${SSH_USER}@${SSH_HOST}" \
                "mkdir -p '${SSH_PATH}/${remote_version_dir}/${dir}'" >/dev/null 2>&1 || true
        done
        set -e  # Re-enable strict error handling
        
        # Upload all indexed files with optimization
        echo "Uploading ${#indexed_files[@]} indexed files using optimized method..."
        local uploaded_count=0
        local skipped_count=0
        local failed_count=0
        
        # Setup SSH connection multiplexing for efficiency
        ssh -i "$SSH_KEY_FILE" -o ControlMaster=yes -o ControlPath=/tmp/ssh-%r@%h:%p -o ControlPersist=300 \
            "${SSH_USER}@${SSH_HOST}" "echo 'SSH multiplex connection established'" >/dev/null 2>&1 &
        local ssh_multiplex_pid=$!
        sleep 1  # Give the master connection time to establish
        
        # Filter out non-existent files and report them
        local existing_files=()
        local missing_files=()
        
        # Temporarily disable strict error handling for file validation
        set +e
        for file in "${indexed_files[@]}"; do
            if [[ -f "$file" ]]; then
                existing_files+=("$file")
            else
                missing_files+=("$file")
                debug_log "File not found, skipping: $file"
                ((skipped_count++))
            fi
        done
        # Re-enable strict error handling
        set -e
        
        # Report file status
        if [[ ${#missing_files[@]} -gt 0 ]]; then
            echo "Warning: ${#missing_files[@]} files listed in index but not found on disk (will skip)"
            if [[ "$DEBUG_MODE" == "true" ]]; then
                echo "Missing files:"
                printf '  %s\n' "${missing_files[@]}"
            fi
        fi
        
        if [[ ${#existing_files[@]} -eq 0 ]]; then
            echo "No files to upload (all indexed files are missing)"
        else
            echo "Found ${#existing_files[@]} files to upload (${#missing_files[@]} missing)"
        fi
        
        # Temporarily disable strict error handling for file uploads
        set +e
        
        # Try rsync first (most efficient and idempotent)
        if [[ ${#existing_files[@]} -gt 0 ]]; then
            debug_log "Attempting rsync upload..."
            if upload_files_with_rsync "$remote_version_dir" "${existing_files[@]}"; then
                uploaded_count=${#existing_files[@]}
                echo "Rsync upload successful: $uploaded_count files"
            else
                debug_log "Rsync upload failed, falling back to batch uploads..."
                
                # Fall back to batch uploads using rsync
                local batch_files=()
                local current_batch_size=0
                local processed_files=0
                
                for file in "${existing_files[@]}"; do
                    batch_files+=("$file")
                    ((current_batch_size++))
                    
                    # Process batch when it reaches the batch size or at the end
                    if [[ $current_batch_size -ge $BATCH_SIZE ]] || [[ $((processed_files + current_batch_size)) -eq ${#existing_files[@]} ]]; then
                        debug_log "Processing batch of $current_batch_size files..."
                        
                        if upload_batch_files "$remote_version_dir" "${batch_files[@]}"; then
                            uploaded_count=$((uploaded_count + current_batch_size))
                            debug_log "Batch upload successful: $current_batch_size files"
                        else
                            debug_log "Batch upload failed, falling back to individual uploads..."
                            
                            # Fall back to parallel individual uploads for this batch
                            local pids=()
                            local batch_index=0
                            
                            for batch_file in "${batch_files[@]}"; do
                                ((batch_index++))
                                local total_index=$((processed_files + batch_index))
                                
                                # Progress update
                                printf "\r  Progress: %d/%d (%d%%) - Uploading: %s" \
                                    "$total_index" "${#existing_files[@]}" \
                                    "$((total_index * 100 / ${#existing_files[@]}))" \
                                    "$batch_file"
                                
                                # Start upload in background if we have available slots
                                while [[ ${#pids[@]} -ge $PARALLEL_UPLOADS ]]; do
                                    # Wait for any process to complete
                                    local new_pids=()
                                    for pid in "${pids[@]}"; do
                                        if kill -0 "$pid" 2>/dev/null; then
                                            new_pids+=("$pid")
                                        else
                                            wait "$pid"
                                            if [[ $? -eq 0 ]]; then
                                                ((uploaded_count++))
                                            else
                                                ((failed_count++))
                                            fi
                                        fi
                                    done
                                    pids=("${new_pids[@]}")
                                    sleep 0.1
                                done
                                
                                # Start the upload in background
                                upload_single_file "$batch_file" "$remote_version_dir" "$total_index" "${#existing_files[@]}" &
                                pids+=($!)
                            done
                            
                            # Wait for remaining uploads to complete
                            for pid in "${pids[@]}"; do
                                wait "$pid"
                                if [[ $? -eq 0 ]]; then
                                    ((uploaded_count++))
                                else
                                    ((failed_count++))
                                fi
                            done
                        fi
                        
                        processed_files=$((processed_files + current_batch_size))
                        batch_files=()
                        current_batch_size=0
                        
                        # Update progress for batch completion
                        printf "\r  Progress: %d/%d (%d%%) - Batch completed" \
                            "$processed_files" "${#existing_files[@]}" \
                            "$((processed_files * 100 / ${#existing_files[@]}))"
                    fi
                done
            fi
        else
            echo "No files to upload - all indexed files are missing"
        fi
        
        # Close SSH multiplex connection
        ssh -i "$SSH_KEY_FILE" -o ControlPath=/tmp/ssh-%r@%h:%p -O exit "${SSH_USER}@${SSH_HOST}" >/dev/null 2>&1 || true
        
        # Re-enable strict error handling
        set -e
        
        # Clear the progress line and show final summary
        echo ""
        echo "Upload summary: $uploaded_count uploaded, $skipped_count skipped, $failed_count failed"
        
        if [[ $failed_count -gt 0 ]]; then
            echo "Warning: $failed_count files failed to upload" >&2
            # Don't exit with error code for failed individual files
            # return 0
        fi
    else
        echo "No indexed files found, uploading only core files"
    fi
    
    # Also upload any additional common directories that exist but might not be in index
    # This catches things like defaultconfigs, scripts, etc. that might not be tracked
    local additional_dirs=("config" "kubejs" "resourcepacks" "shaderpacks" "defaultconfigs" "scripts")
    debug_log "Checking for additional directories..."
    
    for dir in "${additional_dirs[@]}"; do
        if [[ -d "$dir" ]]; then
            # Check if this directory is already covered by indexed files
            local already_covered=false
            for indexed_file in "${indexed_files[@]}"; do
                if [[ "$indexed_file" == "$dir"/* ]]; then
                    already_covered=true
                    break
                fi
            done
            
            if [[ "$already_covered" == "false" ]]; then
                debug_log "  Uploading additional directory: $dir"
                rsync -az --delete -e "ssh -i '$SSH_KEY_FILE' -o ControlMaster=auto -o ControlPath=/tmp/ssh-%r@%h:%p -o ControlPersist=300" \
                    "$dir/" "${SSH_USER}@${SSH_HOST}:${SSH_PATH}/${remote_version_dir}/$dir/" >/dev/null 2>&1
            else
                debug_log "  Directory $dir already covered by indexed files"
            fi
        fi
    done
    
    echo "Upload completed to ${SSH_HOST}:${SSH_PATH}/${remote_version_dir}"
    
    # Create/update symlink to latest version
    debug_log "Updating latest symlink..."
    ssh -i "$SSH_KEY_FILE" "${SSH_USER}@${SSH_HOST}" \
        "cd '${SSH_PATH}' && rm -f '${remote_latest_dir}' && ln -sf '${remote_version_dir}' '${remote_latest_dir}'" >/dev/null 2>&1
    
    debug_log "Created symlink: ${SSH_HOST}:${SSH_PATH}/${remote_latest_dir} -> ${remote_version_dir}"
}

# Main function
main() {
    parse_args "$@"
    
    echo "SSH Upload - Packwiz Project"
    echo "  Version: $RELEASE_VERSION"
    echo "  Host: $SSH_HOST"
    echo "  User: $SSH_USER"
    echo "  Path: $SSH_PATH"
    echo "  Pack File: $PACK_FILE"
    echo "  Parallel uploads: $PARALLEL_UPLOADS"
    echo "  Batch size: $BATCH_SIZE"
    if [[ "$DEBUG_MODE" == "true" ]]; then
        echo "  Debug mode: enabled"
    fi
    echo ""
    
    # Get pack name and index file
    local pack_name
    pack_name=$(get_pack_name)
    echo "Pack name: $pack_name"
    
    local index_file
    index_file=$(get_index_file)
    echo "Index file: $index_file"
    echo ""
    
    # Perform the upload
    upload_packwiz_project "$pack_name" "$index_file"
    
    echo ""
    echo "SSH upload completed successfully!"
    echo "Latest version available at: ${SSH_HOST}:${SSH_PATH}/${pack_name}-latest/"
}

# Run main function with all arguments
main "$@"
