#!/bin/bash

# Nextcloud Bulk File Rename Script
# Renames files containing a search string and updates the database to preserve all metadata

set +e  # Explicit error handling

# Configuration
NC_DATA_DIR="/federated/apps/nextcloud/data/var/www/html/data"
NC_CONTAINER="nextcloud"
PG_CONTAINER="postgresql"
PG_USER="nextcloud"
PG_DB="nextcloud"
DRY_RUN=0
INCLUDE_GROUPFOLDERS=0
INCLUDE_TRASH=0
SEARCH_STRING=""
REPLACE_STRING=""
LOG_FILE="/tmp/nextcloud_bulk_rename_$(date +%Y%m%d_%H%M%S).log"

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC_COLOR='\033[0m'

log() { echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC_COLOR} $1" | tee -a "$LOG_FILE"; }
error() { echo -e "${RED}[ERROR]${NC_COLOR} $1" | tee -a "$LOG_FILE"; }
warn() { echo -e "${YELLOW}[WARN]${NC_COLOR} $1" | tee -a "$LOG_FILE"; }
success() { echo -e "${GREEN}[SUCCESS]${NC_COLOR} $1" | tee -a "$LOG_FILE"; }

show_help() {
    cat <<EOF
Nextcloud Bulk File Rename - Database-safe rename preserving all metadata

USAGE:
    $0 --search <string> --replace <string> [OPTIONS]

REQUIRED:
    --search <string>          String to search for in filenames
    --replace <string>         String to replace with (use "" for deletion)

OPTIONS:
    --dry-run                  Preview changes without making modifications
    --include-groupfolders     Also process __groupfolders (shared team folders)
    --include-trash            Also process files_trashbin (deleted files)
    --data-dir <path>          Custom data directory
    --help                     Show this help message

WHAT GETS PROCESSED:
    ✓ files/                   User files (always)
    ✗ __groupfolders/          Shared folders (use --include-groupfolders)
    ✗ files_trashbin/          Deleted files (use --include-trash)
    ✗ appdata_*                Internal cache (always skipped)
    ✗ files_versions/          File versions (always skipped)

EXAMPLES:
    # Preview: Change @HASH@ back to # 
    $0 --search "@HASH@" --replace "#" --dry-run

    # Execute: Change # to underscore (user files only)
    $0 --search "#" --replace "_"

    # Execute: Change # to @HASH@ including group folders
    $0 --search "#" --replace "@HASH@" --include-groupfolders

    # Remove a string entirely
    $0 --search " (copy)" --replace ""

SAFETY:
    - Always creates detailed log file
    - Dry-run mode available for testing
    - Preserves ALL metadata (shares, permissions, tags, comments, versions)
    - Uses database transactions for safety
    - Enable maintenance mode recommended

MAINTENANCE MODE:
    docker exec -u 33 nextcloud php occ maintenance:mode --on
    # Run script
    docker exec -u 33 nextcloud php occ maintenance:mode --off

EOF
    exit 0
}

# Parse arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        --search)
            SEARCH_STRING="$2"
            shift 2
            ;;
        --replace)
            REPLACE_STRING="$2"
            shift 2
            ;;
        --dry-run)
            DRY_RUN=1
            shift
            ;;
        --include-groupfolders)
            INCLUDE_GROUPFOLDERS=1
            shift
            ;;
        --include-trash)
            INCLUDE_TRASH=1
            shift
            ;;
        --data-dir)
            NC_DATA_DIR="$2"
            shift 2
            ;;
        --help)
            show_help
            ;;
        *)
            error "Unknown option: $1"
            echo "Use --help for usage information"
            exit 1
            ;;
    esac
done

# Validate required parameters
if [ -z "$SEARCH_STRING" ]; then
    error "Missing required parameter: --search"
    echo "Use --help for usage information"
    exit 1
fi

# Note: REPLACE_STRING can be empty (to delete the search string)

# Display configuration
echo
log "================================"
log "Nextcloud Bulk File Rename"
log "================================"
log "Search for:  '$SEARCH_STRING'"
log "Replace with: '$REPLACE_STRING'"
log "Data directory: $NC_DATA_DIR"
log "Log file: $LOG_FILE"
[ $DRY_RUN -eq 1 ] && warn "DRY RUN MODE - No changes will be made"
echo

log "Processing configuration:"
log "  ✓ files/ (user files) - ALWAYS"
[ $INCLUDE_GROUPFOLDERS -eq 1 ] && log "  ✓ __groupfolders/ - ENABLED" || log "  ✗ __groupfolders/ - SKIPPED"
[ $INCLUDE_TRASH -eq 1 ] && log "  ✓ files_trashbin/ - ENABLED" || log "  ✗ files_trashbin/ - SKIPPED"
log "  ✗ appdata_* (cache) - ALWAYS SKIPPED"
log "  ✗ files_versions/ - ALWAYS SKIPPED"
echo

# Check maintenance mode
NC_MAINTENANCE=$(docker exec -u 33 "$NC_CONTAINER" php occ config:system:get maintenance 2>/dev/null || echo "false")
if [ "$NC_MAINTENANCE" != "true" ] && [ $DRY_RUN -eq 0 ]; then
    warn "Nextcloud is not in maintenance mode."
    warn "Recommended: docker exec -u 33 $NC_CONTAINER php occ maintenance:mode --on"
    read -p "Continue anyway? (y/n) " -n 1 -r
    echo
    [[ ! $REPLY =~ ^[Yy]$ ]] && exit 1
fi

# Build WHERE clause
WHERE_CONDITIONS="path LIKE 'files/%'"
[ $INCLUDE_GROUPFOLDERS -eq 1 ] && WHERE_CONDITIONS="$WHERE_CONDITIONS OR path LIKE '__groupfolders/%'"
[ $INCLUDE_TRASH -eq 1 ] && WHERE_CONDITIONS="$WHERE_CONDITIONS OR path LIKE 'files_trashbin/%'"
WHERE_CLAUSE="path LIKE '%${SEARCH_STRING}%' AND ($WHERE_CONDITIONS)"

# Escape single quotes for SQL
escape_sql() { echo "$1" | sed "s/'/''/g"; }

# Calculate MD5 hash
calculate_hash() { echo -n "$1" | md5sum | awk '{print $1}'; }

# Update database for one file
update_database() {
    local old_path="$1"
    local new_path="$2"
    
    local old_path_esc=$(escape_sql "$old_path")
    local new_path_esc=$(escape_sql "$new_path")
    local new_name=$(basename "$new_path")
    local new_name_esc=$(escape_sql "$new_name")
    local new_hash=$(calculate_hash "$new_path")
    
    if [ $DRY_RUN -eq 1 ]; then
        log "    [DRY RUN] Would update database"
        return 0
    fi
    
    # Use -c flag with inline SQL (no heredoc to avoid stdin issues)
    local sql="BEGIN; \
        UPDATE oc_filecache SET path='$new_path_esc', path_hash='$new_hash', name='$new_name_esc', etag=md5(random()::text || now()::text) WHERE path='$old_path_esc'; \
        UPDATE oc_filecache SET etag=md5(random()::text || now()::text) WHERE fileid IN (SELECT parent FROM oc_filecache WHERE path='$new_path_esc'); \
        UPDATE oc_filecache SET path=REPLACE(path, '$old_path_esc/', '$new_path_esc/'), path_hash=md5(REPLACE(path, '$old_path_esc/', '$new_path_esc/')), etag=md5(random()::text || now()::text) WHERE path LIKE '$old_path_esc/%'; \
        COMMIT;"
    
    local result=$(docker exec "$PG_CONTAINER" psql -U "$PG_USER" -d "$PG_DB" -t -A -c "$sql" 2>&1)
    
    if [ $? -eq 0 ]; then
        return 0
    else
        error "    SQL error: $result"
        return 1
    fi
}

# Rename physical file
rename_file() {
    local old_file="$1"
    local new_file="$2"
    
    if [ ! -e "$old_file" ]; then
        warn "    File not found on disk (may be in trash or deleted)"
        return 0
    fi
    
    if [ $DRY_RUN -eq 1 ]; then
        log "    [DRY RUN] Would rename: $(basename "$old_file") -> $(basename "$new_file")"
        return 0
    fi
    
    mkdir -p "$(dirname "$new_file")"
    
    if mv "$old_file" "$new_file" 2>/dev/null; then
        return 0
    else
        error "    Failed to rename file on filesystem"
        return 1
    fi
}

# Main processing
log "Querying database for files containing '$SEARCH_STRING'..."

QUERY="SELECT fileid, storage, path, parent, name FROM oc_filecache WHERE $WHERE_CLAUSE ORDER BY LENGTH(path) DESC"

# Get results into array (avoids while loop stdin issues)
mapfile -t RESULTS < <(docker exec "$PG_CONTAINER" psql -U "$PG_USER" -d "$PG_DB" -t -A -F'|' -c "$QUERY" 2>&1)

TOTAL=${#RESULTS[@]}
log "Found $TOTAL entries to process"
echo

if [ $TOTAL -eq 0 ]; then
    success "No files containing '$SEARCH_STRING' found!"
    exit 0
fi

# Confirm before proceeding (unless dry-run)
if [ $DRY_RUN -eq 0 ]; then
    warn "About to rename $TOTAL files/directories"
    read -p "Proceed? (y/n) " -n 1 -r
    echo
    [[ ! $REPLY =~ ^[Yy]$ ]] && exit 1
    echo
fi

PROCESSED=0
FAILED=0
SKIPPED=0

# Process each entry using array index (no while loop - avoids stdin issues!)
for (( i=0; i<$TOTAL; i++ )); do
    LINE="${RESULTS[$i]}"
    
    # Skip empty lines
    [ -z "$LINE" ] && continue
    
    # Parse fields
    IFS='|' read -r fileid storage db_path parent name <<< "$LINE"
    
    # Skip if no path
    [ -z "$db_path" ] && continue
    
    # Skip if doesn't contain search string
    [[ "$db_path" != *"$SEARCH_STRING"* ]] && { ((SKIPPED++)); continue; }
    
    NUM=$((i + 1))
    log "[$NUM/$TOTAL] $db_path"
    
    # Calculate new path
    new_db_path="${db_path//$SEARCH_STRING/$REPLACE_STRING}"
    
    log "  -> $new_db_path"
    
    # Update database
    if update_database "$db_path" "$new_db_path"; then
        success "    Database updated"
    else
        error "    Database update failed"
        ((FAILED++))
        continue
    fi
    
    # Find and rename physical file
    if [[ "$db_path" =~ ^files/ ]]; then
        # User files
        rel_path="${db_path#files/}"
        file_path=$(find "$NC_DATA_DIR" -type d -name "files" 2>/dev/null | while read dir; do
            [ -e "$dir/$rel_path" ] && echo "$dir/$rel_path" && break
        done)
    elif [[ "$db_path" =~ ^__groupfolders/ ]]; then
        # Group folders
        file_path="$NC_DATA_DIR/$db_path"
    else
        file_path="$NC_DATA_DIR/$db_path"
    fi
    
    if [ -n "$file_path" ]; then
        new_file_path="${file_path//$SEARCH_STRING/$REPLACE_STRING}"
        if rename_file "$file_path" "$new_file_path"; then
            success "    File renamed"
        else
            error "    File rename failed"
            ((FAILED++))
        fi
    fi
    
    ((PROCESSED++))
    echo
done

# Summary
echo
log "================================"
log "SUMMARY"
log "================================"
log "Total entries found: $TOTAL"
log "Successfully processed: $PROCESSED"
log "Failed: $FAILED"
log "Skipped by filter: $SKIPPED"
log "================================"
echo

if [ $FAILED -gt 0 ]; then
    error "Some operations failed. Review log: $LOG_FILE"
    exit 1
fi

if [ $DRY_RUN -eq 0 ] && [ $PROCESSED -gt 0 ]; then
    log "Running Nextcloud file scan..."
    docker exec -u 33 "$NC_CONTAINER" php occ files:scan --all 2>&1 | tee -a "$LOG_FILE" >/dev/null
    
    if [ $INCLUDE_GROUPFOLDERS -eq 1 ]; then
        log "Scanning group folders..."
        docker exec -u 33 "$NC_CONTAINER" php occ groupfolders:scan --all 2>&1 | tee -a "$LOG_FILE" >/dev/null
    fi
    
    echo
    success "All operations completed successfully!"
    echo
    log "If everything works, disable maintenance mode:"
    log "     docker exec -u 33 $NC_CONTAINER php occ maintenance:mode --off"
else
    warn "Dry run completed. Review changes above."
    echo
    log "To apply changes, run without --dry-run:"
    if [ $INCLUDE_GROUPFOLDERS -eq 1 ]; then
        log "  $0 --search '$SEARCH_STRING' --replace '$REPLACE_STRING' --include-groupfolders"
    else
        log "  $0 --search '$SEARCH_STRING' --replace '$REPLACE_STRING'"
    fi
fi

echo
log "Log file saved to: $LOG_FILE"
exit 0
