import { createServer } from 'http';
import { parse } from 'url';
import next from 'next';
import { WebSocketServer } from 'ws';
import { spawn } from 'child_process';
import { join, resolve } from 'path';
import stripAnsi from 'strip-ansi';
import { spawn as ptySpawn } from 'node-pty';
import { getSSHExecutionService } from './src/server/ssh-execution-service.js';
import { getDatabase } from './src/server/database-prisma.js';
import dotenv from 'dotenv';

// Dynamic import for auto sync init to avoid tsx caching issues
/** @type {any} */
let autoSyncModule = null;

// Load environment variables from .env file
dotenv.config();
// Fallback minimal global error handlers for Node runtime (avoid TS import)
function registerGlobalErrorHandlers() {
  if (registerGlobalErrorHandlers._registered) return;
  registerGlobalErrorHandlers._registered = true;
  process.on('uncaughtException', (err) => {
    console.error('uncaught_exception', err);
  });
  process.on('unhandledRejection', (reason) => {
    console.error('unhandled_rejection', reason);
  });
}
registerGlobalErrorHandlers._registered = false;

const dev = process.env.NODE_ENV !== 'production';
const hostname = '0.0.0.0';
const port = parseInt(process.env.PORT || '3000', 10);

const app = next({ dev, hostname, port });
// Register global handlers once at bootstrap
registerGlobalErrorHandlers();
const handle = app.getRequestHandler();

// WebSocket handler for script execution
/**
 * @typedef {import('ws').WebSocket & {connectionTime?: number, clientIP?: string}} ExtendedWebSocket
 */

/**
 * @typedef {Object} Execution
 * @property {any} process
 * @property {ExtendedWebSocket} ws
 */

/**
 * @typedef {Object} ServerInfo
 * @property {string} name
 * @property {string} ip
 * @property {string} user
 * @property {string} password
 * @property {number} [id]
 */

/**
 * @typedef {Object} ExecutionResult
 * @property {any} process
 * @property {Function} kill
 */

/**
 * @typedef {Object} WebSocketMessage
 * @property {string} action
 * @property {string} [scriptPath]
 * @property {string} [executionId]
 * @property {string} [input]
 * @property {string} [mode]
 * @property {ServerInfo} [server]
 * @property {boolean} [isUpdate]
 * @property {boolean} [isShell]
 * @property {boolean} [isBackup]
 * @property {boolean} [isClone]
 * @property {string} [containerId]
 * @property {string} [storage]
 * @property {string} [backupStorage]
 * @property {number} [cloneCount]
 * @property {string[]} [hostnames]
 * @property {'lxc'|'vm'} [containerType]
 * @property {Record<string, string|number|boolean>} [envVars]
 */

class ScriptExecutionHandler {
  /**
   * @param {import('http').Server} server
   */
  constructor(server) {
    // Create WebSocketServer without attaching to server
    // We'll handle upgrades manually to avoid interfering with Next.js HMR
    this.wss = new WebSocketServer({ 
      noServer: true
    });
    this.activeExecutions = new Map();
    this.db = getDatabase();
    this.setupWebSocket();
  }
  
  /**
   * Handle WebSocket upgrade for our endpoint
   * @param {import('http').IncomingMessage} request
   * @param {import('stream').Duplex} socket
   * @param {Buffer} head
   */
  handleUpgrade(request, socket, head) {
    this.wss.handleUpgrade(request, socket, head, (ws) => {
      this.wss.emit('connection', ws, request);
    });
  }

  /**
   * Parse Container ID from terminal output
   * @param {string} output - Terminal output to parse
   * @returns {string|null} - Container ID if found, null otherwise
   */
  parseContainerId(output) {
    // First, strip ANSI color codes to make pattern matching more reliable
    const cleanOutput = output.replace(/\x1b\[[0-9;]*m/g, '');
    
    // Look for various patterns that Proxmox scripts might use
    const patterns = [
      // Primary pattern - the exact format from the output
      /🆔\s+Container\s+ID:\s+(\d+)/i,
      
      // Standard patterns with flexible spacing
      /🆔\s*Container\s*ID:\s*(\d+)/i,
      /Container\s*ID:\s*(\d+)/i,
      /CT\s*ID:\s*(\d+)/i,
      /Container\s*(\d+)/i,
      
      // Alternative patterns
      /CT\s*(\d+)/i,
      /Container\s*created\s*with\s*ID\s*(\d+)/i,
      /Created\s*container\s*(\d+)/i,
      /Container\s*(\d+)\s*created/i,
      /ID:\s*(\d+)/i,
      
      // Patterns with different spacing and punctuation
      /Container\s*ID\s*:\s*(\d+)/i,
      /CT\s*ID\s*:\s*(\d+)/i,
      /Container\s*#\s*(\d+)/i,
      /CT\s*#\s*(\d+)/i,
      
      // Patterns that might appear in success messages
      /Successfully\s*created\s*container\s*(\d+)/i,
      /Container\s*(\d+)\s*is\s*ready/i,
      /Container\s*(\d+)\s*started/i,
      
      // Generic number patterns that might be container IDs (3-4 digits)
      /(?:^|\s)(\d{3,4})(?:\s|$)/m,
    ];

    // Try patterns on both original and cleaned output
    const outputsToTry = [output, cleanOutput];
    
    for (const testOutput of outputsToTry) {
      for (const pattern of patterns) {
        const match = testOutput.match(pattern);
        if (match && match[1]) {
          const containerId = match[1];
          // Additional validation: container IDs are typically 3-4 digits
          if (containerId.length >= 3 && containerId.length <= 4) {
            return containerId;
          }
        }
      }
    }
    
    
    return null;
  }

  /**
   * Parse Web UI URL from terminal output
   * @param {string} output - Terminal output to parse
   * @returns {{ip: string, port: number}|null} - Object with ip and port if found, null otherwise
   */
  parseWebUIUrl(output) {
    // First, strip ANSI color codes to make pattern matching more reliable
    const cleanOutput = output.replace(/\x1b\[[0-9;]*m/g, '');
    
    // Look for URL patterns with any valid IP address (private or public)
    const patterns = [
      // HTTP/HTTPS URLs with IP and port
      /https?:\/\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)/gi,
      // URLs without explicit port (assume default ports)
      /https?:\/\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:\/|$|\s)/gi,
      // URLs with trailing slash and port
      /https?:\/\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)\//gi,
      // URLs with just IP and port (no protocol)
      /(?:^|\s)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)(?:\s|$)/gi,
      // URLs with just IP (no protocol, no port)
      /(?:^|\s)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:\s|$)/gi,
    ];

    // Try patterns on both original and cleaned output
    const outputsToTry = [output, cleanOutput];
    
    for (const testOutput of outputsToTry) {
      for (const pattern of patterns) {
        const matches = [...testOutput.matchAll(pattern)];
        for (const match of matches) {
          if (match[1]) {
            const ip = match[1];
            const port = match[2] || (match[0].startsWith('https') ? '443' : '80');
            
            // Validate IP address format
            if (ip.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) {
              return {
                ip: ip,
                port: parseInt(port, 10)
              };
            }
          }
        }
      }
    }
    
    return null;
  }

  /**
   * Create installation record
   * @param {string} scriptName - Name of the script
   * @param {string} scriptPath - Path to the script
   * @param {string} executionMode - 'local' or 'ssh'
   * @param {number|null} serverId - Server ID for SSH executions
   * @returns {Promise<number|null>} - Installation record ID
   */
  async createInstallationRecord(scriptName, scriptPath, executionMode, serverId = null) {
    try {
      const result = await this.db.createInstalledScript({
        script_name: scriptName,
        script_path: scriptPath,
        container_id: undefined,
        server_id: serverId ?? undefined,
        execution_mode: executionMode,
        status: 'in_progress',
        output_log: ''
      });
      return Number(result.id);
    } catch (error) {
      console.error('Error creating installation record:', error);
      return null;
    }
  }

  /**
   * Update installation record
   * @param {number} installationId - Installation record ID
   * @param {Object} updateData - Data to update
   */
  async updateInstallationRecord(installationId, updateData) {
    try {
      await this.db.updateInstalledScript(installationId, updateData);
    } catch (error) {
      console.error('Error updating installation record:', error);
    }
  }

  setupWebSocket() {
    this.wss.on('connection', (ws, request) => {
      
      // Set connection metadata
      /** @type {ExtendedWebSocket} */ (ws).connectionTime = Date.now();
      /** @type {ExtendedWebSocket} */ (ws).clientIP = request.socket.remoteAddress || 'unknown';
      
      ws.on('message', (data) => {
        try {
          const rawMessage = data.toString();
          const message = JSON.parse(rawMessage);
          this.handleMessage(/** @type {ExtendedWebSocket} */ (ws), message);
        } catch (error) {
          console.error('Error parsing WebSocket message:', error);
          this.sendMessage(ws, {
            type: 'error',
            data: 'Invalid message format',
            timestamp: Date.now()
          });
        }
      });

      ws.on('close', (code, reason) => {
        this.cleanupActiveExecutions(/** @type {ExtendedWebSocket} */ (ws));
      });

      ws.on('error', (error) => {
        console.error('WebSocket error:', error);
        this.cleanupActiveExecutions(/** @type {ExtendedWebSocket} */ (ws));
      });
    });
  }

  /**
   * @param {ExtendedWebSocket} ws
   * @param {WebSocketMessage} message
   */
  async handleMessage(ws, message) {
    const { action, scriptPath, executionId, input, mode, server, isUpdate, isShell, isBackup, isClone, containerId, storage, backupStorage, cloneCount, hostnames, containerType, envVars } = message;

    switch (action) {
      case 'start':
        if (scriptPath && executionId) {
          if (isClone && containerId && storage && server && cloneCount && hostnames && containerType) {
            await this.startSSHCloneExecution(ws, containerId, executionId, storage, server, containerType, cloneCount, hostnames);
          } else if (isBackup && containerId && storage) {
            await this.startBackupExecution(ws, containerId, executionId, storage, mode, server);
          } else if (isUpdate && containerId) {
            await this.startUpdateExecution(ws, containerId, executionId, mode, server, backupStorage);
          } else if (isShell && containerId) {
            await this.startShellExecution(ws, containerId, executionId, mode, server);
          } else {
            await this.startScriptExecution(ws, scriptPath, executionId, mode, server, envVars);
          }
        } else {
          this.sendMessage(ws, {
            type: 'error',
            data: 'Missing scriptPath or executionId',
            timestamp: Date.now()
          });
        }
        break;

      case 'stop':
        if (executionId) {
          this.stopScriptExecution(executionId);
        }
        break;

      case 'input':
        if (executionId && input !== undefined) {
          this.sendInputToProcess(executionId, input);
        }
        break;

      default:
        this.sendMessage(ws, {
          type: 'error',
          data: 'Unknown action',
          timestamp: Date.now()
        });
    }
  }

  /**
   * @param {ExtendedWebSocket} ws
   * @param {string} scriptPath
   * @param {string} executionId
   * @param {string} mode
   * @param {ServerInfo|null} server
   * @param {Object} [envVars] - Optional environment variables to pass to the script
   */
  async startScriptExecution(ws, scriptPath, executionId, mode = 'local', server = null, envVars = {}) {
    /** @type {number|null} */
    let installationId = null;
    
    try {
      
      // Check if execution is already running
      if (this.activeExecutions.has(executionId)) {
        this.sendMessage(ws, {
          type: 'error',
          data: 'Script execution already running',
          timestamp: Date.now()
        });
        return;
      }

      // Extract script name from path
      const scriptName = scriptPath.split('/').pop() ?? scriptPath.split('\\').pop() ?? 'Unknown Script';
      
      // Create installation record
      const serverId = server ? (server.id ?? null) : null;
      installationId = await this.createInstallationRecord(scriptName, scriptPath, mode, serverId);
      
      if (!installationId) {
        console.error('Failed to create installation record');
      }

      // Handle SSH execution
      if (mode === 'ssh' && server) {
        await this.startSSHScriptExecution(ws, scriptPath, executionId, server, installationId, envVars);
        return;
      }
      
      if (mode === 'ssh' && !server) {
        // SSH mode requested but no server provided, falling back to local execution
      }

      // Basic validation for local execution
      const scriptsDir = join(process.cwd(), 'scripts');
      const resolvedPath = resolve(scriptPath);
      
      if (!resolvedPath.startsWith(resolve(scriptsDir))) {
        this.sendMessage(ws, {
          type: 'error',
          data: 'Script path is not within the allowed scripts directory',
          timestamp: Date.now()
        });
        
        // Update installation record with failure
        if (installationId) {
          await this.updateInstallationRecord(installationId, { status: 'failed' });
        }
        return;
      }

      // Format environment variables for local execution
      // Convert envVars object to environment variables
      const envWithVars = {
        ...process.env,
        TERM: 'xterm-256color', // Enable proper terminal support
        FORCE_ANSI: 'true', // Allow ANSI codes for proper display
        COLUMNS: '80', // Set terminal width
        LINES: '24' // Set terminal height
      };

      // Add envVars to environment
      if (envVars && typeof envVars === 'object') {
        for (const [key, value] of Object.entries(envVars)) {
          /** @type {Record<string, string>} */
          const envRecord = envWithVars;
          envRecord[key] = String(value);
        }
      }

      // Start script execution with pty for proper TTY support
      const childProcess = ptySpawn('bash', [resolvedPath], {
        cwd: scriptsDir,
        name: 'xterm-256color',
        cols: 80,
        rows: 24,
        env: envWithVars
      });

      // pty handles encoding automatically
      
      // Store the execution with installation ID
      this.activeExecutions.set(executionId, { 
        process: childProcess, 
        ws, 
        installationId,
        outputBuffer: ''
      });

      // Send start message
      this.sendMessage(ws, {
        type: 'start',
        data: `Starting execution of ${scriptPath}`,
        timestamp: Date.now()
      });

      // Handle pty data (both stdout and stderr combined)
      childProcess.onData(async (data) => {
        const output = data.toString();
        
        // Store output in buffer for logging
        const execution = this.activeExecutions.get(executionId);
        if (execution) {
          execution.outputBuffer += output;
          // Keep only last 1000 characters to avoid memory issues
          if (execution.outputBuffer.length > 1000) {
            execution.outputBuffer = execution.outputBuffer.slice(-1000);
          }
        }
        
        // Parse for Container ID
        const containerId = this.parseContainerId(output);
        if (containerId && installationId) {
          await this.updateInstallationRecord(installationId, { container_id: containerId });
        }
        
        // Parse for Web UI URL
        const webUIUrl = this.parseWebUIUrl(output);
        if (webUIUrl && installationId) {
          const { ip, port } = webUIUrl;
          if (ip && port) {
            await this.updateInstallationRecord(installationId, { 
              web_ui_ip: ip, 
              web_ui_port: port 
            });
          }
        }
        
        this.sendMessage(ws, {
          type: 'output',
          data: output,
          timestamp: Date.now()
        });
      });

      // Handle process exit
      childProcess.onExit((e) => {
        const execution = this.activeExecutions.get(executionId);
        const isSuccess = e.exitCode === 0;
        
        // Update installation record with final status and output
        if (installationId && execution) {
          this.updateInstallationRecord(installationId, {
            status: isSuccess ? 'success' : 'failed',
            output_log: execution.outputBuffer
          });
        }
        
        this.sendMessage(ws, {
          type: 'end',
          data: `Script execution finished with code: ${e.exitCode}, signal: ${e.signal}`,
          timestamp: Date.now()
        });
        
        // Clean up
        this.activeExecutions.delete(executionId);
      });

    } catch (error) {
      this.sendMessage(ws, {
        type: 'error',
        data: `Failed to start script: ${error instanceof Error ? error.message : String(error)}`,
        timestamp: Date.now()
      });
      
      // Update installation record with failure
      if (installationId) {
        await this.updateInstallationRecord(installationId, { status: 'failed' });
      }
    }
  }

  /**
   * Start SSH script execution
   * @param {ExtendedWebSocket} ws
   * @param {string} scriptPath
   * @param {string} executionId
   * @param {ServerInfo} server
   * @param {number|null} installationId
   * @param {Object} [envVars] - Optional environment variables to pass to the script
   */
  async startSSHScriptExecution(ws, scriptPath, executionId, server, installationId = null, envVars = {}) {
    const sshService = getSSHExecutionService();

    // Send start message
    this.sendMessage(ws, {
      type: 'start',
      data: `Starting SSH execution of ${scriptPath} on ${server.name} (${server.ip})`,
      timestamp: Date.now()
    });

    try {
      const execution = /** @type {ExecutionResult} */ (await sshService.executeScript(
        server,
        scriptPath,
        /** @param {string} data */ async (data) => {
          // Store output in buffer for logging
          const exec = this.activeExecutions.get(executionId);
          if (exec) {
            exec.outputBuffer += data;
            // Keep only last 1000 characters to avoid memory issues
            if (exec.outputBuffer.length > 1000) {
              exec.outputBuffer = exec.outputBuffer.slice(-1000);
            }
          }
          
          // Parse for Container ID
          const containerId = this.parseContainerId(data);
          if (containerId && installationId) {
            await this.updateInstallationRecord(installationId, { container_id: containerId });
          }
          
          // Parse for Web UI URL
          const webUIUrl = this.parseWebUIUrl(data);
          if (webUIUrl && installationId) {
            const { ip, port } = webUIUrl;
            if (ip && port) {
              await this.updateInstallationRecord(installationId, { 
                web_ui_ip: ip, 
                web_ui_port: port 
              });
            }
          }
          
          // Handle data output
          this.sendMessage(ws, {
            type: 'output',
            data: data,
            timestamp: Date.now()
          });
        },
        /** @param {string} error */ (error) => {
          // Store error in buffer for logging
          const exec = this.activeExecutions.get(executionId);
          if (exec) {
            exec.outputBuffer += error;
            // Keep only last 1000 characters to avoid memory issues
            if (exec.outputBuffer.length > 1000) {
              exec.outputBuffer = exec.outputBuffer.slice(-1000);
            }
          }
          
          // Handle errors
          this.sendMessage(ws, {
            type: 'error',
            data: error,
            timestamp: Date.now()
          });
        },
        /** @param {number} code */ async (code) => {
          const exec = this.activeExecutions.get(executionId);
          const isSuccess = code === 0;
          
          // Update installation record with final status and output
          if (installationId && exec) {
            await this.updateInstallationRecord(installationId, {
              status: isSuccess ? 'success' : 'failed',
              output_log: exec.outputBuffer
            });
          }
          
          // Handle process exit
          this.sendMessage(ws, {
            type: 'end',
            data: `SSH script execution finished with code: ${code}`,
            timestamp: Date.now()
          });
          
          // Clean up
          this.activeExecutions.delete(executionId);
        },
        envVars
      ));

      // Store the execution with installation ID
      this.activeExecutions.set(executionId, { 
        process: execution.process, 
        ws, 
        installationId,
        outputBuffer: ''
      });

    } catch (error) {
      this.sendMessage(ws, {
        type: 'error',
        data: `Failed to start SSH execution: ${error instanceof Error ? error.message : String(error)}`,
        timestamp: Date.now()
      });
      
      // Update installation record with failure
      if (installationId) {
        await this.updateInstallationRecord(installationId, { status: 'failed' });
      }
    }
  }

  /**
   * @param {string} executionId
   */
  stopScriptExecution(executionId) {
    const execution = this.activeExecutions.get(executionId);
    if (execution) {
      execution.process.kill('SIGTERM');
      this.activeExecutions.delete(executionId);
      
      this.sendMessage(execution.ws, {
        type: 'end',
        data: 'Script execution stopped by user',
        timestamp: Date.now()
      });
    }
  }

  /**
   * @param {string} executionId
   * @param {string} input
   */
  sendInputToProcess(executionId, input) {
    const execution = this.activeExecutions.get(executionId);
    if (execution && execution.process.write) {
      execution.process.write(input);
    }
  }

  /**
   * @param {ExtendedWebSocket} ws
   * @param {any} message
   */
  sendMessage(ws, message) {
    if (ws.readyState === 1) { // WebSocket.OPEN
      ws.send(JSON.stringify(message));
    }
  }

  /**
   * @param {ExtendedWebSocket} ws
   */
  cleanupActiveExecutions(ws) {
    for (const [executionId, execution] of this.activeExecutions.entries()) {
      if (execution.ws === ws) {
        execution.process.kill('SIGTERM');
        this.activeExecutions.delete(executionId);
      }
    }
  }

  /**
   * Start backup execution
   * @param {ExtendedWebSocket} ws
   * @param {string} containerId
   * @param {string} executionId
   * @param {string} storage
   * @param {string} mode
   * @param {ServerInfo|null} server
   */
  async startBackupExecution(ws, containerId, executionId, storage, mode = 'local', server = null) {
    try {
      // Send start message
      this.sendMessage(ws, {
        type: 'start',
        data: `Starting backup for container ${containerId} to storage ${storage}...`,
        timestamp: Date.now()
      });

      if (mode === 'ssh' && server) {
        await this.startSSHBackupExecution(ws, containerId, executionId, storage, server);
      } else {
        this.sendMessage(ws, {
          type: 'error',
          data: 'Backup is only supported via SSH',
          timestamp: Date.now()
        });
      }
    } catch (error) {
      this.sendMessage(ws, {
        type: 'error',
        data: `Failed to start backup: ${error instanceof Error ? error.message : String(error)}`,
        timestamp: Date.now()
      });
    }
  }

  /**
   * Start SSH backup execution
   * @param {ExtendedWebSocket} ws
   * @param {string} containerId
   * @param {string} executionId
   * @param {string} storage
   * @param {ServerInfo} server
   * @param {Function} [onComplete] - Optional callback when backup completes
   */
  startSSHBackupExecution(ws, containerId, executionId, storage, server, onComplete = undefined) {
    const sshService = getSSHExecutionService();
    
    return new Promise((resolve, reject) => {
      try {
        const backupCommand = `vzdump ${containerId} --storage ${storage} --mode snapshot`;
        
        // Wrap the onExit callback to resolve our promise
        let promiseResolved = false;
        
        sshService.executeCommand(
          server,
          backupCommand,
          /** @param {string} data */
          (data) => {
            this.sendMessage(ws, {
              type: 'output',
              data: data,
              timestamp: Date.now()
            });
          },
          /** @param {string} error */
          (error) => {
            this.sendMessage(ws, {
              type: 'error',
              data: error,
              timestamp: Date.now()
            });
          },
          /** @param {number} code */
          (code) => {
            // Don't send 'end' message here if this is part of a backup+update flow
            // The update flow will handle completion messages
            const success = code === 0;
            
            if (!success) {
              this.sendMessage(ws, {
                type: 'error',
                data: `Backup failed with exit code: ${code}`,
                timestamp: Date.now()
              });
            }
            
            // Send a completion message (but not 'end' type to avoid stopping terminal)
            this.sendMessage(ws, {
              type: 'output',
              data: `\n[Backup ${success ? 'completed' : 'failed'} with exit code: ${code}]\n`,
              timestamp: Date.now()
            });
            
            if (onComplete) onComplete(success);
            
            // Resolve the promise when backup completes
            // Use setImmediate to ensure resolution happens in the right execution context
            if (!promiseResolved) {
              promiseResolved = true;
              const result = { success, code };
              
              // Use setImmediate to ensure promise resolution happens in the next tick
              // This ensures the await in startUpdateExecution can properly resume
              setImmediate(() => {
                try {
                  resolve(result);
                } catch (resolveError) {
                  console.error('Error resolving backup promise:', resolveError);
                  reject(resolveError);
                }
              });
            }
            
            this.activeExecutions.delete(executionId);
          }
        ).then((execution) => {
          // Store the execution
          this.activeExecutions.set(executionId, { 
            process: /** @type {any} */ (execution).process, 
            ws
          });
          // Note: Don't resolve here - wait for onExit callback
        }).catch((error) => {
          console.error('Error starting backup execution:', error);
          this.sendMessage(ws, {
            type: 'error',
            data: `SSH backup execution failed: ${error instanceof Error ? error.message : String(error)}`,
            timestamp: Date.now()
          });
          if (onComplete) onComplete(false);
          if (!promiseResolved) {
            promiseResolved = true;
            reject(error);
          }
        });

      } catch (error) {
        console.error('Error in startSSHBackupExecution:', error);
        this.sendMessage(ws, {
          type: 'error',
          data: `SSH backup execution failed: ${error instanceof Error ? error.message : String(error)}`,
          timestamp: Date.now()
        });
        if (onComplete) onComplete(false);
        reject(error);
      }
    });
  }

  /**
   * Start SSH clone execution
   * Gets next IDs sequentially: get next ID → clone → get next ID → clone, etc.
   * @param {ExtendedWebSocket} ws
   * @param {string} containerId
   * @param {string} executionId
   * @param {string} storage
   * @param {ServerInfo} server
   * @param {'lxc'|'vm'} containerType
   * @param {number} cloneCount
   * @param {string[]} hostnames
   */
  async startSSHCloneExecution(ws, containerId, executionId, storage, server, containerType, cloneCount, hostnames) {
    const sshService = getSSHExecutionService();
    
    this.sendMessage(ws, {
      type: 'start',
      data: `Starting clone operation: Creating ${cloneCount} clone(s) of ${containerType.toUpperCase()} ${containerId}...`,
      timestamp: Date.now()
    });

    try {
      // Step 1: Stop source container/VM
      this.sendMessage(ws, {
        type: 'output',
        data: `\n[Step 1/${4 + cloneCount}] Stopping source ${containerType.toUpperCase()} ${containerId}...\n`,
        timestamp: Date.now()
      });

      const stopCommand = containerType === 'lxc' ? `pct stop ${containerId}` : `qm stop ${containerId}`;
      await new Promise(/** @type {(resolve: (value?: void) => void, reject: (error?: any) => void) => void} */ ((resolve, reject) => {
        sshService.executeCommand(
          server,
          stopCommand,
          /** @param {string} data */
          (data) => {
            this.sendMessage(ws, {
              type: 'output',
              data: data,
              timestamp: Date.now()
            });
          },
          /** @param {string} error */
          (error) => {
            this.sendMessage(ws, {
              type: 'error',
              data: error,
              timestamp: Date.now()
            });
          },
          /** @param {number} code */
          (code) => {
            if (code === 0) {
              this.sendMessage(ws, {
                type: 'output',
                data: `\n[Step 1/${4 + cloneCount}] Source ${containerType.toUpperCase()} stopped successfully.\n`,
                timestamp: Date.now()
              });
              resolve();
            } else {
              // Continue even if stop fails (might already be stopped)
              this.sendMessage(ws, {
                type: 'output',
                data: `\n[Step 1/${4 + cloneCount}] Stop command completed with exit code ${code} (container may already be stopped).\n`,
                timestamp: Date.now()
              });
              resolve();
            }
          }
        );
      }));

      // Step 2: Clone for each clone count (get next ID sequentially before each clone)
      const clonedIds = [];
      for (let i = 0; i < cloneCount; i++) {
        const cloneNumber = i + 1;
        const hostname = hostnames[i];

        // Get next ID for this clone
        this.sendMessage(ws, {
          type: 'output',
          data: `\n[Step ${2 + i}/${4 + cloneCount}] Getting next available ID for clone ${cloneNumber}...\n`,
          timestamp: Date.now()
        });

        let nextId = '';
        try {
          let output = '';
          await new Promise(/** @type {(resolve: (value?: void) => void, reject: (error?: any) => void) => void} */ ((resolve, reject) => {
            sshService.executeCommand(
              server,
              'pvesh get /cluster/nextid',
              /** @param {string} data */
              (data) => {
                output += data;
              },
              /** @param {string} error */
              (error) => {
                reject(new Error(`Failed to get next ID: ${error}`));
              },
              /** @param {number} exitCode */
              (exitCode) => {
                if (exitCode === 0) {
                  resolve();
                } else {
                  reject(new Error(`pvesh command failed with exit code ${exitCode}`));
                }
              }
            );
          }));

          nextId = output.trim();
          if (!nextId || !/^\d+$/.test(nextId)) {
            throw new Error('Invalid next ID received');
          }

          this.sendMessage(ws, {
            type: 'output',
            data: `\n[Step ${2 + i}/${4 + cloneCount}] Got next ID: ${nextId}\n`,
            timestamp: Date.now()
          });
        } catch (error) {
          this.sendMessage(ws, {
            type: 'error',
            data: `\n[Step ${2 + i}/${4 + cloneCount}] Failed to get next ID: ${error instanceof Error ? error.message : String(error)}\n`,
            timestamp: Date.now()
          });
          throw error;
        }

        clonedIds.push(nextId);

        // Clone the container/VM
        this.sendMessage(ws, {
          type: 'output',
          data: `\n[Step ${2 + i}/${4 + cloneCount}] Cloning ${containerType.toUpperCase()} ${containerId} to ${nextId} with hostname ${hostname}...\n`,
          timestamp: Date.now()
        });

        const cloneCommand = containerType === 'lxc'
          ? `pct clone ${containerId} ${nextId} --hostname ${hostname} --storage ${storage}`
          : `qm clone ${containerId} ${nextId} --name ${hostname} --storage ${storage}`;

        await new Promise(/** @type {(resolve: (value?: void) => void, reject: (error?: any) => void) => void} */ ((resolve, reject) => {
          sshService.executeCommand(
            server,
            cloneCommand,
            /** @param {string} data */
            (data) => {
              this.sendMessage(ws, {
                type: 'output',
                data: data,
                timestamp: Date.now()
              });
            },
            /** @param {string} error */
            (error) => {
              this.sendMessage(ws, {
                type: 'error',
                data: error,
                timestamp: Date.now()
              });
            },
            /** @param {number} code */
            (code) => {
              if (code === 0) {
                this.sendMessage(ws, {
                  type: 'output',
                  data: `\n[Step ${2 + i}/${4 + cloneCount}] Clone ${cloneNumber} created successfully.\n`,
                  timestamp: Date.now()
                });
                resolve();
              } else {
                this.sendMessage(ws, {
                  type: 'error',
                  data: `\nClone ${cloneNumber} failed with exit code: ${code}\n`,
                  timestamp: Date.now()
                });
                reject(new Error(`Clone ${cloneNumber} failed with exit code ${code}`));
              }
            }
          );
        }));
      }

      // Step 3: Start source container/VM
      this.sendMessage(ws, {
        type: 'output',
        data: `\n[Step ${2 + cloneCount + 1}/${4 + cloneCount}] Starting source ${containerType.toUpperCase()} ${containerId}...\n`,
        timestamp: Date.now()
      });

      const startSourceCommand = containerType === 'lxc' ? `pct start ${containerId}` : `qm start ${containerId}`;
      await new Promise(/** @type {(resolve: (value?: void) => void, reject: (error?: any) => void) => void} */ ((resolve) => {
        sshService.executeCommand(
          server,
          startSourceCommand,
          /** @param {string} data */
          (data) => {
            this.sendMessage(ws, {
              type: 'output',
              data: data,
              timestamp: Date.now()
            });
          },
          /** @param {string} error */
          (error) => {
            this.sendMessage(ws, {
              type: 'error',
              data: error,
              timestamp: Date.now()
            });
          },
          /** @param {number} code */
          (code) => {
            if (code === 0) {
              this.sendMessage(ws, {
                type: 'output',
                data: `\n[Step ${2 + cloneCount + 1}/${4 + cloneCount}] Source ${containerType.toUpperCase()} started successfully.\n`,
                timestamp: Date.now()
              });
            } else {
              this.sendMessage(ws, {
                type: 'output',
                data: `\n[Step ${2 + cloneCount + 1}/${4 + cloneCount}] Start command completed with exit code ${code}.\n`,
                timestamp: Date.now()
              });
            }
            resolve();
          }
        );
      }));

      // Step 4: Start target containers/VMs
      this.sendMessage(ws, {
        type: 'output',
        data: `\n[Step ${2 + cloneCount + 2}/${4 + cloneCount}] Starting cloned ${containerType.toUpperCase()}(s)...\n`,
        timestamp: Date.now()
      });

      for (let i = 0; i < cloneCount; i++) {
        const cloneNumber = i + 1;
        const nextId = clonedIds[i];

        const startTargetCommand = containerType === 'lxc' ? `pct start ${nextId}` : `qm start ${nextId}`;
        await new Promise(/** @type {(resolve: (value?: void) => void, reject: (error?: any) => void) => void} */ ((resolve) => {
          sshService.executeCommand(
            server,
            startTargetCommand,
            /** @param {string} data */
            (data) => {
              this.sendMessage(ws, {
                type: 'output',
                data: data,
                timestamp: Date.now()
              });
            },
            /** @param {string} error */
            (error) => {
              this.sendMessage(ws, {
                type: 'error',
                data: error,
                timestamp: Date.now()
              });
            },
            /** @param {number} code */
            (code) => {
              if (code === 0) {
                this.sendMessage(ws, {
                  type: 'output',
                  data: `\nClone ${cloneNumber} (ID: ${nextId}) started successfully.\n`,
                  timestamp: Date.now()
                });
              } else {
                this.sendMessage(ws, {
                  type: 'output',
                  data: `\nClone ${cloneNumber} (ID: ${nextId}) start completed with exit code ${code}.\n`,
                  timestamp: Date.now()
                });
              }
              resolve();
            }
          );
        }));
      }

      // Step 5: Add to database
      this.sendMessage(ws, {
        type: 'output',
        data: `\n[Step ${2 + cloneCount + 3}/${4 + cloneCount}] Adding cloned ${containerType.toUpperCase()}(s) to database...\n`,
        timestamp: Date.now()
      });

      for (let i = 0; i < cloneCount; i++) {
        const nextId = clonedIds[i];
        const hostname = hostnames[i];
        
        try {
          // Read config file to get hostname/name
          const configPath = containerType === 'lxc' 
            ? `/etc/pve/lxc/${nextId}.conf`
            : `/etc/pve/qemu-server/${nextId}.conf`;
          
          let configContent = '';
          await new Promise(/** @type {(resolve: (value?: void) => void) => void} */ ((resolve) => {
            sshService.executeCommand(
              server,
              `cat "${configPath}" 2>/dev/null || echo ""`,
              /** @param {string} data */
              (data) => {
                configContent += data;
              },
              () => resolve(),
              () => resolve()
            );
          }));

          // Parse config for hostname/name
          let finalHostname = hostname;
          if (configContent.trim()) {
            const lines = configContent.split('\n');
            for (const line of lines) {
              const trimmed = line.trim();
              if (containerType === 'lxc' && trimmed.startsWith('hostname:')) {
                finalHostname = trimmed.substring(9).trim();
                break;
              } else if (containerType === 'vm' && trimmed.startsWith('name:')) {
                finalHostname = trimmed.substring(5).trim();
                break;
              }
            }
          }

          if (!finalHostname) {
            finalHostname = `${containerType}-${nextId}`;
          }

          // Create installed script record
          const script = await this.db.createInstalledScript({
            script_name: finalHostname,
            script_path: `cloned/${finalHostname}`,
            container_id: nextId,
            server_id: server.id,
            execution_mode: 'ssh',
            status: 'success',
            output_log: `Cloned ${containerType.toUpperCase()}`
          });

          // For LXC, store config in database
          if (containerType === 'lxc' && configContent.trim()) {
            // Simple config parser
            /** @type {any} */
            const configData = {};
            const lines = configContent.split('\n');
            for (const line of lines) {
              const trimmed = line.trim();
              if (!trimmed || trimmed.startsWith('#')) continue;
              
              const [key, ...valueParts] = trimmed.split(':');
              const value = valueParts.join(':').trim();
              
              if (key === 'hostname') configData.hostname = value;
              else if (key === 'arch') configData.arch = value;
              else if (key === 'cores') configData.cores = parseInt(value) || null;
              else if (key === 'memory') configData.memory = parseInt(value) || null;
              else if (key === 'swap') configData.swap = parseInt(value) || null;
              else if (key === 'onboot') configData.onboot = parseInt(value) || null;
              else if (key === 'ostype') configData.ostype = value;
              else if (key === 'unprivileged') configData.unprivileged = parseInt(value) || null;
              else if (key === 'tags') configData.tags = value;
              else if (key === 'rootfs') {
                const match = value.match(/^([^:]+):([^,]+)/);
                if (match) {
                  configData.rootfs_storage = match[1];
                  const sizeMatch = value.match(/size=([^,]+)/);
                  if (sizeMatch) {
                    configData.rootfs_size = sizeMatch[1];
                  }
                }
              }
            }
            
            await this.db.createLXCConfig(script.id, configData);
          }

          this.sendMessage(ws, {
            type: 'output',
            data: `\nClone ${i + 1} (ID: ${nextId}, Hostname: ${finalHostname}) added to database successfully.\n`,
            timestamp: Date.now()
          });
        } catch (error) {
          this.sendMessage(ws, {
            type: 'error',
            data: `\nError adding clone ${i + 1} (ID: ${nextId}) to database: ${error instanceof Error ? error.message : String(error)}\n`,
            timestamp: Date.now()
          });
        }
      }

      this.sendMessage(ws, {
        type: 'output',
        data: `\n\n[Clone operation completed successfully!]\nCreated ${cloneCount} clone(s) of ${containerType.toUpperCase()} ${containerId}.\n`,
        timestamp: Date.now()
      });

      this.activeExecutions.delete(executionId);
    } catch (error) {
      this.sendMessage(ws, {
        type: 'error',
        data: `\n\n[Clone operation failed!]\nError: ${error instanceof Error ? error.message : String(error)}\n`,
        timestamp: Date.now()
      });
      this.activeExecutions.delete(executionId);
    }
  }

  /**
   * Start update execution (pct enter + update command)
   * @param {ExtendedWebSocket} ws
   * @param {string} containerId
   * @param {string} executionId
   * @param {string} mode
   * @param {ServerInfo|undefined} server
   * @param {string} [backupStorage] - Optional storage to backup to before update
   */
  async startUpdateExecution(ws, containerId, executionId, mode = 'local', server = undefined, backupStorage = undefined) {
    try {
      // If backup storage is provided, run backup first
      if (backupStorage && mode === 'ssh' && server) {
        this.sendMessage(ws, {
          type: 'start',
          data: `Starting backup before update for container ${containerId}...`,
          timestamp: Date.now()
        });

        // Create a separate execution ID for backup
        const backupExecutionId = `backup_${executionId}`;
        
        // Run backup and wait for it to complete
        try {
          const backupResult = await this.startSSHBackupExecution(
            ws, 
            containerId, 
            backupExecutionId, 
            backupStorage, 
            server
          );
          
          // Backup completed (successfully or not)
          if (!backupResult || !backupResult.success) {
            // Backup failed, but we'll still allow update (per requirement 1b)
            this.sendMessage(ws, {
              type: 'output',
              data: '\n⚠️ Backup failed, but proceeding with update as requested...\n',
              timestamp: Date.now()
            });
          } else {
            // Backup succeeded
            this.sendMessage(ws, {
              type: 'output',
              data: '\n✅ Backup completed successfully. Starting update...\n',
              timestamp: Date.now()
            });
          }
        } catch (error) {
          console.error('Backup error before update:', error);
          // Backup failed to start, but allow update to proceed
          this.sendMessage(ws, {
            type: 'output',
            data: `\n⚠️ Backup error: ${error instanceof Error ? error.message : String(error)}. Proceeding with update...\n`,
            timestamp: Date.now()
          });
        }
        
        // Small delay before starting update
        await new Promise(resolve => setTimeout(resolve, 1000));
      }
      
      // Send start message for update (only if we're actually starting an update)
      this.sendMessage(ws, {
        type: 'start',
        data: `Starting update for container ${containerId}...`,
        timestamp: Date.now()
      });

      if (mode === 'ssh' && server) {
        await this.startSSHUpdateExecution(ws, containerId, executionId, server);
      } else {
        await this.startLocalUpdateExecution(ws, containerId, executionId);
      }

    } catch (error) {
      this.sendMessage(ws, {
        type: 'error',
        data: `Failed to start update: ${error instanceof Error ? error.message : String(error)}`,
        timestamp: Date.now()
      });
    }
  }

  /**
   * Start local update execution
   * @param {ExtendedWebSocket} ws
   * @param {string} containerId
   * @param {string} executionId
   */
  async startLocalUpdateExecution(ws, containerId, executionId) {
    const { spawn } = await import('node-pty');
    
    // Create a shell process that will run pct enter and then update
    const childProcess = spawn('bash', ['-c', `pct enter ${containerId}`], {
      name: 'xterm-color',
      cols: 80,
      rows: 24,
      cwd: process.cwd(),
      env: process.env
    });

    // Store the execution
    this.activeExecutions.set(executionId, { 
      process: childProcess, 
      ws
    });

    // Handle pty data
    childProcess.onData((data) => {
      this.sendMessage(ws, {
        type: 'output',
        data: data.toString(),
        timestamp: Date.now()
      });
    });

    // Send the update command after a delay to ensure we're in the container
    setTimeout(() => {
      childProcess.write('update\n');
    }, 4000);

    // Handle process exit
    childProcess.onExit((e) => {
      this.sendMessage(ws, {
        type: 'end',
        data: `Update completed with exit code: ${e.exitCode}`,
        timestamp: Date.now()
      });
      
      this.activeExecutions.delete(executionId);
    });
  }

  /**
   * Start SSH update execution
   * @param {ExtendedWebSocket} ws
   * @param {string} containerId
   * @param {string} executionId
   * @param {ServerInfo} server
   */
  async startSSHUpdateExecution(ws, containerId, executionId, server) {
    const sshService = getSSHExecutionService();
    
    try {
      const execution = await sshService.executeCommand(
        server,
        `pct enter ${containerId}`,
        /** @param {string} data */
        (data) => {
          this.sendMessage(ws, {
            type: 'output',
            data: data,
            timestamp: Date.now()
          });
        },
        /** @param {string} error */
        (error) => {
          this.sendMessage(ws, {
            type: 'error',
            data: error,
            timestamp: Date.now()
          });
        },
        /** @param {number} code */
        (code) => {
          this.sendMessage(ws, {
            type: 'end',
            data: `Update completed with exit code: ${code}`,
            timestamp: Date.now()
          });
          
          this.activeExecutions.delete(executionId);
        }
      );

      // Store the execution
      this.activeExecutions.set(executionId, { 
        process: /** @type {any} */ (execution).process, 
        ws
      });

      // Send the update command after a delay to ensure we're in the container
      setTimeout(() => {
        /** @type {any} */ (execution).process.write('update\n');
      }, 4000);

    } catch (error) {
      this.sendMessage(ws, {
        type: 'error',
        data: `SSH execution failed: ${error instanceof Error ? error.message : String(error)}`,
        timestamp: Date.now()
      });
    }
  }

  /**
   * Start shell execution
   * @param {ExtendedWebSocket} ws
   * @param {string} containerId
   * @param {string} executionId
   * @param {string} mode
   * @param {ServerInfo|null} server
   */
  async startShellExecution(ws, containerId, executionId, mode = 'local', server = null) {
    try {
      
      // Send start message
      this.sendMessage(ws, {
        type: 'start',
        data: `Starting shell session for container ${containerId}...`,
        timestamp: Date.now()
      });

      if (mode === 'ssh' && server) {
        await this.startSSHShellExecution(ws, containerId, executionId, server);
      } else {
        await this.startLocalShellExecution(ws, containerId, executionId);
      }

    } catch (error) {
      this.sendMessage(ws, {
        type: 'error',
        data: `Failed to start shell: ${error instanceof Error ? error.message : String(error)}`,
        timestamp: Date.now()
      });
    }
  }

  /**
   * Start local shell execution
   * @param {ExtendedWebSocket} ws
   * @param {string} containerId
   * @param {string} executionId
   */
  async startLocalShellExecution(ws, containerId, executionId) {
    const { spawn } = await import('node-pty');
    
    // Create a shell process that will run pct enter
    const childProcess = spawn('bash', ['-c', `pct enter ${containerId}`], {
      name: 'xterm-color',
      cols: 80,
      rows: 24,
      cwd: process.cwd(),
      env: process.env
    });

    // Store the execution
    this.activeExecutions.set(executionId, { 
      process: childProcess, 
      ws
    });

    // Handle pty data
    childProcess.onData((data) => {
      this.sendMessage(ws, {
        type: 'output',
        data: data.toString(),
        timestamp: Date.now()
      });
    });

    // Note: No automatic command is sent - user can type commands interactively

    // Handle process exit
    childProcess.onExit((e) => {
      this.sendMessage(ws, {
        type: 'end',
        data: `Shell session ended with exit code: ${e.exitCode}`,
        timestamp: Date.now()
      });
      
      this.activeExecutions.delete(executionId);
    });
  }

  /**
   * Start SSH shell execution
   * @param {ExtendedWebSocket} ws
   * @param {string} containerId
   * @param {string} executionId
   * @param {ServerInfo} server
   */
  async startSSHShellExecution(ws, containerId, executionId, server) {
    const sshService = getSSHExecutionService();
    
    try {
      const execution = await sshService.executeCommand(
        server,
        `pct enter ${containerId}`,
        /** @param {string} data */
        (data) => {
          this.sendMessage(ws, {
            type: 'output',
            data: data,
            timestamp: Date.now()
          });
        },
        /** @param {string} error */
        (error) => {
          this.sendMessage(ws, {
            type: 'error',
            data: error,
            timestamp: Date.now()
          });
        },
        /** @param {number} code */
        (code) => {
          this.sendMessage(ws, {
            type: 'end',
            data: `Shell session ended with exit code: ${code}`,
            timestamp: Date.now()
          });
          
          this.activeExecutions.delete(executionId);
        }
      );

      // Store the execution
      this.activeExecutions.set(executionId, { 
        process: /** @type {any} */ (execution).process, 
        ws
      });

      // Note: No automatic command is sent - user can type commands interactively

    } catch (error) {
      this.sendMessage(ws, {
        type: 'error',
        data: `SSH shell execution failed: ${error instanceof Error ? error.message : String(error)}`,
        timestamp: Date.now()
      });
    }
  }
}

// TerminalHandler removed - not used by current application

app.prepare().then(() => {
  const httpServer = createServer(async (req, res) => {
    try {
      // Be sure to pass `true` as the second argument to `url.parse`.
      // This tells it to parse the query portion of the URL.
      const parsedUrl = parse(req.url || '', true);
      const { pathname, query } = parsedUrl;

      // Check if this is a WebSocket upgrade request
      const isWebSocketUpgrade = req.headers.upgrade === 'websocket';
      
      // Only intercept WebSocket upgrades for /ws/script-execution
      // Let Next.js handle all other WebSocket upgrades (like HMR) and all HTTP requests
      if (isWebSocketUpgrade && pathname === '/ws/script-execution') {
        // WebSocket upgrade will be handled by the WebSocket server
        // Don't call handle() for this path - let WebSocketServer handle it
        return;
      }

      // Let Next.js handle all other requests including:
      // - HTTP requests to /ws/script-execution (non-WebSocket)
      // - WebSocket upgrades to other paths (like /_next/webpack-hmr)
      // - All static assets (_next routes)
      // - All other routes
      await handle(req, res, parsedUrl);
    } catch (err) {
      console.error('Error occurred handling', req.url, err);
      res.statusCode = 500;
      res.end('internal server error');
    }
  });

  // Create WebSocket handlers
  const scriptHandler = new ScriptExecutionHandler(httpServer);
  
  // Handle WebSocket upgrades manually to avoid interfering with Next.js HMR
  // We need to preserve Next.js's upgrade handlers and call them for non-matching paths
  // Save any existing upgrade listeners (Next.js might have set them up)
  const existingUpgradeListeners = httpServer.listeners('upgrade').slice();
  httpServer.removeAllListeners('upgrade');
  
  // Add our upgrade handler that routes based on path
  httpServer.on('upgrade', (request, socket, head) => {
    const parsedUrl = parse(request.url || '', true);
    const { pathname } = parsedUrl;
    
    if (pathname === '/ws/script-execution') {
      // Handle our custom WebSocket endpoint
      scriptHandler.handleUpgrade(request, socket, head);
    } else {
      // For all other paths (including Next.js HMR), call existing listeners
      // This allows Next.js to handle its own WebSocket upgrades
      for (const listener of existingUpgradeListeners) {
        try {
          listener.call(httpServer, request, socket, head);
        } catch (err) {
          console.error('Error in upgrade listener:', err);
        }
      }
    }
  });
  // Note: TerminalHandler removed as it's not being used by the current application

  httpServer
    .once('error', (err) => {
      console.error(err);
      process.exit(1);
    })
    .listen(port, hostname, async () => {
      console.log(`> Ready on http://${hostname}:${port}`);
      console.log(`> WebSocket server running on ws://${hostname}:${port}/ws/script-execution`);
      
      // Initialize auto sync module and run initialization
      if (!autoSyncModule) {
        try {
          console.log('Dynamically importing autoSyncInit...');
          autoSyncModule = await import('./src/server/lib/autoSyncInit.js');
          console.log('autoSyncModule loaded, exports:', Object.keys(autoSyncModule));
        } catch (error) {
          const err = error instanceof Error ? error : new Error(String(error));
          console.error('Failed to import autoSyncInit:', err.message);
          console.error('Stack:', err.stack);
          throw error;
        }
      }
      
      // Initialize default repositories
      if (typeof autoSyncModule.initializeRepositories === 'function') {
        console.log('Calling initializeRepositories...');
        await autoSyncModule.initializeRepositories();
      } else {
        console.warn('initializeRepositories is not a function, type:', typeof autoSyncModule.initializeRepositories);
      }
      
      // Initialize auto-sync service
      if (typeof autoSyncModule.initializeAutoSync === 'function') {
        console.log('Calling initializeAutoSync...');
        autoSyncModule.initializeAutoSync();
      }
      
      // Setup graceful shutdown handlers
      if (typeof autoSyncModule.setupGracefulShutdown === 'function') {
        console.log('Setting up graceful shutdown...');
        autoSyncModule.setupGracefulShutdown();
      }
    });
});
