/**
 * Synchronization Service
 * Handles bidirectional sync between local IndexedDB and remote API
 */

import storageService from './storage.js';
import api from './api.js';

class SyncService {
  constructor() {
    this.isSyncing = false;
    this.syncListeners = [];
    this.lastSyncTimestamp = null;
    this.retryAttempts = new Map(); // Track retry attempts per operation
    this.maxRetries = 3;
    this.retryDelay = 1000; // Base delay in ms
    this.batchSize = 50; // Records per batch
  }

  /**
   * Add listener for sync events
   */
  addSyncListener(listener) {
    this.syncListeners.push(listener);
  }

  /**
   * Remove sync listener
   */
  removeSyncListener(listener) {
    this.syncListeners = this.syncListeners.filter(l => l !== listener);
  }

  /**
   * Notify all sync listeners
   */
  notifyListeners(event, data) {
    this.syncListeners.forEach(listener => {
      try {
        listener(event, data);
      } catch (error) {
        console.error('Error in sync listener:', error);
      }
    });
  }

  /**
   * Check if currently syncing
   */
  getIsSyncing() {
    return this.isSyncing;
  }

  /**
   * Full bidirectional synchronization
   */
  async performFullSync() {
    if (this.isSyncing) {
      throw new Error('Sync already in progress');
    }

    this.isSyncing = true;
    this.notifyListeners('sync_started', { timestamp: Date.now() });

    try {
      // Step 1: Download fresh data from server
      await this.downloadFromServer();

      // Step 2: Upload pending local changes
      await this.uploadToServer();

      // Step 3: Process sync queue
      await this.processSyncQueue();

      this.lastSyncTimestamp = Date.now();
      this.notifyListeners('sync_completed', { 
        timestamp: this.lastSyncTimestamp,
        success: true 
      });

      return { success: true, timestamp: this.lastSyncTimestamp };

    } catch (error) {
      console.error('Sync failed:', error);
      this.notifyListeners('sync_failed', { 
        error: error.message,
        timestamp: Date.now() 
      });
      throw error;
    } finally {
      this.isSyncing = false;
    }
  }

  /**
   * Download data from server to local storage
   */
  async downloadFromServer() {
    this.notifyListeners('sync_progress', { 
      stage: 'download', 
      message: 'Descargando datos del servidor...' 
    });

    try {
      // Download species data
      const species = await api.get('/species');
      if (species && species.length > 0) {
        await storageService.saveSpeciesBulk(species);
      }

      // Download projects
      const projects = await api.get('/projects');
      if (projects && projects.length > 0) {
        for (const project of projects) {
          project.syncStatus = 'synced';
          await storageService.saveProject(project);
        }
      }

      // Download rodales for each project
      for (const project of projects || []) {
        const rodales = await api.get(`/projects/${project.id}/rodales`);
        if (rodales && rodales.length > 0) {
          for (const rodal of rodales) {
            rodal.syncStatus = 'synced';
            await storageService.saveRodal(rodal);
          }
        }
      }

      this.notifyListeners('sync_progress', { 
        stage: 'download', 
        message: 'Descarga completada' 
      });

    } catch (error) {
      console.error('Download failed:', error);
      throw new Error(`Error descargando datos: ${error.message}`);
    }
  }

  /**
   * Upload pending local changes to server
   */
  async uploadToServer() {
    this.notifyListeners('sync_progress', { 
      stage: 'upload', 
      message: 'Subiendo cambios locales...' 
    });

    try {
      // Upload pending field records
      await this.uploadFieldRecords();

      // Upload pending projects
      await this.uploadProjects();

      // Upload pending rodales
      await this.uploadRodales();

      this.notifyListeners('sync_progress', { 
        stage: 'upload', 
        message: 'Subida completada' 
      });

    } catch (error) {
      console.error('Upload failed:', error);
      throw new Error(`Error subiendo datos: ${error.message}`);
    }
  }

  /**
   * Upload pending field records with batch processing and retry logic
   */
  async uploadFieldRecords() {
    const pendingRecords = await storageService.getPendingSyncRecords();
    
    if (pendingRecords.length === 0) {
      return { success: true, uploaded: 0, failed: 0 };
    }

    this.notifyListeners('sync_progress', { 
      stage: 'upload_field_records', 
      message: `Validando ${pendingRecords.length} registros antes de subir...`,
      total: pendingRecords.length,
      current: 0
    });

    // Validate data integrity before sync
    const { validateSyncDataIntegrity } = await import('../utils/validation.js');
    const integrityValidation = validateSyncDataIntegrity(pendingRecords);
    
    if (!integrityValidation.isValid) {
      this.notifyListeners('sync_validation_failed', {
        stage: 'data_integrity_check',
        message: 'Validación de integridad de datos falló',
        validationResult: integrityValidation
      });
      
      throw new Error(`Validación de datos falló: ${integrityValidation.errors.join(', ')}`);
    }

    // Use only valid records for sync
    const validRecords = integrityValidation.validRecords;
    
    if (integrityValidation.invalidRecords.length > 0) {
      this.notifyListeners('sync_progress', { 
        stage: 'upload_field_records', 
        message: `${integrityValidation.invalidRecords.length} registros inválidos excluidos de la sincronización`,
        total: pendingRecords.length,
        current: 0
      });
    }

    this.notifyListeners('sync_progress', { 
      stage: 'upload_field_records', 
      message: `Preparando ${validRecords.length} registros válidos para subir...`,
      total: validRecords.length,
      current: 0
    });

    // Group records by rodal and split into batches
    const recordsByRodal = this.groupRecordsByRodal(validRecords);
    const batches = this.createBatches(recordsByRodal);
    
    let uploadedCount = 0;
    let failedCount = 0;
    const failedBatches = [];

    // Process batches sequentially to avoid overwhelming the server
    for (let i = 0; i < batches.length; i++) {
      const batch = batches[i];
      const batchKey = `${batch.rodalId}_${i}`;
      
      try {
        this.notifyListeners('sync_progress', { 
          stage: 'upload_field_records', 
          message: `Validando lote ${i + 1}/${batches.length}...`,
          total: validRecords.length,
          current: uploadedCount
        });

        // Validate batch consistency
        const { validateBatchConsistency } = await import('../utils/validation.js');
        const batchValidation = validateBatchConsistency(batch);
        
        if (!batchValidation.isValid) {
          console.warn(`Batch ${i + 1} validation failed:`, batchValidation.errors);
          failedCount += batch.records.length;
          failedBatches.push({ 
            batch, 
            error: `Validación de lote falló: ${batchValidation.errors.join(', ')}` 
          });
          continue;
        }

        // Log batch warnings if any
        if (batchValidation.warnings.length > 0) {
          console.warn(`Batch ${i + 1} warnings:`, batchValidation.warnings);
          this.notifyListeners('sync_progress', { 
            stage: 'upload_field_records', 
            message: `Lote ${i + 1} tiene advertencias: ${batchValidation.warnings[0]}`,
            total: validRecords.length,
            current: uploadedCount
          });
        }

        this.notifyListeners('sync_progress', { 
          stage: 'upload_field_records', 
          message: `Subiendo lote ${i + 1}/${batches.length} (${batch.records.length} registros)...`,
          total: validRecords.length,
          current: uploadedCount
        });

        const result = await this.uploadBatchWithRetry(batch, batchKey);
        
        if (result.success) {
          uploadedCount += result.uploaded;
          
          // Update sync status for successfully uploaded records
          const recordIds = batch.records.map(r => r.id);
          await storageService.updateSyncStatus(recordIds, 'Sincronizado');
          
          // Clear retry attempts for this batch
          this.retryAttempts.delete(batchKey);
          
          // Trigger calculation refresh for the rodal
          this.notifyListeners('calculations_refresh_needed', {
            rodalId: batch.rodalId,
            recordCount: result.uploaded
          });
        } else {
          failedCount += batch.records.length;
          failedBatches.push({ batch, error: result.error });
        }

      } catch (error) {
        console.error(`Error uploading batch ${i + 1}:`, error);
        failedCount += batch.records.length;
        failedBatches.push({ batch, error: error.message });
      }

      // Small delay between batches to prevent server overload
      if (i < batches.length - 1) {
        await this.delay(200);
      }
    }

    // Final progress update
    this.notifyListeners('sync_progress', { 
      stage: 'upload_field_records', 
      message: `Completado: ${uploadedCount} subidos, ${failedCount} fallidos`,
      total: validRecords.length,
      current: uploadedCount
    });

    // Log failed batches for debugging
    if (failedBatches.length > 0) {
      console.warn(`Failed to upload ${failedBatches.length} batches:`, failedBatches);
    }

    return {
      success: failedCount === 0,
      uploaded: uploadedCount,
      failed: failedCount,
      failedBatches: failedBatches.length
    };
  }

  /**
   * Group records by rodal ID
   */
  groupRecordsByRodal(records) {
    return records.reduce((acc, record) => {
      if (!acc[record.rodalId]) {
        acc[record.rodalId] = [];
      }
      acc[record.rodalId].push(record);
      return acc;
    }, {});
  }

  /**
   * Create batches from grouped records
   */
  createBatches(recordsByRodal) {
    const batches = [];
    
    for (const [rodalId, records] of Object.entries(recordsByRodal)) {
      // Split records into batches of specified size
      for (let i = 0; i < records.length; i += this.batchSize) {
        const batchRecords = records.slice(i, i + this.batchSize);
        batches.push({
          rodalId: parseInt(rodalId),
          records: batchRecords,
          batchIndex: Math.floor(i / this.batchSize)
        });
      }
    }
    
    return batches;
  }

  /**
   * Upload a batch with retry logic
   */
  async uploadBatchWithRetry(batch, batchKey) {
    const maxAttempts = this.maxRetries + 1;
    let lastError = null;

    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        // Track retry attempts
        if (attempt > 1) {
          const currentAttempts = this.retryAttempts.get(batchKey) || 0;
          this.retryAttempts.set(batchKey, currentAttempts + 1);
          
          // Exponential backoff delay
          const delay = this.retryDelay * Math.pow(2, attempt - 2);
          await this.delay(delay);
          
          console.log(`Retrying batch ${batchKey}, attempt ${attempt}/${maxAttempts}`);
        }

        // Prepare records for API
        const apiRecords = batch.records.map(record => ({
          rodal_id: record.rodalId,
          numero_parcela: record.numeroParcela,
          fecha_captura: record.fechaCaptura,
          latitud: record.latitud,
          longitud: record.longitud,
          especie_id: record.especieId,
          dap_cm: record.dapCm,
          altura_m: record.alturaM,
          condicion_arbol: record.condicionArbol,
          usuario_id: record.usuarioId
        }));

        // Upload batch to API
        const response = await api.post('/field-data/sync', {
          records: apiRecords,
          batch_info: {
            rodal_id: batch.rodalId,
            batch_index: batch.batchIndex,
            total_records: batch.records.length
          }
        });

        if (response && response.success) {
          return {
            success: true,
            uploaded: batch.records.length,
            response: response
          };
        } else {
          throw new Error(response?.message || 'API response indicates failure');
        }

      } catch (error) {
        lastError = error;
        console.error(`Batch upload attempt ${attempt} failed:`, error);
        
        // Don't retry on certain types of errors
        if (this.isNonRetryableError(error)) {
          break;
        }
      }
    }

    return {
      success: false,
      uploaded: 0,
      error: lastError?.message || 'Unknown error'
    };
  }

  /**
   * Check if error should not be retried
   */
  isNonRetryableError(error) {
    const nonRetryablePatterns = [
      /400/i, // Bad Request
      /401/i, // Unauthorized
      /403/i, // Forbidden
      /404/i, // Not Found
      /422/i  // Unprocessable Entity
    ];

    const errorMessage = error.message || error.toString();
    return nonRetryablePatterns.some(pattern => pattern.test(errorMessage));
  }

  /**
   * Utility function for delays
   */
  delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  /**
   * Upload pending projects
   */
  async uploadProjects() {
    const projects = await storageService.getProjects();
    const pendingProjects = projects.filter(p => p.syncStatus === 'pending');

    for (const project of pendingProjects) {
      try {
        const apiProject = {
          nombre_proyecto: project.nombreProyecto,
          nombre_finca: project.nombreFinca,
          departamento: project.departamento,
          municipio: project.municipio,
          aldea: project.aldea,
          nombre_propietario: project.nombrePropietario,
          nombre_regente_forestal: project.nombreRegenteForestal
        };

        let response;
        if (typeof project.id === 'string' && project.id.startsWith('temp_')) {
          // New project - create
          response = await api.post('/projects', apiProject);
          if (response.id) {
            // Update local project with server ID
            project.id = response.id;
            project.syncStatus = 'synced';
            await storageService.saveProject(project);
          }
        } else {
          // Existing project - update
          response = await api.put(`/projects/${project.id}`, apiProject);
          if (response.success) {
            project.syncStatus = 'synced';
            await storageService.saveProject(project);
          }
        }

      } catch (error) {
        console.error(`Error uploading project ${project.id}:`, error);
      }
    }
  }

  /**
   * Upload pending rodales
   */
  async uploadRodales() {
    const projects = await storageService.getProjects();
    
    for (const project of projects) {
      const rodales = await storageService.getRodalesByProject(project.id);
      const pendingRodales = rodales.filter(r => r.syncStatus === 'pending');

      for (const rodal of pendingRodales) {
        try {
          const apiRodal = {
            nombre_rodal: rodal.nombreRodal,
            area_ha: rodal.areaHa
          };

          let response;
          if (typeof rodal.id === 'string' && rodal.id.startsWith('temp_')) {
            // New rodal - create
            response = await api.post(`/projects/${project.id}/rodales`, apiRodal);
            if (response.id) {
              // Update local rodal with server ID
              rodal.id = response.id;
              rodal.syncStatus = 'synced';
              await storageService.saveRodal(rodal);
            }
          } else {
            // Existing rodal - update
            response = await api.put(`/rodales/${rodal.id}`, apiRodal);
            if (response.success) {
              rodal.syncStatus = 'synced';
              await storageService.saveRodal(rodal);
            }
          }

        } catch (error) {
          console.error(`Error uploading rodal ${rodal.id}:`, error);
        }
      }
    }
  }

  /**
   * Process sync queue operations
   */
  async processSyncQueue() {
    const pendingOperations = await storageService.getPendingSyncOperations();
    
    for (const operation of pendingOperations) {
      try {
        await this.processSyncOperation(operation);
        await storageService.removeSyncOperation(operation.id);
      } catch (error) {
        console.error(`Error processing sync operation ${operation.id}:`, error);
        // Keep operation in queue for retry
      }
    }
  }

  /**
   * Process individual sync operation
   */
  async processSyncOperation(operation) {
    const { operation: opType, entity, data } = operation;

    switch (entity) {
      case 'project':
        if (opType === 'delete') {
          await api.delete(`/projects/${data.id}`);
        }
        break;

      case 'rodal':
        if (opType === 'delete') {
          await api.delete(`/rodales/${data.id}`);
        }
        break;

      case 'fieldRecord':
        if (opType === 'delete') {
          await api.delete(`/field-data/${data.id}`);
        }
        break;

      default:
        console.warn(`Unknown sync operation: ${entity}/${opType}`);
    }
  }

  /**
   * Quick sync for field records only
   */
  async syncFieldRecords() {
    if (this.isSyncing) {
      throw new Error('Sync already in progress');
    }

    this.isSyncing = true;
    this.notifyListeners('sync_started', { 
      type: 'field_records',
      timestamp: Date.now() 
    });

    try {
      await this.uploadFieldRecords();
      
      this.notifyListeners('sync_completed', { 
        type: 'field_records',
        timestamp: Date.now(),
        success: true 
      });

      return { success: true };

    } catch (error) {
      this.notifyListeners('sync_failed', { 
        type: 'field_records',
        error: error.message,
        timestamp: Date.now() 
      });
      throw error;
    } finally {
      this.isSyncing = false;
    }
  }

  /**
   * Get sync status information
   */
  async getSyncStatus() {
    const pendingRecords = await storageService.getPendingSyncRecords();
    const pendingOperations = await storageService.getPendingSyncOperations();
    const stats = await storageService.getStorageStats();

    return {
      lastSync: this.lastSyncTimestamp,
      isSyncing: this.isSyncing,
      pendingRecords: pendingRecords.length,
      pendingOperations: pendingOperations.length,
      storageStats: stats,
      isOnline: navigator.onLine
    };
  }

  /**
   * Force sync when connection is restored
   */
  async handleConnectionRestored() {
    if (!navigator.onLine) {
      return;
    }

    try {
      // Wait a bit for connection to stabilize
      await new Promise(resolve => setTimeout(resolve, 1000));
      
      // Perform quick field records sync
      await this.syncFieldRecords();
    } catch (error) {
      console.error('Auto-sync on connection restored failed:', error);
    }
  }

  /**
   * Mark specific records as synchronized
   */
  async markRecordsAsSynced(recordIds) {
    try {
      await storageService.updateSyncStatus(recordIds, 'Sincronizado');
      
      // Notify listeners of sync status change
      this.notifyListeners('sync_progress', {
        stage: 'mark_synced',
        message: `Marcados ${recordIds.length} registros como sincronizados`,
        current: recordIds.length,
        total: recordIds.length
      });

      return { success: true, count: recordIds.length };
    } catch (error) {
      console.error('Error marking records as synced:', error);
      throw error;
    }
  }

  /**
   * Get detailed sync statistics
   */
  async getSyncStatistics() {
    try {
      const [pendingRecords, pendingOperations, storageStats] = await Promise.all([
        storageService.getPendingSyncRecords(),
        storageService.getPendingSyncOperations(),
        storageService.getStorageStats()
      ]);

      // Group pending records by rodal for better insights
      const recordsByRodal = pendingRecords.reduce((acc, record) => {
        if (!acc[record.rodalId]) {
          acc[record.rodalId] = [];
        }
        acc[record.rodalId].push(record);
        return acc;
      }, {});

      return {
        pendingRecords: pendingRecords.length,
        pendingOperations: pendingOperations.length,
        recordsByRodal: Object.keys(recordsByRodal).length,
        storageStats,
        lastSync: this.lastSyncTimestamp,
        isSyncing: this.isSyncing,
        isOnline: navigator.onLine,
        retryAttempts: this.retryAttempts.size
      };
    } catch (error) {
      console.error('Error getting sync statistics:', error);
      return {
        pendingRecords: 0,
        pendingOperations: 0,
        recordsByRodal: 0,
        storageStats: {},
        lastSync: this.lastSyncTimestamp,
        isSyncing: this.isSyncing,
        isOnline: navigator.onLine,
        retryAttempts: 0
      };
    }
  }

  /**
   * Clear all retry attempts (useful for manual retry)
   */
  clearRetryAttempts() {
    this.retryAttempts.clear();
  }

  /**
   * Get retry status for debugging
   */
  getRetryStatus() {
    const retryInfo = {};
    for (const [key, attempts] of this.retryAttempts.entries()) {
      retryInfo[key] = {
        attempts,
        maxRetries: this.maxRetries,
        canRetry: attempts < this.maxRetries
      };
    }
    return retryInfo;
  }
}

// Create singleton instance
const syncService = new SyncService();

// Auto-sync when connection is restored
window.addEventListener('online', () => {
  syncService.handleConnectionRestored();
});

export default syncService;