// lib/services/submission_ftp_service.dart import 'dart:io'; import 'package:flutter/foundation.dart'; import 'dart:convert'; // Added for jsonEncode import 'package:path/path.dart' as p; // Added for basename import 'package:environment_monitoring_app/services/user_preferences_service.dart'; import 'package:environment_monitoring_app/services/ftp_service.dart'; import 'package:environment_monitoring_app/services/retry_service.dart'; // Import necessary services and models if needed for queueFtpTasksForSkippedAttempt import 'package:environment_monitoring_app/services/zipping_service.dart'; import 'package:environment_monitoring_app/services/api_service.dart'; // For DatabaseHelper /// A generic, reusable service for handling the FTP submission process. /// It respects user preferences for enabled destinations for any given module. class SubmissionFtpService { final UserPreferencesService _userPreferencesService = UserPreferencesService(); final FtpService _ftpService = FtpService(); final RetryService _retryService = RetryService(); // Add ZippingService and DatabaseHelper if queueFtpTasksForSkippedAttempt needs them final ZippingService _zippingService = ZippingService(); final DatabaseHelper _dbHelper = DatabaseHelper(); /// Submits a file to all enabled FTP destinations for a given module. /// /// This method works differently from the API service. It attempts to upload /// to ALL enabled destinations. It returns a summary of success/failure for each. /// If any upload fails, it is queued for individual retry. /// The overall result is considered successful if there are no hard errors /// during the process, even if some uploads are queued. Future> submit({ required String moduleName, required File fileToUpload, required String remotePath, }) async { final destinations = await _userPreferencesService.getEnabledFtpConfigsForModule(moduleName); if (destinations.isEmpty) { debugPrint("SubmissionFtpService: No enabled FTP destinations for module '$moduleName'. Skipping."); // Return success with a specific status indicating no config return { 'success': true, // Process succeeded because there was nothing to do 'message': 'No FTP destinations enabled for this module.', 'statuses': [{'status': 'Not Configured', 'message': 'No destinations enabled.', 'success': true}] }; } final List> statuses = []; bool allSucceededOrNotConfigured = true; // Track if all attempts either succeeded or weren't configured for (final dest in destinations) { final configName = dest['config_name'] as String? ?? 'Unknown FTP'; final int? configId = dest['ftp_config_id'] as int?; // Get the config ID // Skip if config ID is missing (should not happen with DB data) if (configId == null) { debugPrint("SubmissionFtpService: Skipping destination '$configName' due to missing config ID."); statuses.add({ 'config_name': configName, 'status': 'Error', 'success': false, 'message': 'Configuration ID missing.', }); allSucceededOrNotConfigured = false; continue; } debugPrint("SubmissionFtpService: Attempting to upload to '$configName' (ID: $configId)"); final result = await _ftpService.uploadFile( config: dest, fileToUpload: fileToUpload, remotePath: remotePath, ); statuses.add({ 'config_name': configName, 'ftp_config_id': configId, // Include ID in status 'success': result['success'], 'message': result['message'], 'status': result['success'] ? 'Success' : 'Failed', // Add status text }); if (result['success'] != true) { allSucceededOrNotConfigured = false; // If an individual upload fails, queue it for manual retry. debugPrint("SubmissionFtpService: Upload to '$configName' (ID: $configId) failed. Queuing for retry."); // --- START FIX: Add ftpConfigId --- await _retryService.addFtpToQueue( localFilePath: fileToUpload.path, remotePath: remotePath, ftpConfigId: configId, // Pass the specific config ID ); // --- END FIX --- } } if (allSucceededOrNotConfigured) { return { 'success': true, 'message': 'File successfully uploaded to all enabled FTP destinations.', 'statuses': statuses, }; } else { return { 'success': true, // The process itself succeeded (attempted all), even if some uploads were queued. 'message': 'One or more FTP uploads failed and have been queued for retry.', 'statuses': statuses, }; } } /// Manually queues FTP tasks when the initial FTP attempt is skipped (e.g., due to session expiry). Future queueFtpTasksForSkippedAttempt({ required String moduleName, required Map dataJson, // The data model converted to JSON (toDbJson) required Map imageFiles, required String baseFileName, // Base name for zip files }) async { debugPrint("Manually queuing FTP tasks for skipped attempt (Module: $moduleName)."); final ftpConfigs = await _dbHelper.loadFtpConfigs() ?? []; if (ftpConfigs.isEmpty) { debugPrint("Cannot queue skipped FTP tasks: No FTP configurations found."); return; } // 1. Create Data ZIP (in temp directory) final dataZip = await _zippingService.createDataZip( // Adapt jsonDataMap based on module if needed, using dataJson jsonDataMap: {'db.json': jsonEncode(dataJson)}, // Default, adjust per module if needed baseFileName: baseFileName, destinationDir: null, // Save to temp dir ); // 2. Create Image ZIP (in temp directory) File? imageZip; if (imageFiles.isNotEmpty) { imageZip = await _zippingService.createImageZip( imageFiles: imageFiles.values.toList(), baseFileName: baseFileName, destinationDir: null, // Save to temp dir ); } // 3. Queue uploads for each config for (final config in ftpConfigs) { final configId = config['ftp_config_id']; if (configId != null) { // Queue data zip upload if (dataZip != null) { await _retryService.addFtpToQueue( localFilePath: dataZip.path, remotePath: '/${p.basename(dataZip.path)}', ftpConfigId: configId ); debugPrint("Queued skipped data ZIP upload for FTP config ID $configId"); } // Queue image zip upload if (imageZip != null) { await _retryService.addFtpToQueue( localFilePath: imageZip.path, remotePath: '/${p.basename(imageZip.path)}', ftpConfigId: configId ); debugPrint("Queued skipped image ZIP upload for FTP config ID $configId"); } } } // Temporary ZIP files will be cleaned up by OS eventually, or handled by retry logic upon success/failure. } }