#!/usr/bin/env python3
"""
AEI Hawaii Development Server - File Restoration Tool

This script restores specific files from the production server to the local 
development environment. It operates in READ-ONLY mode on production and only
downloads files that are missing or corrupted locally.

IMPORTANT: This script makes NO CHANGES to the production server.
It only downloads files FROM production TO the development environment.
"""

import paramiko
import os
import json
import time
import hashlib
from pathlib import Path
from datetime import datetime, timedelta
import fnmatch
import argparse

# =============================================================================
# CONFIGURATION SECTION - Development Environment Settings
# =============================================================================

# SSH Connection Configuration (Production Server - READ-ONLY)
SSH_HOSTNAME = '18.225.0.90'
SSH_PORT = 22
SSH_USERNAME = 'Julian'
SSH_PRIVATE_KEY_PATH = 'schedular_server_private_key.pem'

# Path Configuration - Production vs Development Structure
REMOTE_BASE_PATH = '/var/www/vhosts/aeihawaii.com/httpdocs/scheduler/'  # Production path
LOCAL_BASE_PATH = '/var/www/html/aei_site/scheduler/'                   # Development path

# Restoration Cache Configuration
USE_CACHE = True
CACHE_FILE = 'restore_cache.json'
CACHE_VALIDITY_HOURS = 6  # Shorter cache for restoration operations

# File Filtering Configuration
MAX_FILE_SIZE_MB = 20  # Smaller max size for essential files only

# Essential File Types (only restore these types)
ESSENTIAL_FILE_PATTERNS = [
    '*.php',
    '*.js', 
    '*.css',
    '*.html',
    '*.htm',
    '*.json',
    '*.xml',
    '*.htaccess',
    '*.config',
    '*.ini',
    '*.txt',
    '*.md'
]

# Critical Directories to Monitor
CRITICAL_DIRECTORIES = [
    'system/',
    'assets/',
    'includes/',
    'config/',
    'libraries/',
    'controllers/',
    'models/',
    'views/',
    'helpers/'
]

# Files/Directories to NEVER restore (safety)
NEVER_RESTORE_PATTERNS = [
    'config/database.php',  # Local database config
    'config/config.php',    # Local configuration
    '.git/*',
    'uploads/*',           # User uploads
    'backups/*',           # Backup files
    'logs/*',              # Log files
    'tmp/*',               # Temporary files
    'cache/*',             # Cache files
    'temp/*'               # Temporary files
]

# Connection Configuration
RETRY_DELAY = 2
MAX_RETRIES = 3
CONNECTION_TIMEOUT = 120

# =============================================================================
# FILE RESTORATION FUNCTIONS
# =============================================================================

def should_restore_file(file_path, file_size=None):
    """
    Check if a file should be restored based on configured patterns
    """
    file_name = os.path.basename(file_path).lower()
    relative_path = file_path.replace(REMOTE_BASE_PATH, '').lower()
    
    # Check if file should NEVER be restored
    for pattern in NEVER_RESTORE_PATTERNS:
        if fnmatch.fnmatch(relative_path, pattern.lower()):
            return False, f"matches never-restore pattern '{pattern}'"
    
    # Check if file is essential type
    is_essential = False
    for pattern in ESSENTIAL_FILE_PATTERNS:
        if fnmatch.fnmatch(file_name, pattern.lower()):
            is_essential = True
            break
    
    if not is_essential:
        return False, "not an essential file type"
    
    # Check file size
    if MAX_FILE_SIZE_MB is not None and file_size is not None:
        max_size_bytes = MAX_FILE_SIZE_MB * 1024 * 1024
        if file_size > max_size_bytes:
            size_mb = file_size / (1024 * 1024)
            return False, f"file size {size_mb:.1f}MB exceeds limit {MAX_FILE_SIZE_MB}MB"
    
    # Check if in critical directory
    for critical_dir in CRITICAL_DIRECTORIES:
        if relative_path.startswith(critical_dir.lower()):
            return True, f"in critical directory '{critical_dir}'"
    
    return True, "essential file type"

def create_ssh_connection():
    """
    Create SSH connection for READ-ONLY operations on production
    """
    print("="*60)
    print("FILE RESTORATION FROM PRODUCTION (READ-ONLY)")
    print("="*60)
    print(f"Production Server: {SSH_HOSTNAME}:{SSH_PORT}")
    print(f"User: {SSH_USERNAME}")
    print(f"Key: {SSH_PRIVATE_KEY_PATH}")
    print(f"⚠️  READ-ONLY MODE: No changes will be made to production")
    print("-"*60)
    
    ssh_client = paramiko.SSHClient()
    ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    
    try:
        print("🔍 Loading private key...")
        if not os.path.exists(SSH_PRIVATE_KEY_PATH):
            print(f"❌ Private key file not found: {SSH_PRIVATE_KEY_PATH}")
            return None
        
        private_key = paramiko.RSAKey.from_private_key_file(SSH_PRIVATE_KEY_PATH)
        print("✅ Private key loaded successfully")
        
        print("🌐 Connecting to production server (READ-ONLY)...")
        ssh_client.connect(
            hostname=SSH_HOSTNAME,
            port=SSH_PORT,
            username=SSH_USERNAME,
            pkey=private_key,
            timeout=60,
            allow_agent=False,
            look_for_keys=False
        )
        
        transport = ssh_client.get_transport()
        transport.set_keepalive(30)
        
        print("✅ SSH connection established!")
        
        # Verify connection with read-only test
        stdin, stdout, stderr = ssh_client.exec_command('whoami')
        current_user = stdout.read().decode('utf-8').strip()
        print(f"👤 Connected as: {current_user}")
        print(f"🔒 Mode: READ-ONLY (no production changes)")
        
        return ssh_client
        
    except Exception as e:
        print(f"❌ Connection error: {e}")
        return None

def check_local_file_integrity(local_file_path, remote_file_info):
    """
    Check if local file needs restoration by comparing with remote
    """
    if not os.path.exists(local_file_path):
        return True, "file missing locally"
    
    try:
        local_stat = os.stat(local_file_path)
        local_size = local_stat.st_size
        remote_size = remote_file_info.get('size', 0)
        
        # Size comparison
        if local_size != remote_size:
            return True, f"size mismatch (local: {local_size}, remote: {remote_size})"
        
        # Modification time comparison (if remote is newer)
        local_mtime = int(local_stat.st_mtime)
        remote_mtime = remote_file_info.get('mtime', 0)
        
        if remote_mtime > local_mtime + 300:  # 5 minute tolerance
            return True, f"remote file is newer"
        
        return False, "file appears current"
        
    except Exception as e:
        return True, f"error checking local file: {e}"

def restore_file_from_production(sftp, remote_file_info, reason=""):
    """
    Restore a single file from production to local development environment
    READ-ONLY operation on production - only downloads files
    """
    remote_path = remote_file_info['path']
    
    try:
        # Calculate local path
        relative_path = remote_path.replace(REMOTE_BASE_PATH, '')
        local_path = os.path.join(LOCAL_BASE_PATH, relative_path)
        
        # Create local directory structure
        local_dir = os.path.dirname(local_path)
        os.makedirs(local_dir, exist_ok=True)
        
        # Download file (READ-ONLY operation on production)
        print(f"📥 Restoring: {relative_path}")
        if reason:
            print(f"   Reason: {reason}")
        
        sftp.get(remote_path, local_path)
        
        # Preserve file timestamps
        if 'mtime' in remote_file_info:
            mtime = remote_file_info['mtime']
            atime = remote_file_info.get('atime', mtime)
            os.utime(local_path, (atime, mtime))
        
        file_size = remote_file_info.get('size', 0)
        print(f"✅ Restored: {relative_path} ({file_size} bytes)")
        
        return {
            'success': True,
            'local_path': local_path,
            'size': file_size,
            'reason': reason
        }
        
    except Exception as e:
        print(f"❌ Failed to restore {remote_path}: {e}")
        return {
            'success': False,
            'error': str(e),
            'remote_path': remote_path
        }

def scan_production_files(ssh_client):
    """
    Scan production server for essential files (READ-ONLY operation)
    """
    print(f"\n🔍 Scanning production server for essential files...")
    print(f"   Source: {REMOTE_BASE_PATH}")
    print(f"   Target: {LOCAL_BASE_PATH}")
    
    # Build file exclusion patterns for find command
    find_patterns = []
    for pattern in ESSENTIAL_FILE_PATTERNS:
        find_patterns.extend(['-name', pattern, '-o'])
    
    # Remove last '-o'
    if find_patterns:
        find_patterns.pop()
    
    # Build directory exclusion patterns
    dir_exclude_args = []
    for never_pattern in NEVER_RESTORE_PATTERNS:
        if never_pattern.endswith('/*'):
            dir_name = never_pattern.replace('/*', '')
            dir_exclude_args.extend(['-path', f'*/{dir_name}', '-prune', '-o'])
    
    # Construct find command
    find_cmd_parts = ['find', REMOTE_BASE_PATH]
    find_cmd_parts.extend(dir_exclude_args)
    find_cmd_parts.extend(['-type', 'f', '('])
    find_cmd_parts.extend(find_patterns)
    find_cmd_parts.extend([')', '-exec', 'stat', '-c', '"%n|%s|%Y|%X"', '{}', '\\;'])
    
    command = ' '.join(find_cmd_parts)
    
    print("   📊 Executing file scan on production server...")
    stdin, stdout, stderr = ssh_client.exec_command(command, timeout=300)
    
    result = stdout.read().decode('utf-8')
    error = stderr.read().decode('utf-8')
    
    if error:
        print(f"   ⚠️ Scan warnings: {error}")
    
    # Parse results
    files_found = []
    for line in result.strip().split('\n'):
        if not line.strip():
            continue
        
        line = line.strip('"')
        parts = line.split('|')
        if len(parts) >= 4:
            file_path = parts[0]
            size = int(parts[1]) if parts[1].isdigit() else 0
            mtime = int(parts[2]) if parts[2].isdigit() else 0
            atime = int(parts[3]) if parts[3].isdigit() else 0
            
            # Check if file should be restored
            should_restore, reason = should_restore_file(file_path, size)
            if should_restore:
                files_found.append({
                    'path': file_path,
                    'size': size,
                    'mtime': mtime,
                    'atime': atime,
                    'reason': reason
                })
    
    print(f"✅ Found {len(files_found)} essential files on production")
    return files_found

def restore_missing_files():
    """
    Main function to restore missing/corrupted files from production
    READ-ONLY operation on production server
    """
    print("🚀 AEI Hawaii Development Server - File Restoration")
    print("⚠️  Production server access: READ-ONLY (no changes to production)")
    print(f"   Restoration started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    
    # Ensure local directory exists
    os.makedirs(LOCAL_BASE_PATH, exist_ok=True)
    
    # Create SSH connection
    ssh = create_ssh_connection()
    if not ssh:
        print("❌ Cannot proceed without SSH connection")
        return
    
    try:
        # Create SFTP client
        print("\n🔗 Creating SFTP connection...")
        sftp = ssh.open_sftp()
        print("✅ SFTP connection established")
        
        # Scan production files
        production_files = scan_production_files(ssh)
        
        if not production_files:
            print("⚠️ No essential files found on production server")
            return
        
        # Check which files need restoration
        print(f"\n📋 Checking which files need restoration...")
        files_to_restore = []
        
        for file_info in production_files:
            remote_path = file_info['path']
            relative_path = remote_path.replace(REMOTE_BASE_PATH, '')
            local_path = os.path.join(LOCAL_BASE_PATH, relative_path)
            
            needs_restore, check_reason = check_local_file_integrity(local_path, file_info)
            if needs_restore:
                file_info['restore_reason'] = check_reason
                files_to_restore.append(file_info)
        
        if not files_to_restore:
            print("🎉 All essential files are current - no restoration needed!")
            return
        
        print(f"\n🔧 {len(files_to_restore)} files need restoration:")
        for file_info in files_to_restore[:10]:  # Show first 10
            relative_path = file_info['path'].replace(REMOTE_BASE_PATH, '')
            print(f"   📁 {relative_path} - {file_info['restore_reason']}")
        
        if len(files_to_restore) > 10:
            print(f"   ... and {len(files_to_restore) - 10} more files")
        
        # Restore files
        print(f"\n🚀 Starting file restoration...")
        restored_count = 0
        failed_count = 0
        total_bytes = 0
        
        for i, file_info in enumerate(files_to_restore, 1):
            progress = f"[{i}/{len(files_to_restore)}]"
            print(f"\n{progress} Processing file...")
            
            result = restore_file_from_production(sftp, file_info, file_info['restore_reason'])
            
            if result['success']:
                restored_count += 1
                total_bytes += result['size']
            else:
                failed_count += 1
        
        # Summary
        print("\n" + "="*60)
        print("FILE RESTORATION COMPLETED")
        print("="*60)
        print(f"📊 Statistics:")
        print(f"   Files scanned on production: {len(production_files)}")
        print(f"   Files needing restoration: {len(files_to_restore)}")
        print(f"   Successfully restored: {restored_count}")
        print(f"   Failed restorations: {failed_count}")
        print(f"   Total data restored: {total_bytes / (1024*1024):.1f} MB")
        print(f"   Completion time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        
        if restored_count > 0:
            print(f"✅ Successfully restored {restored_count} files from production!")
        
        if failed_count > 0:
            print(f"⚠️ {failed_count} files failed to restore - check connection and permissions")
        
    except Exception as e:
        print(f"❌ Restoration failed: {e}")
    
    finally:
        print("\n🔒 Cleaning up connections...")
        try:
            sftp.close()
        except:
            pass
        ssh.close()
        print("🔒 All connections closed")
        print("⚠️ REMINDER: No changes were made to the production server")

def main():
    """
    Main entry point with command line options
    """
    parser = argparse.ArgumentParser(description='AEI Hawaii File Restoration Tool')
    parser.add_argument('--dry-run', action='store_true', help='Show what would be restored without doing it')
    parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output')
    
    args = parser.parse_args()
    
    if args.dry_run:
        print("🧪 DRY RUN MODE - No files will be restored")
    
    restore_missing_files()

if __name__ == "__main__":
    main()