/**
 * Crash Recovery Module
 * Robust auto-restart mechanism untuk handle errors dan crashes
 */

const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');

class CrashRecovery {
	constructor(options = {}) {
		this.maxRestarts = options.maxRestarts || 10;
		this.restartWindow = options.restartWindow || 60000; // 1 minute
		this.restartDelay = options.restartDelay || 1000; // 1 second initial delay
		this.maxRestartDelay = options.maxRestartDelay || 30000; // 30 seconds max delay
		this.logDir = options.logDir || path.join(__dirname, '../logs');
		this.crashLogFile = path.join(this.logDir, 'crashes.log');
		this.restartLogFile = path.join(this.logDir, 'restarts.log');
		
		this.restartCount = 0;
		this.restartTimestamps = [];
		this.isRestarting = false;
		this.lastCrashTime = null;
		this.crashDetails = [];
		
		// Ensure log directory exists
		this.ensureLogDirectory();
		
		// Load previous crash history
		this.loadCrashHistory();
	}
	
	ensureLogDirectory() {
		if (!fs.existsSync(this.logDir)) {
			fs.mkdirSync(this.logDir, { recursive: true });
		}
	}
	
	loadCrashHistory() {
		try {
			if (fs.existsSync(this.crashLogFile)) {
				const content = fs.readFileSync(this.crashLogFile, 'utf8');
				const lines = content.split('\n').filter(line => line.trim());
				// Get last 10 crashes
				this.crashDetails = lines.slice(-10).map(line => {
					try {
						return JSON.parse(line);
					} catch {
						return null;
					}
				}).filter(Boolean);
			}
		} catch (err) {
			console.error('Error loading crash history:', err);
		}
	}
	
	logCrash(error, context = {}) {
		const crashInfo = {
			timestamp: new Date().toISOString(),
			pid: process.pid,
			error: {
				name: error.name,
				message: error.message,
				stack: error.stack,
			},
			context,
			memory: process.memoryUsage(),
			uptime: process.uptime(),
		};
		
		this.crashDetails.push(crashInfo);
		// Keep only last 50 crashes
		if (this.crashDetails.length > 50) {
			this.crashDetails = this.crashDetails.slice(-50);
		}
		
		// Write to log file
		try {
			fs.appendFileSync(
				this.crashLogFile,
				JSON.stringify(crashInfo) + '\n',
				'utf8'
			);
		} catch (err) {
			console.error('Error writing crash log:', err);
		}
		
		this.lastCrashTime = Date.now();
		
		return crashInfo;
	}
	
	logRestart(reason, delay) {
		const restartInfo = {
			timestamp: new Date().toISOString(),
			pid: process.pid,
			reason,
			delay,
			restartCount: this.restartCount,
			uptime: process.uptime(),
		};
		
		try {
			fs.appendFileSync(
				this.restartLogFile,
				JSON.stringify(restartInfo) + '\n',
				'utf8'
			);
		} catch (err) {
			console.error('Error writing restart log:', err);
		}
	}
	
	shouldRestart() {
		const now = Date.now();
		
		// Remove timestamps outside the restart window
		this.restartTimestamps = this.restartTimestamps.filter(
			timestamp => now - timestamp < this.restartWindow
		);
		
		// Check if we've exceeded max restarts in the window
		if (this.restartTimestamps.length >= this.maxRestarts) {
			return false;
		}
		
		return true;
	}
	
	calculateRestartDelay() {
		// Exponential backoff with jitter
		const baseDelay = Math.min(
			this.restartDelay * Math.pow(2, this.restartCount),
			this.maxRestartDelay
		);
		
		// Add random jitter (0-20% of delay)
		const jitter = Math.random() * baseDelay * 0.2;
		
		return Math.floor(baseDelay + jitter);
	}
	
	async restart(reason = 'Unknown error') {
		if (this.isRestarting) {
			console.warn('Restart already in progress, skipping...');
			return;
		}
		
		if (!this.shouldRestart()) {
			console.error('========================================');
			console.error('MAX RESTARTS EXCEEDED');
			console.error(`Restarted ${this.restartCount} times in the last ${this.restartWindow / 1000} seconds`);
			console.error('System will not restart automatically');
			console.error('Please investigate the root cause');
			console.error('========================================');
			process.exit(1);
		}
		
		// FIXED: Add safety check to prevent restart loop if process is already exiting
		if (process.exitCode !== undefined && process.exitCode !== 0) {
			console.warn('Process already has exit code set, skipping restart to prevent loop');
			return;
		}
		
		this.isRestarting = true;
		this.restartCount++;
		this.restartTimestamps.push(Date.now());
		
		const delay = this.calculateRestartDelay();
		
		console.error('========================================');
		console.error(`CRASH DETECTED - Restarting in ${delay}ms`);
		console.error(`Reason: ${reason}`);
		console.error(`Restart count: ${this.restartCount}/${this.maxRestarts}`);
		console.error(`Uptime before crash: ${Math.floor(process.uptime())}s`);
		console.error('========================================');
		
		this.logRestart(reason, delay);
		
		// Wait before restarting
		await new Promise(resolve => setTimeout(resolve, delay));
		
		// FIXED: Double-check before restarting to prevent infinite loop
		if (!this.isRestarting) {
			console.warn('Restart flag was cleared, aborting restart');
			return;
		}
		
		// Restart the process
		this.performRestart();
	}
	
	performRestart() {
		try {
			const scriptPath = process.argv[1] || 'app.js';
			const nodeArgs = process.execArgv;
			const scriptArgs = process.argv.slice(2);
			
			console.log('Restarting process...');
			console.log(`Script: ${scriptPath}`);
			console.log(`Args: ${scriptArgs.join(' ')}`);
			
			// Spawn new process
			const child = spawn(process.execPath, [...nodeArgs, scriptPath, ...scriptArgs], {
				detached: true,
				stdio: 'inherit',
				env: process.env,
			});
			
			// Unref child process so parent can exit
			child.unref();
			
			// Give child process time to start
			setTimeout(() => {
				process.exit(0);
			}, 1000);
			
		} catch (err) {
			console.error('Error during restart:', err);
			process.exit(1);
		}
	}
	
	getCrashStats() {
		return {
			restartCount: this.restartCount,
			lastCrashTime: this.lastCrashTime,
			recentCrashes: this.crashDetails.slice(-5),
			canRestart: this.shouldRestart(),
			restartsInWindow: this.restartTimestamps.length,
			maxRestarts: this.maxRestarts,
		};
	}
	
	resetRestartCount() {
		// Reset after successful uptime (e.g., 5 minutes)
		if (process.uptime() > 300) { // 5 minutes
			this.restartCount = 0;
			this.restartTimestamps = [];
		}
	}
}

// Singleton instance
let crashRecoveryInstance = null;

function getCrashRecovery(options) {
	if (!crashRecoveryInstance) {
		crashRecoveryInstance = new CrashRecovery(options);
	}
	return crashRecoveryInstance;
}

module.exports = {
	CrashRecovery,
	getCrashRecovery,
};
