/**
 * Improvement Startup Service
 *
 * Handles self-improvement continuation after process restart.
 * This is the critical hook that makes self-improvement work
 * with hot-reload / nodemon.
 *
 * @module services/orchestrator/improvement-startup
 */
import { spawn } from 'child_process';
import * as path from 'path';
import * as fs from 'fs/promises';
import { getImprovementMarkerService, } from './improvement-marker.service.js';
import { LoggerService } from '../core/logger.service.js';
/**
 * Default validation checks
 */
const DEFAULT_VALIDATIONS = [
    {
        name: 'build',
        command: 'npm',
        args: ['run', 'build'],
        required: true,
        timeoutMs: 120000,
    },
    {
        name: 'lint',
        command: 'npm',
        args: ['run', 'lint'],
        required: true,
        timeoutMs: 60000,
    },
    {
        name: 'test',
        command: 'npm',
        args: ['run', 'test'],
        required: true,
        timeoutMs: 300000,
    },
];
/**
 * Maximum restart count before giving up
 */
const MAX_RESTART_COUNT = 3;
/**
 * ImprovementStartupService class
 *
 * Runs at application startup to check for and handle pending
 * self-improvements that were interrupted by hot-reload.
 */
export class ImprovementStartupService {
    projectRoot;
    logger = LoggerService.getInstance().createComponentLogger('ImprovementStartup');
    /**
     * Creates an instance of ImprovementStartupService.
     *
     * @param projectRoot - Project root directory
     */
    constructor(projectRoot) {
        this.projectRoot = projectRoot || process.cwd();
    }
    /**
     * Run the startup check.
     *
     * This should be called early in application startup,
     * before normal operations begin.
     *
     * @returns Result of the startup check
     */
    async runStartupCheck() {
        const markerService = getImprovementMarkerService();
        await markerService.initialize();
        // Check for pending improvement
        const marker = await markerService.getPendingImprovement();
        if (!marker) {
            return { hadPendingImprovement: false };
        }
        this.logger.info('Found pending improvement', { id: marker.id });
        this.logger.info('Improvement state', { phase: marker.phase, restartCount: marker.restartCount });
        // Increment restart count
        const restartCount = await markerService.incrementRestartCount();
        // Check for too many restarts (possible infinite loop)
        if (restartCount > MAX_RESTART_COUNT) {
            this.logger.error('Too many restarts, forcing rollback', { restartCount });
            return await this.handleTooManyRestarts(marker);
        }
        // Handle based on phase
        switch (marker.phase) {
            case 'planning':
            case 'backing_up':
                // Improvement was interrupted before changes were applied
                // Safe to just clean up
                return await this.handleInterruptedPlanning(marker);
            case 'changes_applied':
                // Changes were applied, need to validate
                return await this.handleChangesApplied(marker);
            case 'validating':
                // Was in the middle of validation, continue
                return await this.handleValidating(marker);
            case 'rolling_back':
                // Was rolling back, continue
                return await this.handleRollingBack(marker);
            case 'rolled_back':
                // Rollback complete, just clean up
                return await this.handleRolledBack(marker);
            case 'complete':
                // Shouldn't have a pending marker, clean up
                await markerService.deleteMarker();
                return { hadPendingImprovement: true, action: 'cleaned_up' };
            default:
                this.logger.warn('Unknown phase', { phase: marker.phase });
                return { hadPendingImprovement: true, action: 'none' };
        }
    }
    /**
     * Handle interrupted planning phase.
     *
     * @param marker - The improvement marker
     * @returns Startup result
     */
    async handleInterruptedPlanning(marker) {
        this.logger.info('Improvement interrupted during planning, cleaning up');
        const markerService = getImprovementMarkerService();
        await markerService.deleteMarker();
        await this.notifySlack(marker, ':warning: Self-improvement was interrupted during planning and has been cancelled.', 'normal');
        return {
            hadPendingImprovement: true,
            improvementId: marker.id,
            phase: marker.phase,
            action: 'cleaned_up',
        };
    }
    /**
     * Handle changes_applied phase - run validation.
     *
     * @param marker - The improvement marker
     * @returns Startup result
     */
    async handleChangesApplied(marker) {
        this.logger.info('Changes applied, running validation...');
        const markerService = getImprovementMarkerService();
        await markerService.updatePhase('validating');
        await this.notifySlack(marker, ':hourglass: Crewly restarted after self-improvement. Running validation...', 'normal');
        // Run validation
        const validationPassed = await this.runValidation(marker);
        if (validationPassed) {
            return await this.handleValidationSuccess(marker);
        }
        else {
            return await this.handleValidationFailure(marker);
        }
    }
    /**
     * Handle validating phase - continue validation.
     *
     * @param marker - The improvement marker
     * @returns Startup result
     */
    async handleValidating(marker) {
        this.logger.info('Continuing validation...');
        // Check which validations have already passed
        const passedChecks = marker.validation.results
            .filter(r => r.passed)
            .map(r => r.check);
        // Run remaining validations
        const validationPassed = await this.runValidation(marker, passedChecks);
        if (validationPassed) {
            return await this.handleValidationSuccess(marker);
        }
        else {
            return await this.handleValidationFailure(marker);
        }
    }
    /**
     * Run validation checks.
     *
     * @param marker - The improvement marker
     * @param skipChecks - Checks to skip (already passed)
     * @returns True if all required validations passed
     */
    async runValidation(marker, skipChecks = []) {
        const markerService = getImprovementMarkerService();
        let allPassed = true;
        for (const check of DEFAULT_VALIDATIONS) {
            if (skipChecks.includes(check.name)) {
                this.logger.info('Skipping check (already passed)', { check: check.name });
                continue;
            }
            this.logger.info('Running validation check', { check: check.name });
            const startTime = Date.now();
            try {
                await this.runCommand(check.command, check.args, check.timeoutMs);
                const result = {
                    check: check.name,
                    passed: true,
                    duration: Date.now() - startTime,
                };
                await markerService.recordValidationResult(result);
                this.logger.info('Validation check passed', { check: check.name, duration: result.duration });
            }
            catch (error) {
                const result = {
                    check: check.name,
                    passed: false,
                    output: error.message,
                    duration: Date.now() - startTime,
                };
                await markerService.recordValidationResult(result);
                this.logger.error('Validation check FAILED', { check: check.name, output: result.output });
                if (check.required) {
                    allPassed = false;
                    break; // Stop on first required failure
                }
            }
        }
        return allPassed;
    }
    /**
     * Handle validation success.
     *
     * @param marker - The improvement marker
     * @returns Startup result
     */
    async handleValidationSuccess(marker) {
        this.logger.info('All validation passed!');
        const markerService = getImprovementMarkerService();
        await markerService.completeImprovement(true);
        await this.notifySlack(marker, `:white_check_mark: *Self-improvement completed successfully!*\n\n*${marker.description}*\n\nAll validation checks passed. Changes are now active.`, 'normal');
        return {
            hadPendingImprovement: true,
            improvementId: marker.id,
            phase: 'complete',
            action: 'validated',
            validationPassed: true,
        };
    }
    /**
     * Handle validation failure - trigger rollback.
     *
     * @param marker - The improvement marker
     * @returns Startup result
     */
    async handleValidationFailure(marker) {
        this.logger.info('Validation failed, initiating rollback...');
        const markerService = getImprovementMarkerService();
        // Get fresh marker with updated validation results
        const updatedMarker = await markerService.getPendingImprovement();
        const currentMarker = updatedMarker || marker;
        // Get failed checks
        const failedChecks = currentMarker.validation.results
            .filter(r => !r.passed)
            .map(r => `${r.check}: ${r.output || 'failed'}`)
            .join('\n');
        await markerService.recordRollbackStarted(`Validation failed:\n${failedChecks}`);
        await this.notifySlack(currentMarker, `:x: *Self-improvement validation failed!*\n\n*${currentMarker.description}*\n\nRolling back changes...\n\n\`\`\`\n${failedChecks}\n\`\`\``, 'high');
        // Perform rollback
        const rollbackResult = await this.performRollback(currentMarker);
        if (rollbackResult.success) {
            await markerService.recordRollbackCompleted(rollbackResult.filesRestored, rollbackResult.gitReset);
            // Complete with failure status
            await markerService.completeImprovement(false);
            await this.notifySlack(currentMarker, `:rewind: *Rollback completed.* The codebase has been restored to its previous state.`, 'normal');
            // Note: This may trigger another hot-reload from the rollback file changes
            // The next startup will see phase='complete' and just clean up
        }
        return {
            hadPendingImprovement: true,
            improvementId: currentMarker.id,
            phase: currentMarker.phase,
            action: 'rolled_back',
            validationPassed: false,
            error: failedChecks,
        };
    }
    /**
     * Handle rolling_back phase - continue rollback.
     *
     * @param marker - The improvement marker
     * @returns Startup result
     */
    async handleRollingBack(marker) {
        this.logger.info('Continuing rollback...');
        const markerService = getImprovementMarkerService();
        const rollbackResult = await this.performRollback(marker);
        await markerService.recordRollbackCompleted(rollbackResult.filesRestored, rollbackResult.gitReset);
        await markerService.completeImprovement(false);
        return {
            hadPendingImprovement: true,
            improvementId: marker.id,
            phase: 'rolled_back',
            action: 'rolled_back',
            validationPassed: false,
        };
    }
    /**
     * Handle rolled_back phase - just clean up.
     *
     * @param marker - The improvement marker
     * @returns Startup result
     */
    async handleRolledBack(marker) {
        this.logger.info('Rollback already complete, cleaning up');
        const markerService = getImprovementMarkerService();
        await markerService.completeImprovement(false);
        return {
            hadPendingImprovement: true,
            improvementId: marker.id,
            phase: 'rolled_back',
            action: 'cleaned_up',
        };
    }
    /**
     * Force a rollback of the current improvement.
     * Used for manual rollback requests via the API.
     *
     * @param reason - Reason for the rollback
     * @returns Rollback result with success status and files restored count
     */
    async forceRollback(reason) {
        const markerService = getImprovementMarkerService();
        await markerService.initialize();
        const marker = await markerService.getPendingImprovement();
        if (!marker) {
            return {
                success: false,
                filesRestored: 0,
                message: 'No pending improvement to rollback',
            };
        }
        this.logger.info('Manual rollback requested', { reason });
        await markerService.recordRollbackStarted(reason);
        await this.notifySlack(marker, `:rewind: *Manual rollback requested*\n\n*${marker.description}*\n\nReason: ${reason}`, 'high');
        const rollbackResult = await this.performRollback(marker);
        await markerService.recordRollbackCompleted(rollbackResult.filesRestored, rollbackResult.gitReset);
        await markerService.completeImprovement(false);
        await this.notifySlack(marker, `:white_check_mark: *Rollback completed.* Restored ${rollbackResult.filesRestored.length} files.`, 'normal');
        return {
            success: true,
            filesRestored: rollbackResult.filesRestored.length,
            message: `Rollback completed. Restored ${rollbackResult.filesRestored.length} files.`,
        };
    }
    /**
     * Handle too many restarts.
     *
     * @param marker - The improvement marker
     * @returns Startup result
     */
    async handleTooManyRestarts(marker) {
        const markerService = getImprovementMarkerService();
        await markerService.recordError(`Too many restarts (${marker.restartCount}), possible infinite loop`);
        await this.notifySlack(marker, `:rotating_light: *Self-improvement stuck in restart loop!*\n\n*${marker.description}*\n\nForcing rollback after ${marker.restartCount} restarts.`, 'critical');
        // Force rollback
        await this.performRollback(marker);
        await markerService.completeImprovement(false);
        return {
            hadPendingImprovement: true,
            improvementId: marker.id,
            action: 'rolled_back',
            validationPassed: false,
            error: 'Too many restarts, forced rollback',
        };
    }
    /**
     * Perform rollback of file changes.
     *
     * @param marker - The improvement marker with backup info
     * @returns Rollback result
     */
    async performRollback(marker) {
        const filesRestored = [];
        let gitReset = false;
        // Try git reset first
        if (marker.backup.gitCommit) {
            try {
                await this.runCommand('git', ['reset', '--hard', marker.backup.gitCommit], 30000);
                gitReset = true;
                this.logger.info('Git reset successful', { commit: marker.backup.gitCommit });
                return { success: true, filesRestored: marker.targetFiles, gitReset: true };
            }
            catch (error) {
                this.logger.warn('Git reset failed, using file backups');
            }
        }
        // Restore from file backups
        for (const backup of marker.backup.files) {
            try {
                if (backup.existed) {
                    // Restore original file
                    const content = await fs.readFile(backup.backupPath, 'utf-8');
                    await fs.mkdir(path.dirname(backup.originalPath), { recursive: true });
                    await fs.writeFile(path.join(this.projectRoot, backup.originalPath), content);
                }
                else {
                    // Delete file that was created
                    await fs.unlink(path.join(this.projectRoot, backup.originalPath));
                }
                filesRestored.push(backup.originalPath);
                this.logger.info('Restored file', { path: backup.originalPath });
            }
            catch (error) {
                this.logger.error('Failed to restore file', { path: backup.originalPath, error: error instanceof Error ? error.message : String(error) });
            }
        }
        return { success: true, filesRestored, gitReset };
    }
    /**
     * Run a shell command.
     *
     * @param cmd - Command to run
     * @param args - Command arguments
     * @param timeoutMs - Timeout in milliseconds
     * @returns Command output
     */
    runCommand(cmd, args, timeoutMs) {
        return new Promise((resolve, reject) => {
            const child = spawn(cmd, args, {
                cwd: this.projectRoot,
                stdio: ['pipe', 'pipe', 'pipe'],
                timeout: timeoutMs,
            });
            let stdout = '';
            let stderr = '';
            child.stdout.on('data', (data) => { stdout += data; });
            child.stderr.on('data', (data) => { stderr += data; });
            child.on('close', (code) => {
                if (code === 0) {
                    resolve(stdout);
                }
                else {
                    reject(new Error(stderr || stdout || `Exit code ${code}`));
                }
            });
            child.on('error', reject);
        });
    }
    /**
     * Send Slack notification.
     *
     * @param marker - The improvement marker
     * @param message - Message to send
     * @param urgency - Message urgency level
     */
    async notifySlack(marker, message, urgency) {
        try {
            // Dynamic import to avoid circular dependency
            const { getSlackOrchestratorBridge } = await import('../slack/slack-orchestrator-bridge.js');
            const slackBridge = getSlackOrchestratorBridge();
            await slackBridge.sendNotification({
                type: 'alert',
                title: 'Self-Improvement Update',
                message,
                urgency,
                timestamp: new Date().toISOString(),
            });
        }
        catch {
            // Slack not available, silently ignore
        }
    }
}
/**
 * Singleton instance
 */
let startupServiceInstance = null;
/**
 * Get singleton instance of ImprovementStartupService.
 *
 * @returns The singleton ImprovementStartupService instance
 */
export function getImprovementStartupService() {
    if (!startupServiceInstance) {
        startupServiceInstance = new ImprovementStartupService();
    }
    return startupServiceInstance;
}
/**
 * Reset singleton instance (for testing).
 */
export function resetImprovementStartupService() {
    startupServiceInstance = null;
}
//# sourceMappingURL=improvement-startup.service.js.map