// lib/services/retry_service.dart import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:path/path.dart' as p; import 'package:environment_monitoring_app/models/in_situ_sampling_data.dart'; import 'package:environment_monitoring_app/services/marine_in_situ_sampling_service.dart'; import 'package:environment_monitoring_app/models/river_in_situ_sampling_data.dart'; // ADDED import 'package:environment_monitoring_app/services/river_in_situ_sampling_service.dart'; // ADDED import 'package:environment_monitoring_app/services/api_service.dart'; import 'package:environment_monitoring_app/services/base_api_service.dart'; import 'package:environment_monitoring_app/services/ftp_service.dart'; import 'package:environment_monitoring_app/services/server_config_service.dart'; import 'package:environment_monitoring_app/auth_provider.dart'; /// A dedicated service to manage the queue of failed API, FTP, and complex submission tasks. class RetryService { final DatabaseHelper _dbHelper = DatabaseHelper(); final BaseApiService _baseApiService = BaseApiService(); final FtpService _ftpService = FtpService(); final ServerConfigService _serverConfigService = ServerConfigService(); bool _isProcessing = false; // --- START: MODIFICATION FOR HANDLING COMPLEX TASKS --- // These services will be provided after the RetryService is created. MarineInSituSamplingService? _marineInSituService; RiverInSituSamplingService? _riverInSituService; // ADDED AuthProvider? _authProvider; // Call this method from your main app setup to provide the necessary services. void initialize({ required MarineInSituSamplingService marineInSituService, required RiverInSituSamplingService riverInSituService, // ADDED required AuthProvider authProvider, }) { _marineInSituService = marineInSituService; _riverInSituService = riverInSituService; // ADDED _authProvider = authProvider; } // --- END: MODIFICATION FOR HANDLING COMPLEX TASKS --- /// Adds a generic, complex task to the queue, to be handled by a background processor. Future queueTask({ required String type, required Map payload, }) async { await _dbHelper.queueFailedRequest({ 'type': type, 'endpoint_or_path': 'N/A', 'payload': jsonEncode(payload), 'timestamp': DateTime.now().toIso8601String(), 'status': 'pending', }); debugPrint("Task of type '$type' has been queued for background processing."); } /// Adds a failed API request to the local database queue. Future addApiToQueue({ required String endpoint, required String method, Map? body, Map? fields, Map? files, }) async { final serializableFiles = files?.map((key, value) => MapEntry(key, value.path)); final payload = { 'method': method, 'body': body, 'fields': fields, 'files': serializableFiles, }; await _dbHelper.queueFailedRequest({ 'type': 'api', 'endpoint_or_path': endpoint, 'payload': jsonEncode(payload), 'timestamp': DateTime.now().toIso8601String(), 'status': 'pending', }); debugPrint("API request for endpoint '$endpoint' has been queued for retry."); } /// Adds a failed FTP upload to the local database queue. Future addFtpToQueue({ required String localFilePath, required String remotePath, }) async { final payload = {'localFilePath': localFilePath}; await _dbHelper.queueFailedRequest({ 'type': 'ftp', 'endpoint_or_path': remotePath, 'payload': jsonEncode(payload), 'timestamp': DateTime.now().toIso8601String(), 'status': 'pending', }); debugPrint("FTP upload for file '$localFilePath' has been queued for retry."); } /// Retrieves all tasks currently in the 'pending' state from the queue. Future>> getPendingTasks() { return _dbHelper.getPendingRequests(); } /// Processes the entire queue of pending tasks. Future processRetryQueue() async { if (_isProcessing) { debugPrint("[RetryService] ⏳ Queue is already being processed. Skipping."); return; } _isProcessing = true; debugPrint("[RetryService] ▶️ Starting to process main retry queue..."); final pendingTasks = await getPendingTasks(); if (pendingTasks.isEmpty) { debugPrint("[RetryService] ⏹️ Queue is empty. Nothing to process."); _isProcessing = false; return; } if (_authProvider == null || !await _authProvider!.isConnected()) { debugPrint("[RetryService] ❌ No internet connection. Aborting queue processing."); _isProcessing = false; return; } debugPrint("[RetryService] 🔎 Found ${pendingTasks.length} pending tasks."); for (final task in pendingTasks) { await retryTask(task['id'] as int); } debugPrint("[RetryService] ⏹️ Finished processing retry queue."); _isProcessing = false; } /// Attempts to re-execute a single failed task from the queue. /// Returns `true` on success, `false` on failure. Future retryTask(int taskId) async { final task = await _dbHelper.getRequestById(taskId); if (task == null) { debugPrint("Retry failed: Task with ID $taskId not found in the queue."); return false; } bool success = false; final payload = jsonDecode(task['payload'] as String); try { if (_authProvider == null) { debugPrint("RetryService has not been initialized. Cannot process task."); return false; } if (task['type'] == 'insitu_submission') { debugPrint("Retrying complex task 'insitu_submission' with ID $taskId."); if (_marineInSituService == null) return false; final String logFilePath = payload['localLogPath']; final file = File(logFilePath); if (!await file.exists()) { debugPrint("Retry failed: Source log file no longer exists at $logFilePath"); await _dbHelper.deleteRequestFromQueue(taskId); // Remove invalid task return false; } final content = await file.readAsString(); final jsonData = jsonDecode(content) as Map; final InSituSamplingData dataToResubmit = InSituSamplingData.fromJson(jsonData); final String logDirectoryPath = p.dirname(logFilePath); final result = await _marineInSituService!.submitInSituSample( data: dataToResubmit, appSettings: _authProvider!.appSettings, authProvider: _authProvider!, logDirectory: logDirectoryPath, ); success = result['success']; // --- START: ADDED LOGIC FOR RIVER IN-SITU SUBMISSION --- } else if (task['type'] == 'river_insitu_submission') { debugPrint("Retrying complex task 'river_insitu_submission' with ID $taskId."); if (_riverInSituService == null) return false; final String logFilePath = payload['localLogPath']; final file = File(logFilePath); if (!await file.exists()) { debugPrint("Retry failed: Source log file no longer exists at $logFilePath"); await _dbHelper.deleteRequestFromQueue(taskId); // Remove invalid task return false; } final content = await file.readAsString(); final jsonData = jsonDecode(content) as Map; final RiverInSituSamplingData dataToResubmit = RiverInSituSamplingData.fromJson(jsonData); final String logDirectoryPath = p.dirname(logFilePath); final result = await _riverInSituService!.submitData( data: dataToResubmit, appSettings: _authProvider!.appSettings, authProvider: _authProvider!, logDirectory: logDirectoryPath, ); success = result['success']; // --- END: ADDED LOGIC FOR RIVER IN-SITU SUBMISSION --- } else if (task['type'] == 'api') { final endpoint = task['endpoint_or_path'] as String; final method = payload['method'] as String; final baseUrl = await _serverConfigService.getActiveApiUrl(); debugPrint("Retrying API task $taskId: $method to $baseUrl/$endpoint"); Map result; if (method == 'POST_MULTIPART') { final Map fields = Map.from(payload['fields'] ?? {}); final Map files = (payload['files'] as Map?) ?.map((key, value) => MapEntry(key, File(value as String))) ?? {}; result = await _baseApiService.postMultipart(baseUrl: baseUrl, endpoint: endpoint, fields: fields, files: files); } else { final Map body = Map.from(payload['body'] ?? {}); result = await _baseApiService.post(baseUrl, endpoint, body); } success = result['success']; } else if (task['type'] == 'ftp') { final remotePath = task['endpoint_or_path'] as String; final localFile = File(payload['localFilePath'] as String); debugPrint("Retrying FTP task $taskId: Uploading ${localFile.path} to $remotePath"); if (await localFile.exists()) { final ftpConfigs = await _dbHelper.loadFtpConfigs() ?? []; if (ftpConfigs.isEmpty) { debugPrint("Retry failed for FTP task $taskId: No FTP configurations found."); return false; } for (final config in ftpConfigs) { final result = await _ftpService.uploadFile(config: config, fileToUpload: localFile, remotePath: remotePath); if (result['success']) { success = true; break; } } } else { debugPrint("Retry failed for FTP task $taskId: Source file no longer exists at ${localFile.path}"); success = false; } } } catch (e) { debugPrint("A critical error occurred while retrying task $taskId: $e"); success = false; } if (success) { debugPrint("Task $taskId completed successfully. Removing from queue."); await _dbHelper.deleteRequestFromQueue(taskId); } else { debugPrint("Retry attempt for task $taskId failed. It will remain in the queue."); } return success; } }