/**
 * Transaction Manager
 * Robust transaction handling dengan retry mechanism dan data integrity protection
 */

const DatabaseConnectionManager = require('./databaseConnectionManager');

class TransactionManager {
	constructor(dbConnectionManager) {
		this.dbManager = dbConnectionManager;
	}
	
	/**
	 * Execute operation with transaction and automatic retry
	 * Ensures data integrity - no partial writes
	 */
	async executeWithTransaction(operation, options = {}) {
		const {
			maxRetries = 3,
			retryDelay = 1000,
			isolationLevel = 'READ COMMITTED',
		} = options;
		
		let lastError = null;
		
		for (let attempt = 0; attempt <= maxRetries; attempt++) {
			// Ensure connection is alive
			const sequelize = await this.dbManager.ensureConnection();
			const transaction = await sequelize.transaction({
				isolationLevel: sequelize.Transaction.ISOLATION_LEVELS[isolationLevel],
			});
			
			try {
				// Execute operation with transaction
				const result = await operation(transaction);
				
				// Commit transaction
				await transaction.commit();
				
				return result;
				
			} catch (error) {
				// Rollback transaction immediately to prevent data corruption
				try {
					await transaction.rollback();
				} catch (rollbackError) {
					console.error('Transaction rollback error:', rollbackError);
					// Log but don't throw - we need to handle the original error
				}
				
				lastError = error;
				
				// Check if error is retryable
				const isRetryable = this.isRetryableTransactionError(error);
				
				if (isRetryable && attempt < maxRetries) {
					const delay = retryDelay * Math.pow(2, attempt); // Exponential backoff
					
					console.warn(`Transaction failed (attempt ${attempt + 1}/${maxRetries + 1}):`, error.message);
					console.warn(`Retrying in ${delay}ms...`);
					
					// Wait before retry
					await new Promise(resolve => setTimeout(resolve, delay));
					
					// Reconnect if connection is lost
					if (this.isConnectionError(error)) {
						try {
							await this.dbManager.reconnect();
						} catch (reconnectError) {
							console.error('Reconnection failed:', reconnectError);
							throw reconnectError;
						}
					}
					
					continue; // Retry
				}
				
				// Not retryable or max retries exceeded
				throw error;
			}
		}
		
		// Should never reach here, but just in case
		throw lastError || new Error('Transaction failed after all retries');
	}
	
	/**
	 * Execute operation without transaction (read-only operations)
	 * With connection retry
	 */
	async executeWithoutTransaction(operation, options = {}) {
		const { maxRetries = 3, retryDelay = 1000 } = options;
		
		let lastError = null;
		
		for (let attempt = 0; attempt <= maxRetries; attempt++) {
			try {
				// Ensure connection is alive
				const sequelize = await this.dbManager.ensureConnection();
				
				// Execute operation
				const result = await operation(sequelize);
				
				return result;
				
			} catch (error) {
				lastError = error;
				
				// Check if error is retryable
				const isRetryable = this.isRetryableError(error);
				
				if (isRetryable && attempt < maxRetries) {
					const delay = retryDelay * Math.pow(2, attempt);
					
					console.warn(`Operation failed (attempt ${attempt + 1}/${maxRetries + 1}):`, error.message);
					console.warn(`Retrying in ${delay}ms...`);
					
					await new Promise(resolve => setTimeout(resolve, delay));
					
					// Reconnect if connection is lost
					if (this.isConnectionError(error)) {
						try {
							await this.dbManager.reconnect();
						} catch (reconnectError) {
							console.error('Reconnection failed:', reconnectError);
							throw reconnectError;
						}
					}
					
					continue; // Retry
				}
				
				// Not retryable or max retries exceeded
				throw error;
			}
		}
		
		throw lastError || new Error('Operation failed after all retries');
	}
	
	isRetryableTransactionError(error) {
		// Transaction-specific retryable errors
		const retryableErrors = [
			'ER_LOCK_WAIT_TIMEOUT',
			'ER_LOCK_DEADLOCK',
			'ER_LOCK_ABORTED',
			'SequelizeConnectionError',
			'SequelizeConnectionRefusedError',
			'SequelizeHostNotFoundError',
			'SequelizeHostNotReachableError',
			'SequelizeInvalidConnectionError',
			'SequelizeConnectionTimedOutError',
			'ETIMEDOUT',
			'ECONNRESET',
			'ECONNREFUSED',
		];
		
		return this.isRetryableError(error) || 
			retryableErrors.some(code => 
				error.code === code || 
				error.name === code ||
				(error.message && error.message.includes(code))
			);
	}
	
	isRetryableError(error) {
		const retryableCodes = [
			'ECONNREFUSED',
			'ETIMEDOUT',
			'ENOTFOUND',
			'ECONNRESET',
			'EHOSTUNREACH',
		];
		
		const retryableMessages = [
			'Connection lost',
			'Connection terminated',
			'Unable to connect',
			'SequelizeConnectionError',
		];
		
		if (error.code && retryableCodes.includes(error.code)) {
			return true;
		}
		
		if (error.message) {
			return retryableMessages.some(msg => error.message.includes(msg));
		}
		
		if (error.name) {
			return retryableMessages.some(msg => error.name.includes(msg));
		}
		
		return false;
	}
	
	isConnectionError(error) {
		const connectionErrors = [
			'ECONNREFUSED',
			'ETIMEDOUT',
			'ECONNRESET',
			'SequelizeConnectionError',
			'SequelizeConnectionRefusedError',
			'SequelizeHostNotFoundError',
			'SequelizeHostNotReachableError',
			'SequelizeInvalidConnectionError',
			'SequelizeConnectionTimedOutError',
		];
		
		return connectionErrors.some(code => 
			error.code === code || 
			error.name === code ||
			(error.message && error.message.includes(code))
		);
	}
}

module.exports = TransactionManager;
