#!/bin/bash

################################################################################
# Advanced Plex Database Recovery Script
################################################################################
#
# Author: Peter Wood <peter@peterwood.dev>
# Description: Advanced database recovery script with multiple repair strategies
#              for corrupted Plex databases. Implements progressive recovery
#              techniques from gentle repairs to aggressive reconstruction
#              methods, with comprehensive logging and rollback capabilities.
#
# Features:
#   - Progressive recovery strategy (gentle to aggressive)
#   - Multiple repair techniques (VACUUM, dump/restore, rebuild)
#   - Automatic backup before any recovery attempts
#   - Database integrity verification at each step
#   - Rollback capability if recovery fails
#   - Dry-run mode for safe testing
#   - Comprehensive logging and reporting
#
# Related Scripts:
#   - backup-plex.sh: Creates backups for recovery scenarios
#   - icu-aware-recovery.sh: ICU-specific recovery methods
#   - nuclear-plex-recovery.sh: Last-resort complete replacement
#   - validate-plex-recovery.sh: Validates recovery results
#   - restore-plex.sh: Standard restoration from backups
#   - plex.sh: General Plex service management
#
# Usage:
#   ./recover-plex-database.sh                 # Interactive recovery
#   ./recover-plex-database.sh --auto          # Automated recovery
#   ./recover-plex-database.sh --dry-run       # Show recovery plan
#   ./recover-plex-database.sh --gentle        # Gentle repair only
#   ./recover-plex-database.sh --aggressive    # Aggressive repair methods
#
# Dependencies:
#   - sqlite3 or Plex SQLite binary
#   - systemctl (for service management)
#   - Sufficient disk space for backups and temp files
#
# Exit Codes:
#   0 - Recovery successful
#   1 - General error
#   2 - Database corruption beyond repair
#   3 - Service management failure
#   4 - Insufficient disk space
#   5 - Recovery partially successful (manual intervention needed)
#
################################################################################

# Advanced Plex Database Recovery Script
# Usage: ./recover-plex-database.sh [--auto] [--dry-run]

set -e

# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Configuration
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
PLEX_DB_DIR="/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-in Support/Databases"
MAIN_DB="com.plexapp.plugins.library.db"
PLEX_SQLITE="/usr/lib/plexmediaserver/Plex SQLite"
BACKUP_SUFFIX="recovery-$(date +%Y%m%d_%H%M%S)"
RECOVERY_LOG="$SCRIPT_DIR/logs/database-recovery-$(date +%Y%m%d_%H%M%S).log"

# Script options
AUTO_MODE=false
DRY_RUN=false

# Ensure logs directory exists
mkdir -p "$SCRIPT_DIR/logs"

# Logging function
log_message() {
    local message
    message="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
    echo -e "$message"
    echo "$message" >> "$RECOVERY_LOG"
}

log_success() {
    log_message "${GREEN}SUCCESS: $1${NC}"
}

log_error() {
    log_message "${RED}ERROR: $1${NC}"
}

log_warning() {
    log_message "${YELLOW}WARNING: $1${NC}"
}

log_info() {
    log_message "${BLUE}INFO: $1${NC}"
}

# Parse command line arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        --auto)
            AUTO_MODE=true
            shift
            ;;
        --dry-run)
            DRY_RUN=true
            shift
            ;;
        -h|--help)
            echo "Usage: $0 [--auto] [--dry-run] [--help]"
            echo ""
            echo "Options:"
            echo "  --auto      Automatically attempt all recovery methods without prompts"
            echo "  --dry-run   Show what would be done without making changes"
            echo "  --help      Show this help message"
            echo ""
            echo "Recovery Methods (in order):"
            echo "  1. SQLite .recover command (modern SQLite recovery)"
            echo "  2. Partial table extraction with LIMIT"
            echo "  3. Emergency data extraction"
            echo "  4. Backup restoration from most recent good backup"
            echo ""
            exit 0
            ;;
        *)
            log_error "Unknown option: $1"
            exit 1
            ;;
    esac
done

# Check dependencies
check_dependencies() {
    log_info "Checking dependencies..."

    if [ ! -f "$PLEX_SQLITE" ]; then
        log_error "Plex SQLite binary not found at: $PLEX_SQLITE"
        return 1
    fi

    if ! command -v sqlite3 >/dev/null 2>&1; then
        log_error "Standard sqlite3 command not found"
        return 1
    fi

    # Make Plex SQLite executable
    sudo chmod +x "$PLEX_SQLITE" 2>/dev/null || true

    log_success "Dependencies check passed"
    return 0
}

# Stop Plex service safely
stop_plex_service() {
    log_info "Stopping Plex Media Server..."

    if [ "$DRY_RUN" = true ]; then
        log_info "DRY RUN: Would stop Plex service"
        return 0
    fi

    if sudo systemctl is-active --quiet plexmediaserver; then
        sudo systemctl stop plexmediaserver

        # Wait for service to fully stop
        local timeout=30
        while sudo systemctl is-active --quiet plexmediaserver && [ $timeout -gt 0 ]; do
            sleep 1
            timeout=$((timeout - 1))
        done

        if sudo systemctl is-active --quiet plexmediaserver; then
            log_error "Failed to stop Plex service within timeout"
            return 1
        fi

        log_success "Plex service stopped successfully"
    else
        log_info "Plex service was already stopped"
    fi

    return 0
}

# Start Plex service
start_plex_service() {
    log_info "Starting Plex Media Server..."

    if [ "$DRY_RUN" = true ]; then
        log_info "DRY RUN: Would start Plex service"
        return 0
    fi

    sudo systemctl start plexmediaserver

    # Wait for service to start
    local timeout=30
    while ! sudo systemctl is-active --quiet plexmediaserver && [ $timeout -gt 0 ]; do
        sleep 1
        timeout=$((timeout - 1))
    done

    if sudo systemctl is-active --quiet plexmediaserver; then
        log_success "Plex service started successfully"
    else
        log_warning "Plex service may not have started properly"
    fi
}

# Check database integrity
check_database_integrity() {
    local db_file="$1"
    local db_name
    db_name=$(basename "$db_file")

    log_info "Checking integrity of $db_name..."

    if [ ! -f "$db_file" ]; then
        log_error "Database file not found: $db_file"
        return 1
    fi

    local integrity_result
    integrity_result=$(sudo "$PLEX_SQLITE" "$db_file" "PRAGMA integrity_check;" 2>&1)
    local check_exit_code=$?

    if [ $check_exit_code -ne 0 ]; then
        log_error "Failed to run integrity check on $db_name"
        return 1
    fi

    if echo "$integrity_result" | grep -q "^ok$"; then
        log_success "Database integrity check passed: $db_name"
        return 0
    else
        log_warning "Database integrity issues detected in $db_name:"
        echo "$integrity_result" | while IFS= read -r line; do
            log_warning "  $line"
        done
        return 1
    fi
}

# Recovery Method 1: SQLite .recover command
recovery_method_sqlite_recover() {
    local db_file="$1"
    local db_name
    db_name=$(basename "$db_file")
    local recovered_sql="${db_file}.recovered.sql"
    local new_db="${db_file}.recovered"

    log_info "Recovery Method 1: SQLite .recover command for $db_name"

    if [ "$DRY_RUN" = true ]; then
        log_info "DRY RUN: Would attempt SQLite .recover method"
        return 0
    fi

    # Check if .recover is available (SQLite 3.37.0+)
    if ! echo ".help" | sqlite3 2>/dev/null | grep -q "\.recover"; then
        log_warning "SQLite .recover command not available in this version"
        return 1
    fi

    log_info "Attempting SQLite .recover method..."

    # Use standard sqlite3 for .recover as it's more reliable
    if sqlite3 "$db_file" ".recover" > "$recovered_sql" 2>/dev/null; then
        log_success "Recovery SQL generated successfully"

        # Create new database from recovered data
        if [ -f "$recovered_sql" ] && [ -s "$recovered_sql" ]; then
            if sqlite3 "$new_db" < "$recovered_sql" 2>/dev/null; then
                log_success "New database created from recovered data"

                # Verify new database integrity
                if sqlite3 "$new_db" "PRAGMA integrity_check;" | grep -q "ok"; then
                    log_success "Recovered database integrity verified"

                    # Replace original with recovered database
                    if sudo mv "$db_file" "${db_file}.corrupted" && sudo mv "$new_db" "$db_file"; then
                        sudo chown plex:plex "$db_file"
                        sudo chmod 644 "$db_file"
                        log_success "Database successfully recovered using .recover method"

                        # Clean up
                        rm -f "$recovered_sql"

                        return 0
                    else
                        log_error "Failed to replace original database"
                    fi
                else
                    log_error "Recovered database failed integrity check"
                fi
            else
                log_error "Failed to create database from recovered SQL"
            fi
        else
            log_error "Recovery SQL file is empty or not generated"
        fi
    else
        log_error "SQLite .recover command failed"
    fi

    # Clean up on failure
    rm -f "$recovered_sql" "$new_db"
    return 1
}

# Recovery Method 2: Partial table extraction
recovery_method_partial_extraction() {
    local db_file="$1"
    local db_name
    db_name=$(basename "$db_file")
    local partial_sql="${db_file}.partial.sql"
    local new_db="${db_file}.partial"

    log_info "Recovery Method 2: Partial table extraction for $db_name"

    if [ "$DRY_RUN" = true ]; then
        log_info "DRY RUN: Would attempt partial extraction method"
        return 0
    fi

    log_info "Extracting schema and partial data..."

    # Start the SQL file with schema
    {
        echo "-- Partial recovery of $db_name"
        echo "-- Generated on $(date)"
        echo ""
    } > "$partial_sql"

    # Extract schema
    if sudo "$PLEX_SQLITE" "$db_file" ".schema" | sudo tee -a "$partial_sql" >/dev/null 2>&1; then
        log_success "Schema extracted successfully"
    else
        log_warning "Schema extraction failed, trying alternative method"
        # Try with standard sqlite3
        if sqlite3 "$db_file" ".schema" >> "$partial_sql" 2>/dev/null; then
            log_success "Schema extracted with standard sqlite3"
        else
            log_error "Schema extraction failed completely"
            rm -f "$partial_sql"
            return 1
        fi
    fi

    # Critical tables to extract (in order of importance)
    local critical_tables=(
        "accounts"
        "library_sections"
        "directories"
        "metadata_items"
        "media_items"
        "media_parts"
        "media_streams"
        "taggings"
        "tags"
    )

    log_info "Attempting to extract critical tables..."

    for table in "${critical_tables[@]}"; do
        log_info "Extracting table: $table"

        # Try to extract with LIMIT to avoid hitting corrupted data
        local extract_success=false
        local limit=10000

        while [ "$limit" -le 100000 ] && [ "$extract_success" = false ]; do
            if sudo "$PLEX_SQLITE" "$db_file" "SELECT COUNT(*) FROM $table;" >/dev/null 2>&1; then
                # Table exists and is readable
                {
                    echo ""
                    echo "-- Data for table $table (limited to $limit rows)"
                    echo "DELETE FROM $table;"
                } >> "$partial_sql"

                if sudo "$PLEX_SQLITE" "$db_file" ".mode insert $table" >>/dev/null 2>&1 && \
                   sudo "$PLEX_SQLITE" "$db_file" "SELECT * FROM $table LIMIT $limit;" | sudo tee -a "$partial_sql" >/dev/null 2>&1; then
                    local row_count
                    row_count=$(tail -n +3 "$partial_sql" | grep -c "INSERT INTO $table")
                    log_success "Extracted $row_count rows from $table"
                    extract_success=true
                else
                    log_warning "Failed to extract $table with limit $limit, trying smaller limit"
                    limit=$((limit / 2))
                fi
            else
                log_warning "Table $table is not accessible or doesn't exist"
                break
            fi
        done

        if [ "$extract_success" = false ]; then
            log_warning "Could not extract any data from table $table"
        fi
    done

    # Create new database from partial data
    if [ -f "$partial_sql" ] && [ -s "$partial_sql" ]; then
        log_info "Creating database from partial extraction..."

        if sqlite3 "$new_db" < "$partial_sql" 2>/dev/null; then
            log_success "Partial database created successfully"

            # Verify basic functionality
            if sqlite3 "$new_db" "PRAGMA integrity_check;" | grep -q "ok"; then
                log_success "Partial database integrity verified"

                # Replace original with partial database
                if sudo mv "$db_file" "${db_file}.corrupted" && sudo mv "$new_db" "$db_file"; then
                    sudo chown plex:plex "$db_file"
                    sudo chmod 644 "$db_file"
                    log_success "Database partially recovered - some data may be lost"
                    log_warning "Please verify your Plex library after recovery"

                    # Clean up
                    rm -f "$partial_sql"

                    return 0
                else
                    log_error "Failed to replace original database"
                fi
            else
                log_error "Partial database failed integrity check"
            fi
        else
            log_error "Failed to create database from partial extraction"
        fi
    else
        log_error "Partial extraction SQL file is empty"
    fi

    # Clean up on failure
    rm -f "$partial_sql" "$new_db"
    return 1
}

# Recovery Method 3: Emergency data extraction
recovery_method_emergency_extraction() {
    local db_file="$1"
    local db_name
    db_name=$(basename "$db_file")

    log_info "Recovery Method 3: Emergency data extraction for $db_name"

    if [ "$DRY_RUN" = true ]; then
        log_info "DRY RUN: Would attempt emergency extraction method"
        return 0
    fi

    log_warning "This method will create a minimal database with basic library structure"
    log_warning "You will likely need to re-scan your media libraries"

    if [ "$AUTO_MODE" = false ]; then
        read -p "Continue with emergency extraction? This will lose most metadata [y/N]: " -n 1 -r
        echo
        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
            log_info "Emergency extraction cancelled by user"
            return 1
        fi
    fi

    local emergency_db="${db_file}.emergency"

    # Create a minimal database with essential tables
    log_info "Creating minimal emergency database..."

    cat > "/tmp/emergency_schema.sql" << 'EOF'
-- Emergency Plex database schema (minimal)
CREATE TABLE accounts (
    id INTEGER PRIMARY KEY,
    name TEXT,
    hashed_password TEXT,
    salt TEXT,
    created_at DATETIME,
    updated_at DATETIME
);

CREATE TABLE library_sections (
    id INTEGER PRIMARY KEY,
    name TEXT,
    section_type INTEGER,
    agent TEXT,
    scanner TEXT,
    language TEXT,
    created_at DATETIME,
    updated_at DATETIME
);

CREATE TABLE directories (
    id INTEGER PRIMARY KEY,
    library_section_id INTEGER,
    path TEXT,
    created_at DATETIME,
    updated_at DATETIME
);

-- Insert default admin account
INSERT INTO accounts (id, name, created_at, updated_at)
VALUES (1, 'plex', datetime('now'), datetime('now'));
EOF

    if sqlite3 "$emergency_db" < "/tmp/emergency_schema.sql" 2>/dev/null; then
        log_success "Emergency database created"

        # Replace original with emergency database
        if sudo mv "$db_file" "${db_file}.corrupted" && sudo mv "$emergency_db" "$db_file"; then
            sudo chown plex:plex "$db_file"
            sudo chmod 644 "$db_file"
            log_success "Emergency database installed"
            log_warning "You will need to re-add library sections and re-scan media"

            # Clean up
            rm -f "/tmp/emergency_schema.sql"

            return 0
        else
            log_error "Failed to install emergency database"
        fi
    else
        log_error "Failed to create emergency database"
    fi

    # Clean up on failure
    rm -f "/tmp/emergency_schema.sql" "$emergency_db"
    return 1
}

# Recovery Method 4: Restore from backup
recovery_method_backup_restore() {
    local db_file="$1"
    local backup_dir="/mnt/share/media/backups/plex"

    log_info "Recovery Method 4: Restore from most recent backup"

    if [ "$DRY_RUN" = true ]; then
        log_info "DRY RUN: Would attempt backup restoration"
        return 0
    fi

    # Find most recent backup
    local latest_backup
    latest_backup=$(find "$backup_dir" -maxdepth 1 -name "plex-backup-*.tar.gz" -type f 2>/dev/null | sort -r | head -1)

    if [ -z "$latest_backup" ]; then
        log_error "No backup files found in $backup_dir"
        return 1
    fi

    log_info "Found latest backup: $(basename "$latest_backup")"

    if [ "$AUTO_MODE" = false ]; then
        read -p "Restore from backup $(basename "$latest_backup")? [y/N]: " -n 1 -r
        echo
        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
            log_info "Backup restoration cancelled by user"
            return 1
        fi
    fi

    # Extract and restore database from backup
    local temp_extract="/tmp/plex-recovery-extract-$(date +%Y%m%d_%H%M%S)"
    mkdir -p "$temp_extract"

    log_info "Extracting backup..."
    if tar -xzf "$latest_backup" -C "$temp_extract" 2>/dev/null; then
        local backup_db_file="$temp_extract/$(basename "$db_file")"

        if [ -f "$backup_db_file" ]; then
            # Verify backup database integrity
            if sqlite3 "$backup_db_file" "PRAGMA integrity_check;" | grep -q "ok"; then
                log_success "Backup database integrity verified"

                # Replace corrupted database with backup
                if sudo mv "$db_file" "${db_file}.corrupted" && sudo cp "$backup_db_file" "$db_file"; then
                    sudo chown plex:plex "$db_file"
                    sudo chmod 644 "$db_file"
                    log_success "Database restored from backup"

                    # Clean up
                    rm -rf "$temp_extract"

                    return 0
                else
                    log_error "Failed to replace database with backup"
                fi
            else
                log_error "Backup database also has integrity issues"
            fi
        else
            log_error "Database file not found in backup"
        fi
    else
        log_error "Failed to extract backup"
    fi

    # Clean up on failure
    rm -rf "$temp_extract"
    return 1
}

# Main recovery function
main_recovery() {
    local db_file="$PLEX_DB_DIR/$MAIN_DB"

    log_info "Starting Plex database recovery process"
    log_info "Recovery log: $RECOVERY_LOG"

    # Check dependencies
    if ! check_dependencies; then
        exit 1
    fi

    # Stop Plex service
    if ! stop_plex_service; then
        exit 1
    fi

    # Change to database directory
    cd "$PLEX_DB_DIR" || {
        log_error "Failed to change to database directory"
        exit 1
    }

    # Check if database exists
    if [ ! -f "$MAIN_DB" ]; then
        log_error "Main database file not found: $MAIN_DB"
        exit 1
    fi

    # Create backup of current corrupted state
    log_info "Creating backup of current corrupted database..."
    if [ "$DRY_RUN" = false ]; then
        sudo cp "$MAIN_DB" "${MAIN_DB}.${BACKUP_SUFFIX}"
        log_success "Corrupted database backed up as: ${MAIN_DB}.${BACKUP_SUFFIX}"
    fi

    # Check current integrity
    log_info "Verifying database corruption..."
    if check_database_integrity "$MAIN_DB"; then
        log_success "Database integrity check passed - no recovery needed!"
        start_plex_service
        exit 0
    fi

    log_warning "Database corruption confirmed, attempting recovery..."

    # Try recovery methods in order
    local recovery_methods=(
        "recovery_method_sqlite_recover"
        "recovery_method_partial_extraction"
        "recovery_method_emergency_extraction"
        "recovery_method_backup_restore"
    )

    for method in "${recovery_methods[@]}"; do
        log_info "Attempting: $method"

        if $method "$MAIN_DB"; then
            log_success "Recovery successful using: $method"

            # Verify the recovered database
            if check_database_integrity "$MAIN_DB"; then
                log_success "Recovered database integrity verified"
                start_plex_service
                log_success "Database recovery completed successfully!"
                log_info "Please check your Plex server and verify your libraries"
                exit 0
            else
                log_error "Recovered database still has integrity issues"
                # Restore backup for next attempt
                if [ "$DRY_RUN" = false ]; then
                    sudo cp "${MAIN_DB}.${BACKUP_SUFFIX}" "$MAIN_DB"
                fi
            fi
        else
            log_warning "Recovery method failed: $method"
        fi
    done

    log_error "All recovery methods failed"
    log_error "Manual intervention required"

    # Restore original corrupted database
    if [ "$DRY_RUN" = false ]; then
        sudo cp "${MAIN_DB}.${BACKUP_SUFFIX}" "$MAIN_DB"
    fi

    start_plex_service
    exit 1
}

# Trap to ensure Plex service is restarted
trap 'start_plex_service' EXIT

# Run main recovery
main_recovery "$@"
