const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const database = require('../config/database');

const migrationsDir = path.join(__dirname);

/**
 * Enterprise-grade migration runner with proper state tracking
 */
class MigrationRunner {
  constructor() {
    this.processId = `migration_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  /**
   * Calculate SHA-256 checksum of migration content
   */
  calculateChecksum(content) {
    return crypto.createHash('sha256').update(content).digest('hex');
  }

  /**
   * Parse migration version from filename
   */
  parseVersion(filename) {
    const match = filename.match(/^(\d{3})_/);
    return match ? match[1] : null;
  }

  /**
   * Check if migration tracking system exists
   */
  async ensureMigrationTrackingExists() {
    try {
      const result = await database.query(`
        SELECT EXISTS (
          SELECT 1 FROM information_schema.tables 
          WHERE table_name = 'schema_migrations' 
          AND table_schema = 'public'
        ) as exists
      `);
      
      return result.rows[0].exists;
    } catch (error) {
      console.error('Error checking migration tracking:', error);
      return false;
    }
  }

  /**
   * Initialize migration tracking system
   */
  async initializeMigrationTracking() {
    console.log('🔧 Initializing migration tracking system...');
    
    const trackingMigrationPath = path.join(migrationsDir, '000_migration_tracking_system.sql');
    if (!fs.existsSync(trackingMigrationPath)) {
      throw new Error('Migration tracking system file not found: 000_migration_tracking_system.sql');
    }

    const trackingSQL = fs.readFileSync(trackingMigrationPath, 'utf8');
    await database.query(trackingSQL);
    console.log('✅ Migration tracking system initialized');
  }

  /**
   * Acquire migration lock to prevent concurrent runs
   */
  async acquireLock() {
    console.log(`🔒 Acquiring migration lock (${this.processId})...`);
    
    const result = await database.query(
      'SELECT acquire_migration_lock($1) as acquired',
      [this.processId]
    );
    
    if (!result.rows[0].acquired) {
      throw new Error('Could not acquire migration lock. Another migration may be running.');
    }
    
    console.log('✅ Migration lock acquired');
  }

  /**
   * Release migration lock
   */
  async releaseLock() {
    try {
      await database.query('SELECT release_migration_lock($1)', [this.processId]);
      console.log('🔓 Migration lock released');
    } catch (error) {
      console.warn('⚠️  Error releasing migration lock:', error.message);
    }
  }

  /**
   * Check if migration has already been applied
   */
  async isMigrationApplied(version) {
    const result = await database.query(
      'SELECT migration_applied($1) as applied',
      [version]
    );
    return result.rows[0].applied;
  }

  /**
   * Record migration execution
   */
  async recordMigration(version, filename, checksum, executionTime, success, errorMessage = null) {
    await database.query(
      'SELECT record_migration($1, $2, $3, $4, $5, $6)',
      [version, filename, checksum, executionTime, success, errorMessage]
    );
  }

  /**
   * Get list of migration files to run
   */
  getMigrationFiles() {
    return fs.readdirSync(migrationsDir)
      .filter(file => file.endsWith('.sql') && file !== '000_migration_tracking_system.sql')
      .sort();
  }

  /**
   * Run a single migration
   */
  async runSingleMigration(migrationFile) {
    const version = this.parseVersion(migrationFile);
    if (!version) {
      console.warn(`⚠️  Skipping file with invalid version format: ${migrationFile}`);
      return;
    }

    // Check if already applied
    if (await this.isMigrationApplied(version)) {
      console.log(`⏭️  Skipping already applied migration: ${migrationFile}`);
      return;
    }

    console.log(`🚀 Running migration: ${migrationFile}`);
    
    const migrationPath = path.join(migrationsDir, migrationFile);
    const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
    const checksum = this.calculateChecksum(migrationSQL);
    
    const startTime = Date.now();
    let success = false;
    let errorMessage = null;

    try {
      await database.query(migrationSQL);
      success = true;
      console.log(`✅ Migration ${migrationFile} completed successfully!`);
    } catch (err) {
      errorMessage = err.message;
      console.error(`❌ Migration ${migrationFile} failed:`, err.message);
      
      // Check if it's an idempotent error we can ignore
      const isIdempotentError = this.isIdempotentError(err);
      if (isIdempotentError) {
        console.warn(`⚠️  Treating as idempotent error, marking as successful`);
        success = true;
        errorMessage = `Idempotent: ${err.message}`;
      } else {
        throw err; // Re-throw non-idempotent errors
      }
    } finally {
      const executionTime = Date.now() - startTime;
      await this.recordMigration(version, migrationFile, checksum, executionTime, success, errorMessage);
    }
  }

  /**
   * Check if error is idempotent (safe to ignore)
   */
  isIdempotentError(err) {
    const message = (err && err.message) ? err.message.toLowerCase() : '';
    const code = err && err.code ? err.code : '';
    
    return message.includes('already exists') ||
           code === '42710' /* duplicate_object */ ||
           code === '42P07' /* duplicate_table */ ||
           code === '42701' /* duplicate_column */ ||
           code === '42P06' /* duplicate_schema */ ||
           code === '42723' /* duplicate_function */;
  }

  /**
   * Display migration status
   */
  async displayStatus() {
    try {
      const result = await database.query('SELECT * FROM get_migration_history() LIMIT 10');
      console.log('\n📊 Recent Migration History:');
      console.log('Version | Filename | Applied At | Success | Time (ms)');
      console.log('--------|----------|------------|---------|----------');
      
      result.rows.forEach(row => {
        const status = row.success ? '✅' : '❌';
        const time = row.execution_time_ms || 'N/A';
        console.log(`${row.version.padEnd(7)} | ${row.filename.substring(0, 30).padEnd(30)} | ${row.applied_at.toISOString().substring(0, 19)} | ${status.padEnd(7)} | ${time}`);
      });

      const versionResult = await database.query('SELECT get_current_schema_version() as version');
      console.log(`\n🏷️  Current Schema Version: ${versionResult.rows[0].version || 'None'}`);
    } catch (error) {
      console.warn('⚠️  Could not display migration status:', error.message);
    }
  }

  /**
   * Main migration runner
   */
  async runMigrations() {
    console.log('🚀 Starting Enterprise Database Migration System...');
    
    try {
      // Connect to database
      await database.testConnection();
      console.log('✅ Database connected successfully');

      // Ensure migration tracking exists
      const trackingExists = await this.ensureMigrationTrackingExists();
      if (!trackingExists) {
        await this.initializeMigrationTracking();
      }

      // Acquire lock
      await this.acquireLock();

      // Get migration files
      const migrationFiles = this.getMigrationFiles();
      console.log(`📄 Found ${migrationFiles.length} migration files to process`);

      // Run migrations
      for (const migrationFile of migrationFiles) {
        await this.runSingleMigration(migrationFile);
      }

      // Display status
      await this.displayStatus();
      
      console.log('🎉 All migrations completed successfully!');
      
    } catch (error) {
      console.error('❌ Migration failed:', error);
      process.exit(1);
    } finally {
      await this.releaseLock();
      await database.close();
      console.log('🔌 Database connection closed');
    }
  }
}

// Run migrations if this file is executed directly
if (require.main === module) {
  const runner = new MigrationRunner();
  runner.runMigrations();
}

module.exports = { MigrationRunner };
