/**
 * Automated Database Installation System
 * Handles automatic installation and configuration of Neo4j
 */
import { exec, spawn } from 'child_process';
import { createWriteStream } from 'fs';
import * as fs from 'fs/promises';
import * as os from 'os';
import * as path from 'path';
import * as readline from 'readline/promises';
import { promisify } from 'util';
import { AdaptiveTimeout, ProgressIndicators } from '../../utils/adaptive-timeout.js';
import { generateScrivenerUUID } from '../../utils/scrivener-utils.js';
import { AppError, ErrorCode, ensureDir, safeReadFile, safeStringify, safeWriteFile, } from '../../utils/common.js';
import { getLogger } from '../../core/logger.js';
import { PermissionManager } from '../../utils/permission-manager.js';
const execAsync = promisify(exec);
const logger = getLogger('auto-installer');
export class Neo4jAutoInstaller {
    /**
     * Main installation entry point
     */
    static async install(options) {
        logger.info('Neo4j Auto-Installation Starting');
        // Check what's available on the system
        const systemInfo = await this.checkSystemCapabilities();
        // Determine installation method
        let method = options.method;
        if (method === 'auto') {
            method = await this.determineOptimalMethod(systemInfo);
        }
        // Get user confirmation if interactive
        if (options.interactive) {
            const confirmed = await this.getUserConfirmation(method, systemInfo);
            if (!confirmed) {
                return {
                    success: false,
                    method: 'none',
                    message: 'Installation cancelled by user',
                };
            }
        }
        // Perform installation
        let result;
        switch (method) {
            case 'docker':
                result = await this.installViaDocker(options);
                break;
            case 'homebrew':
                result = await this.installViaHomebrew(options);
                break;
            case 'native':
                result = await this.installNative(options);
                break;
            default:
                throw new AppError(`Unsupported installation method: ${method}`, ErrorCode.CONFIGURATION_ERROR);
        }
        // Save credentials if successful
        if (result.success && result.credentials) {
            await this.saveCredentials(options.projectPath, result.credentials);
        }
        return result;
    }
    /**
     * Check system capabilities
     */
    static async checkSystemCapabilities() {
        const info = {
            platform: os.platform(),
            arch: os.arch(),
            dockerAvailable: false,
            dockerRunning: false,
            homebrewAvailable: false,
            neo4jInstalled: false,
            java11Available: false,
        };
        // Check Docker
        try {
            await execAsync('docker --version');
            info.dockerAvailable = true;
            // Check if Docker daemon is running
            await execAsync('docker ps');
            info.dockerRunning = true;
        }
        catch {
            // Docker not available or not running
        }
        // Check Homebrew (macOS/Linux)
        if (info.platform === 'darwin' || info.platform === 'linux') {
            try {
                await execAsync('brew --version');
                info.homebrewAvailable = true;
            }
            catch {
                // Homebrew not available
            }
        }
        // Check for existing Neo4j installation
        try {
            await execAsync('neo4j version');
            info.neo4jInstalled = true;
        }
        catch {
            // Neo4j not installed
        }
        // Check for Java 11+
        try {
            const javaVersion = await execAsync('java -version 2>&1');
            if (javaVersion.stdout.includes('11') || javaVersion.stdout.includes('17')) {
                info.java11Available = true;
            }
        }
        catch {
            // Java not available
        }
        return info;
    }
    /**
     * Determine optimal installation method
     */
    static async determineOptimalMethod(systemInfo) {
        // Prefer Docker if available and running
        if (systemInfo.dockerAvailable && systemInfo.dockerRunning) {
            return 'docker';
        }
        // Use Homebrew on macOS if available
        if (systemInfo.platform === 'darwin' && systemInfo.homebrewAvailable) {
            return 'homebrew';
        }
        // Try to start Docker if available but not running
        if (systemInfo.dockerAvailable && !systemInfo.dockerRunning) {
            logger.info('Docker is installed but not running. Attempting to start...');
            try {
                if (systemInfo.platform === 'darwin') {
                    await PermissionManager.executeWithPermissions('open -a Docker', {
                        operation: 'start-docker',
                        timeout: 10000,
                    });
                    await this.waitForDocker(30);
                    return 'docker';
                }
                else if (systemInfo.platform === 'linux') {
                    await PermissionManager.executeWithPermissions('systemctl start docker', {
                        operation: 'start-docker-daemon',
                        timeout: 15000,
                    });
                    await this.waitForDocker(10);
                    return 'docker';
                }
            }
            catch (error) {
                logger.warn('Could not start Docker automatically', {
                    error: error.message,
                });
            }
        }
        // Fall back to native installation
        return 'native';
    }
    /**
     * Get user confirmation for installation
     */
    static async getUserConfirmation(method, systemInfo) {
        const rl = readline.createInterface({
            input: process.stdin,
            output: process.stdout,
        });
        console.error('\n📋 Installation Plan:');
        console.error(`   Method: ${method}`);
        console.error(`   Platform: ${systemInfo.platform}`);
        console.error(`   Architecture: ${systemInfo.arch}`);
        if (method === 'docker') {
            console.error('\n   Docker will be used to run Neo4j in a container.');
            console.error('   This is the recommended approach - clean and isolated.');
        }
        else if (method === 'homebrew') {
            console.error('\n   Homebrew will be used to install Neo4j.');
            console.error('   This will install Neo4j as a system service.');
        }
        else {
            console.error('\n   Native installation will download and configure Neo4j.');
            console.error('   Java will be required (will be installed if missing).');
        }
        console.error("\n   ✅ Benefits you'll get with Neo4j:");
        console.error('      • Character relationship visualization');
        console.error('      • Story structure analysis');
        console.error('      • Plot complexity tracking');
        console.error('      • Advanced writing analytics');
        const answer = await rl.question('\n❓ Do you want to proceed with installation? (yes/no): ');
        rl.close();
        return answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y';
    }
    /**
     * Install Neo4j via Docker
     */
    static async installViaDocker(options) {
        try {
            console.error('\n🐳 Installing Neo4j via Docker...');
            // Pull Neo4j image
            console.error('📥 Downloading Neo4j Docker image...');
            await execAsync(`docker pull neo4j:${options.version || this.NEO4J_VERSION}`);
            // Check if container already exists
            try {
                await execAsync('docker rm -f scrivener-neo4j 2>/dev/null');
            }
            catch {
                // Container doesn't exist, that's fine
            }
            // Create data volume
            console.error('💾 Creating data volume...');
            await execAsync('docker volume create scrivener-neo4j-data');
            // Run Neo4j container
            console.error('🚀 Starting Neo4j container...');
            const password = this.DEFAULT_PASSWORD;
            const runCommand = [
                'docker run -d',
                '--name scrivener-neo4j',
                '--restart unless-stopped',
                '-p 7474:7474',
                '-p 7687:7687',
                `-e NEO4J_AUTH=neo4j/${password}`,
                '-e NEO4J_PLUGINS=\'["apoc-core", "graph-data-science"]\'',
                '-e NEO4J_dbms_memory_heap_initial__size=512m',
                '-e NEO4J_dbms_memory_heap_max__size=2G',
                '-v scrivener-neo4j-data:/data',
                `neo4j:${options.version || this.NEO4J_VERSION}`,
            ].join(' ');
            await execAsync(runCommand);
            // Wait for Neo4j to start
            console.error('⏳ Waiting for Neo4j to start...');
            await this.waitForNeo4j(password);
            console.error('✅ Neo4j installed successfully via Docker!');
            return {
                success: true,
                method: 'docker',
                credentials: {
                    uri: 'bolt://localhost:7687',
                    user: 'neo4j',
                    password,
                    database: 'neo4j',
                },
                message: 'Neo4j installed and running in Docker container "scrivener-neo4j"',
            };
        }
        catch (error) {
            console.error('❌ Docker installation failed:', error);
            // Try to install Docker if not available
            if (error.message?.includes('docker: command not found')) {
                console.error('\n📦 Docker is not installed. Would you like to install it?');
                console.error('Visit: https://docs.docker.com/get-docker/');
            }
            return {
                success: false,
                method: 'docker',
                message: `Docker installation failed: ${error.message}`,
            };
        }
    }
    /**
     * Install Neo4j via Homebrew (macOS/Linux)
     */
    static async installViaHomebrew(options) {
        try {
            console.error('\n🍺 Installing Neo4j via Homebrew...');
            // Update Homebrew
            console.error('📥 Updating Homebrew...');
            await PermissionManager.executeWithPermissions('brew update', {
                operation: 'update-homebrew',
                timeout: 30000,
            });
            // Install Neo4j
            console.error('📦 Installing Neo4j...');
            await PermissionManager.executeWithPermissions('brew install neo4j', {
                operation: 'install-neo4j',
                timeout: 60000,
            });
            // Configure Neo4j
            const password = this.DEFAULT_PASSWORD;
            // Set initial password
            console.error('🔐 Setting initial password...');
            await PermissionManager.executeWithPermissions(`neo4j-admin set-initial-password ${password}`, {
                operation: 'set-neo4j-password',
                timeout: 10000,
            });
            // Start Neo4j service
            if (options.autoStart) {
                console.error('🚀 Starting Neo4j service...');
                await PermissionManager.executeWithPermissions('brew services start neo4j', {
                    operation: 'start-neo4j-service',
                    timeout: 20000,
                });
                // Wait for Neo4j to start
                console.error('⏳ Waiting for Neo4j to start...');
                await this.waitForNeo4j(password);
            }
            console.error('✅ Neo4j installed successfully via Homebrew!');
            console.error('📝 To manage Neo4j:');
            console.error('   Start: brew services start neo4j');
            console.error('   Stop:  brew services stop neo4j');
            console.error('   Status: brew services list');
            return {
                success: true,
                method: 'homebrew',
                credentials: {
                    uri: 'bolt://localhost:7687',
                    user: 'neo4j',
                    password,
                    database: 'neo4j',
                },
                message: 'Neo4j installed via Homebrew. Use "brew services" to manage.',
            };
        }
        catch (error) {
            console.error('❌ Homebrew installation failed:', error);
            return {
                success: false,
                method: 'homebrew',
                message: `Homebrew installation failed: ${error.message}`,
            };
        }
    }
    /**
     * Native installation (download and extract)
     */
    static async installNative(options) {
        try {
            console.error('\n📦 Installing Neo4j natively...');
            // Check Java availability
            const javaVersion = await this.checkJava();
            if (!javaVersion) {
                console.error('☕ Java is required for Neo4j. Installing Java...');
                await this.installJava();
            }
            // Determine download URL
            const platform = os.platform();
            const downloadUrl = this.getNeo4jDownloadUrl(platform);
            // Create installation directory
            const installDir = path.join(os.homedir(), '.scrivener-mcp', 'neo4j');
            await ensureDir(installDir);
            // Download Neo4j
            console.error('📥 Downloading Neo4j...');
            const filename = path.basename(downloadUrl);
            const downloadPath = path.join(installDir, filename);
            await this.downloadFile(downloadUrl, downloadPath);
            // Extract Neo4j
            console.error('📂 Extracting Neo4j...');
            await this.extractArchive(downloadPath, installDir);
            // Find Neo4j directory
            const dirs = await fs.readdir(installDir);
            const neo4jDir = dirs.find((d) => d.startsWith('neo4j-'));
            if (!neo4jDir) {
                throw new AppError('Neo4j directory not found after extraction', ErrorCode.INITIALIZATION_ERROR);
            }
            const neo4jHome = path.join(installDir, neo4jDir);
            // Configure Neo4j
            const password = this.DEFAULT_PASSWORD;
            console.error('🔐 Configuring Neo4j...');
            await this.configureNeo4j(neo4jHome, password);
            // Create start script
            await this.createStartScript(neo4jHome, options.projectPath);
            // Start Neo4j if requested
            if (options.autoStart) {
                console.error('🚀 Starting Neo4j...');
                await this.startNeo4j(neo4jHome);
                console.error('⏳ Waiting for Neo4j to start...');
                await this.waitForNeo4j(password);
            }
            console.error('✅ Neo4j installed successfully!');
            console.error(`📂 Installation directory: ${neo4jHome}`);
            console.error(`📝 Start script: ${path.join(options.projectPath, 'start-neo4j.sh')}`);
            return {
                success: true,
                method: 'native',
                credentials: {
                    uri: 'bolt://localhost:7687',
                    user: 'neo4j',
                    password,
                    database: 'neo4j',
                },
                message: `Neo4j installed at ${neo4jHome}`,
            };
        }
        catch (error) {
            console.error('❌ Native installation failed:', error);
            return {
                success: false,
                method: 'native',
                message: `Native installation failed: ${error.message}`,
            };
        }
    }
    /**
     * Check if Java is installed
     */
    static async checkJava() {
        try {
            const { stdout } = await execAsync('java -version 2>&1');
            return stdout;
        }
        catch {
            return null;
        }
    }
    /**
     * Install Java
     */
    static async installJava() {
        const platform = os.platform();
        if (platform === 'darwin') {
            // macOS
            try {
                await execAsync('brew install openjdk@17');
            }
            catch {
                throw new AppError('Please install Java 17 manually: https://adoptium.net/', ErrorCode.INITIALIZATION_ERROR);
            }
        }
        else if (platform === 'linux') {
            // Linux
            try {
                await execAsync('sudo apt-get update && sudo apt-get install -y openjdk-17-jdk');
            }
            catch {
                try {
                    await execAsync('sudo yum install -y java-17-openjdk');
                }
                catch {
                    throw new AppError('Please install Java 17 manually: https://adoptium.net/', ErrorCode.INITIALIZATION_ERROR);
                }
            }
        }
        else {
            // Windows
            throw new AppError('Please install Java 17 from: https://adoptium.net/', ErrorCode.INITIALIZATION_ERROR);
        }
    }
    /**
     * Get Neo4j download URL for platform
     */
    static getNeo4jDownloadUrl(platform) {
        const version = this.NEO4J_VERSION;
        const baseUrl = 'https://neo4j.com/artifact.php';
        if (platform === 'darwin') {
            return `${baseUrl}?name=neo4j-community-${version}-unix.tar.gz`;
        }
        else if (platform === 'linux') {
            return `${baseUrl}?name=neo4j-community-${version}-unix.tar.gz`;
        }
        else {
            return `${baseUrl}?name=neo4j-community-${version}-windows.zip`;
        }
    }
    /**
     * Download file from URL
     */
    static async downloadFile(url, destination) {
        const https = await import('https');
        const http = await import('http');
        const protocol = url.startsWith('https') ? https : http;
        return new Promise((resolve, reject) => {
            const file = createWriteStream(destination);
            protocol
                .get(url, (response) => {
                if (response.statusCode === 302 || response.statusCode === 301) {
                    // Handle redirect
                    const redirectUrl = response.headers.location;
                    if (redirectUrl) {
                        this.downloadFile(redirectUrl, destination).then(resolve).catch(reject);
                        return;
                    }
                }
                response.pipe(file);
                file.on('finish', () => {
                    file.close();
                    resolve();
                });
            })
                .on('error', (err) => {
                fs.unlink(destination).catch(() => { });
                reject(err);
            });
        });
    }
    /**
     * Extract archive
     */
    static async extractArchive(archivePath, destination) {
        if (archivePath.endsWith('.tar.gz')) {
            await execAsync(`tar -xzf "${archivePath}" -C "${destination}"`);
        }
        else if (archivePath.endsWith('.zip')) {
            await execAsync(`unzip -q "${archivePath}" -d "${destination}"`);
        }
        else {
            throw new AppError('Unsupported archive format', ErrorCode.INVALID_INPUT);
        }
    }
    /**
     * Configure Neo4j
     */
    static async configureNeo4j(neo4jHome, password) {
        const configPath = path.join(neo4jHome, 'conf', 'neo4j.conf');
        // Set initial password
        const adminCmd = path.join(neo4jHome, 'bin', 'neo4j-admin');
        await execAsync(`"${adminCmd}" set-initial-password ${password}`);
        // Update configuration
        let config = await safeReadFile(configPath);
        // Enable APOC procedures
        config += '\n# Scrivener MCP Configuration\n';
        config += 'dbms.security.procedures.unrestricted=apoc.*\n';
        config += 'dbms.security.procedures.allowlist=apoc.*\n';
        await safeWriteFile(configPath, config);
    }
    /**
     * Create start script
     */
    static async createStartScript(neo4jHome, projectPath) {
        const scriptPath = path.join(projectPath, 'start-neo4j.sh');
        const script = `#!/bin/bash
# Start Neo4j for Scrivener MCP
"${neo4jHome}/bin/neo4j" console
`;
        await safeWriteFile(scriptPath, script);
        await fs.chmod(scriptPath, 0o755);
    }
    /**
     * Start Neo4j
     */
    static async startNeo4j(neo4jHome) {
        const neo4jBin = path.join(neo4jHome, 'bin', 'neo4j');
        spawn(neo4jBin, ['start'], {
            detached: true,
            stdio: 'ignore',
        }).unref();
    }
    /**
     * Wait for Docker to be ready with adaptive timeout
     */
    static async waitForDocker(maxSeconds = 30) {
        const timeout = new AdaptiveTimeout({
            operation: 'Docker startup',
            baseTimeout: maxSeconds * 1000,
            maxTimeout: maxSeconds * 1000,
            stallTimeout: 10000,
            progressIndicators: [
                {
                    type: 'completion_check',
                    description: 'Docker availability',
                    check: async () => {
                        try {
                            await execAsync('docker ps', { timeout: 5000 });
                            return true;
                        }
                        catch {
                            return false;
                        }
                    },
                },
            ],
        });
        await timeout.wait(new Promise((resolve, _reject) => {
            const checkDocker = async () => {
                try {
                    await execAsync('docker ps', { timeout: 5000 });
                    resolve();
                }
                catch {
                    // Let adaptive timeout handle the completion check
                    setTimeout(checkDocker, 100);
                }
            };
            checkDocker();
        }));
    }
    /**
     * Wait for Neo4j to be ready with adaptive timeout
     */
    static async waitForNeo4j(password, maxAttempts = 30) {
        const neo4j = await import('neo4j-driver');
        const timeout = new AdaptiveTimeout({
            operation: 'Neo4j startup',
            baseTimeout: maxAttempts * 1000,
            maxTimeout: maxAttempts * 1500, // Allow some extra time
            stallTimeout: 15000, // Neo4j can take longer to start
            progressIndicators: [
                ProgressIndicators.networkProgress('localhost', 7687),
                {
                    type: 'completion_check',
                    description: 'Neo4j connectivity',
                    check: async () => {
                        try {
                            const driver = neo4j.default.driver('bolt://localhost:7687', neo4j.default.auth.basic('neo4j', password));
                            await driver.verifyConnectivity();
                            await driver.close();
                            return true;
                        }
                        catch {
                            return false;
                        }
                    },
                },
            ],
            onProgress: (progress) => {
                if (progress.message?.includes('connectivity')) {
                    process.stderr.write('.');
                }
            },
        });
        await timeout.wait(new Promise((resolve, _reject) => {
            const checkNeo4j = async () => {
                try {
                    const driver = neo4j.default.driver('bolt://localhost:7687', neo4j.default.auth.basic('neo4j', password));
                    await driver.verifyConnectivity();
                    await driver.close();
                    resolve();
                }
                catch {
                    // Let adaptive timeout handle the completion check
                    setTimeout(checkNeo4j, 200);
                }
            };
            checkNeo4j();
        }));
        // Note: If timeout.wait() rejects due to timeout, it will throw an appropriate error
    }
    /**
     * Save credentials to project
     */
    static async saveCredentials(projectPath, credentials) {
        const configDir = path.join(projectPath, '.scrivener-databases');
        await ensureDir(configDir);
        const configPath = path.join(configDir, 'credentials.json');
        await safeWriteFile(configPath, safeStringify({ neo4j: credentials, createdAt: new Date().toISOString() }), { mode: 0o600 });
        // Also create .env file if it doesn't exist
        const envPath = path.join(projectPath, '.env');
        try {
            await fs.access(envPath);
        }
        catch {
            const envContent = `# Neo4j Configuration (auto-generated)
NEO4J_URI=${credentials.uri}
NEO4J_USER=${credentials.user}
NEO4J_PASSWORD=${credentials.password}
NEO4J_DATABASE=${credentials.database}
`;
            await safeWriteFile(envPath, envContent);
        }
    }
}
Neo4jAutoInstaller.DEFAULT_PASSWORD = `scrivener-${generateScrivenerUUID().toLowerCase().slice(0, 8)}`;
Neo4jAutoInstaller.NEO4J_VERSION = '5.15.0';
//# sourceMappingURL=auto-installer.js.map