/**
 * Africa Alert - Sync Engine
 * Bidirectional sync between local SQLite and Supabase cloud
 */

const Database = require('better-sqlite3');
const { createClient } = require('@supabase/supabase-js');
const path = require('path');
const fs = require('fs');
const https = require('https');
const http = require('http');

class SyncEngine {
  constructor() {
    this.dbPath = process.env.DB_PATH || path.join(__dirname, '../../data/school.db');
    this.syncInterval = parseInt(process.env.SYNC_INTERVAL || '30000'); // 30 seconds default
    this.syncRunning = false;
    this.syncTimer = null;
    
    // Supabase config
    this.supabaseUrl = process.env.SUPABASE_URL || 'https://api.next_gen.techarvest.co.zw';
    this.supabaseKey = process.env.SUPABASE_KEY || '';
    
    // Tables to sync (order matters for foreign keys)
    this.tablesToSync = [
      'users',
      'departments',
      'classes',
      'subjects',
      'subjects_new',
      'courses',
      'enrollments',
      'course_enrolments',
      'attendance',
      'attendance_new',
      'fee_groups',
      'student_fees',
      'payments',
      'messages',
      'messages_new',
      'events',
      'calendar_events',
      'expenses',
      'grades',
      'course_grades',
      'assignments',
      'submissions',
      'settings',
      'system_settings',
      // Payroll
      'staff_roles',
      'salary_grades',
      'staff_records',
      'leave_types',
      'leave_requests',
      'staff_attendance',
      'payroll_runs',
      'payslips',
      // Inventory
      'item_categories',
      'suppliers',
      'store_locations',
      'items',
      'item_stock',
      'stock_transactions',
      'item_issues',
      // Exams
      'exam_groups',
      'question_banks',
      'exam_schedules',
      'exam_attempts',
      'exam_answers',
      // Hostel
      'hostels',
      'room_types',
      'rooms',
      'room_assignments',
      'hostel_fees',
      // Transport
      'vehicles',
      'routes',
      'pickup_points',
      'vehicle_routes',
      'transport_allocations',
      // Front Office
      'admission_enquiries',
      'visitor_logs',
      'phone_call_logs',
      'postal_dispatch',
      'complaints',
    ];
    
    this.init();
  }

  init() {
    // Ensure data directory exists
    const dataDir = path.dirname(this.dbPath);
    if (!fs.existsSync(dataDir)) {
      fs.mkdirSync(dataDir, { recursive: true });
    }
    
    this.db = new Database(this.dbPath);
    this.db.pragma('foreign_keys = ON');
    this.db.pragma('journal_mode = WAL');
    
    console.log('🚀 SyncEngine initialized');
    console.log(`   Database: ${this.dbPath}`);
    console.log(`   Supabase: ${this.supabaseUrl}`);
    console.log(`   Interval: ${this.syncInterval}ms`);
    
    // Start sync timer
    this.startSyncTimer();
  }

  /**
   * Start the sync timer
   */
  startSyncTimer() {
    if (this.syncTimer) {
      clearInterval(this.syncTimer);
    }
    
    this.syncTimer = setInterval(() => {
      this.runSyncCycle().catch(err => {
        console.error('Sync cycle error:', err);
        this.logSyncError('sync_cycle', null, 'error', err.message);
      });
    }, this.syncInterval);
    
    console.log(`   Sync timer started (every ${this.syncInterval / 1000}s)`);
    
    // Run initial sync after 5 seconds
    setTimeout(() => {
      this.runSyncCycle().catch(console.error);
    }, 5000);
  }

  /**
   * Stop the sync timer
   */
  stopSyncTimer() {
    if (this.syncTimer) {
      clearInterval(this.syncTimer);
      this.syncTimer = null;
      console.log('   Sync timer stopped');
    }
  }

  /**
   * Run a full sync cycle (push + pull)
   */
  async runSyncCycle() {
    if (this.syncRunning) {
      console.log('⏳ Sync already in progress, skipping...');
      return;
    }
    
    if (!this.supabaseKey) {
      console.log('⚠️ Supabase key not configured, skipping sync');
      return;
    }
    
    this.syncRunning = true;
    const startTime = Date.now();
    
    try {
      // Update sync status
      this.db.prepare('INSERT OR REPLACE INTO sync_config (key, value) VALUES (?, ?)').run('sync_running', '1');
      
      console.log('🔄 Starting sync cycle...');
      
      // Step 1: Push local changes to Supabase
      await this.pushChanges();
      
      // Step 2: Pull remote changes from Supabase
      await this.pullChanges();
      
      const duration = Date.now() - startTime;
      console.log(`✅ Sync cycle completed in ${duration}ms`);
      
    } catch (error) {
      console.error('❌ Sync cycle failed:', error.message);
      this.logSyncError('sync_cycle', null, 'error', error.message);
    } finally {
      this.syncRunning = false;
      this.db.prepare('INSERT OR REPLACE INTO sync_config (key, value) VALUES (?, ?)').run('sync_running', '0');
      this.db.prepare('INSERT OR REPLACE INTO sync_config (key, value) VALUES (?, ?)').run('last_sync', new Date().toISOString());
    }
  }

  /**
   * Push local pending changes to Supabase
   */
  async pushChanges() {
    console.log('📤 Pushing local changes...');
    let pushedCount = 0;
    
    for (const tableName of this.tablesToSync) {
      try {
        const pendingRecords = this.db.prepare(`
          SELECT * FROM ${tableName} 
          WHERE sync_status = 'pending' AND is_deleted = 0
          LIMIT 100
        `).all();
        
        if (pendingRecords.length > 0) {
          const result = await this.syncToSupabase(tableName, pendingRecords, 'push');
          pushedCount += result.success;
          
          // Mark successfully synced records
          for (const record of result.successRecords) {
            this.db.prepare(`
              UPDATE ${tableName} 
              SET sync_status = 'synced', last_synced_at = ?
              WHERE id = ?
            `).run(new Date().toISOString(), record.id);
          }
        }
        
        // Handle deleted records
        const deletedRecords = this.db.prepare(`
          SELECT * FROM ${tableName} 
          WHERE is_deleted = 1 AND sync_status = 'pending'
          LIMIT 50
        `).all();
        
        if (deletedRecords.length > 0) {
          await this.deleteFromSupabase(tableName, deletedRecords);
        }
        
      } catch (error) {
        console.log(`   ⚠️ ${tableName}: ${error.message}`);
        this.logSyncError(tableName, null, 'push', error.message);
      }
    }
    
    console.log(`   📤 Pushed ${pushedCount} records`);
  }

  /**
   * Pull remote changes from Supabase
   */
  async pullChanges() {
    console.log('📥 Pulling remote changes...');
    let pulledCount = 0;
    
    // Get last sync time
    const lastSync = this.db.prepare('SELECT value FROM sync_config WHERE key = ?').get('last_sync');
    const lastSyncTime = lastSync?.value || '1970-01-01T00:00:00Z';
    
    for (const tableName of this.tablesToSync) {
      try {
        // For demo/offline mode, skip pull if no Supabase key
        if (!this.supabaseKey) {
          continue;
        }
        
        const remoteRecords = await this.fetchFromSupabase(tableName, lastSyncTime);
        
        if (remoteRecords.length > 0) {
          const result = this.mergeFromSupabase(tableName, remoteRecords);
          pulledCount += result;
        }
        
      } catch (error) {
        console.log(`   ⚠️ ${tableName}: ${error.message}`);
        this.logSyncError(tableName, null, 'pull', error.message);
      }
    }
    
    console.log(`   📥 Pulled ${pulledCount} records`);
  }

  /**
   * Sync records to Supabase via REST API
   */
  async syncToSupabase(tableName, records, operation) {
    const results = { success: 0, failed: 0, successRecords: [], failedRecords: [] };
    
    if (!this.supabaseKey) {
      // Offline mode - just mark as synced locally
      return { success: records.length, successRecords: records };
    }
    
    try {
      // Use Supabase REST API directly
      const url = `${this.supabaseUrl}/rest/v1/${tableName}`;
      
      for (const record of records) {
        try {
          // Exclude local-only fields before sending to Supabase
          const { id, created_at, ...syncData } = record;
          
          const response = await this.httpRequest({
            method: 'POST',
            url: url,
            headers: {
              'apikey': this.supabaseKey,
              'Authorization': `Bearer ${this.supabaseKey}`,
              'Content-Type': 'application/json',
              'Prefer': 'resolution=merge-duplicates'
            },
            body: syncData
          });
          
          if (response.success) {
            results.success++;
            results.successRecords.push(record);
          } else {
            results.failed++;
            results.failedRecords.push(record);
          }
          
        } catch (err) {
          results.failed++;
          results.failedRecords.push(record);
        }
      }
      
    } catch (error) {
      console.log(`   Error syncing ${tableName}:`, error.message);
    }
    
    return results;
  }

  /**
   * Fetch records from Supabase
   */
  async fetchFromSupabase(tableName, since) {
    if (!this.supabaseKey) {
      return [];
    }
    
    try {
      const url = `${this.supabaseUrl}/rest/v1/${tableName}?updated_at=gte.${encodeURIComponent(since)}&select=*`;
      
      const response = await this.httpRequest({
        method: 'GET',
        url: url,
        headers: {
          'apikey': this.supabaseKey,
          'Authorization': `Bearer ${this.supabaseKey}`
        }
      });
      
      return response.data || [];
      
    } catch (error) {
      return [];
    }
  }

  /**
   * Merge remote records into local database
   */
  mergeFromSupabase(tableName, records) {
    if (!records || records.length === 0) return 0;
    
    let mergedCount = 0;
    
    // Get columns from the first record, excluding local-only fields
    // We want to keep fields that exist in our local schema
    const firstRecord = records[0];
    const columns = Object.keys(firstRecord).filter(k => !['id', 'created_at'].includes(k));
    
    if (columns.length === 0) return 0;

    const placeholders = columns.map(() => '?').join(', ');
    const columnList = columns.join(', ');
    const updateList = columns
      .filter(k => k !== 'uid') // Don't update the unique identifier
      .map(k => `${k} = excluded.${k}`)
      .join(', ');

    try {
      const upsert = this.db.prepare(`
        INSERT INTO ${tableName} (${columnList}, sync_status, last_synced_at)
        VALUES (${placeholders}, 'synced', ?)
        ON CONFLICT(uid) DO UPDATE SET
          ${updateList},
          sync_status = 'synced',
          last_synced_at = excluded.last_synced_at
      `);
      
      for (const record of records) {
        try {
          const values = columns.map(k => record[k]);
          values.push(new Date().toISOString()); // For last_synced_at
          
          upsert.run(...values);
          mergedCount++;
          
        } catch (err) {
          console.log(`   Merge error for ${tableName} record ${record.uid}:`, err.message);
        }
      }
    } catch (err) {
      console.log(`   Merge preparation error for ${tableName}:`, err.message);
    }
    
    return mergedCount;
  }

  /**
   * Delete records from Supabase
   */
  async deleteFromSupabase(tableName, records) {
    if (!this.supabaseKey) {
      // Mark as synced if no Supabase
      for (const record of records) {
        this.db.prepare(`UPDATE ${tableName} SET sync_status = 'synced' WHERE id = ?`).run(record.id);
      }
      return;
    }
    
    for (const record of records) {
      try {
        const url = `${this.supabaseUrl}/rest/v1/${tableName}?uid=eq.${record.uid}`;
        
        await this.httpRequest({
          method: 'DELETE',
          url: url,
          headers: {
            'apikey': this.supabaseKey,
            'Authorization': `Bearer ${this.supabaseKey}`
          }
        });
        
        this.db.prepare(`UPDATE ${tableName} SET sync_status = 'synced' WHERE id = ?`).run(record.id);
        
      } catch (err) {
        // Log but continue
        this.logSyncError(tableName, record.id, 'delete', err.message);
      }
    }
  }

  /**
   * HTTP Request helper
   */
  httpRequest(options) {
    return new Promise((resolve, reject) => {
      try {
        const urlObj = new URL(options.url);
        const isHttps = urlObj.protocol === 'https:';
        const httpModule = isHttps ? https : http;
        
        const reqOptions = {
          hostname: urlObj.hostname,
          port: urlObj.port || (isHttps ? 443 : 80),
          path: urlObj.pathname + urlObj.search,
          method: options.method || 'GET',
          headers: options.headers || {},
          timeout: 10000
        };
        
        const req = httpModule.request(reqOptions, (res) => {
          let data = '';
          
          res.on('data', (chunk) => { data += chunk; });
          
          res.on('end', () => {
            try {
              const json = JSON.parse(data);
              resolve({ success: true, data: json, status: res.statusCode });
            } catch {
              resolve({ success: true, data: data, status: res.statusCode });
            }
          });
        });
        
        req.on('error', reject);
        req.on('timeout', () => reject(new Error('Request timeout')));
        
        if (options.body) {
          req.write(JSON.stringify(options.body));
        }
        
        req.end();
        
      } catch (error) {
        reject(error);
      }
    });
  }

  /**
   * Log sync errors
   */
  logSyncError(tableName, recordId, operation, errorMessage) {
    try {
      this.db.prepare(`
        INSERT INTO sync_logs (table_name, record_id, operation, status, error_message)
        VALUES (?, ?, ?, 'failed', ?)
      `).run(tableName, recordId, operation, errorMessage);
    } catch (err) {
      console.log('Failed to log sync error:', err.message);
    }
  }

  /**
   * Get sync status
   */
  getSyncStatus() {
    const lastSync = this.db.prepare('SELECT value FROM sync_config WHERE key = ?').get('last_sync');
    const syncRunning = this.db.prepare('SELECT value FROM sync_config WHERE key = ?').get('sync_running');
    
    const pendingCounts = {};
    for (const table of this.tablesToSync) {
      try {
        const count = this.db.prepare(`SELECT COUNT(*) as count FROM ${table} WHERE sync_status = 'pending'`).get();
        pendingCounts[table] = count?.count || 0;
      } catch {
        pendingCounts[table] = 0;
      }
    }
    
    const totalPending = Object.values(pendingCounts).reduce((a, b) => a + b, 0);
    
    return {
      lastSync: lastSync?.value || 'Never',
      syncRunning: syncRunning?.value === '1',
      totalPending,
      pendingByTable: pendingCounts,
      supabaseConfigured: !!this.supabaseKey
    };
  }

  /**
   * Force sync (manual trigger)
   */
  async forceSync() {
    console.log('🔄 Force sync triggered');
    await this.runSyncCycle();
    return this.getSyncStatus();
  }

  /**
   * Resolve conflicts (Server Wins policy)
   */
  resolveConflict(tableName, recordId, serverData) {
    try {
      // Update local with server data
      const updates = Object.entries(serverData)
        .filter(([key]) => !['id', 'uid', 'created_at'].includes(key))
        .map(([key, value]) => `${key} = ?`);
      
      const values = Object.entries(serverData)
        .filter(([key]) => !['id', 'uid', 'created_at'].includes(key))
        .map(([, value]) => value);
      
      if (updates.length > 0) {
        this.db.prepare(`
          UPDATE ${tableName} 
          SET ${updates.join(', ')}, sync_status = 'synced', last_synced_at = ?
          WHERE id = ?
        `).run(...values, new Date().toISOString(), recordId);
      }
      
      this.logSyncError(tableName, recordId, 'conflict', 'Resolved with server data');
      
      return true;
    } catch (error) {
      this.logSyncError(tableName, recordId, 'conflict', error.message);
      return false;
    }
  }

  /**
   * Cleanup old sync logs
   */
  cleanupOldLogs(daysToKeep = 30) {
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
    
    const result = this.db.prepare(`
      DELETE FROM sync_logs WHERE created_at < ?
    `).run(cutoffDate.toISOString());
    
    console.log(`🧹 Cleaned up ${result.changes} old sync logs`);
    return result.changes;
  }
}

// Singleton instance
let syncEngineInstance = null;

function getSyncEngine() {
  if (!syncEngineInstance) {
    syncEngineInstance = new SyncEngine();
  }
  return syncEngineInstance;
}

// Export for use in Express
module.exports = { SyncEngine, getSyncEngine };

// CLI mode
if (require.main === module) {
  const engine = new SyncEngine();
  
  // Run single sync
  engine.runSyncCycle().then(() => {
    console.log('Sync completed');
    process.exit(0);
  }).catch(err => {
    console.error('Sync failed:', err);
    process.exit(1);
  });
}