// lib/services/base_api_service.dart import 'dart:convert'; import 'dart:io'; import 'package:async/async.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; import 'package:path/path.dart' as path; import 'package:environment_monitoring_app/auth_provider.dart'; import 'package:environment_monitoring_app/services/server_config_service.dart'; import 'package:environment_monitoring_app/services/retry_service.dart'; import 'package:environment_monitoring_app/services/api_service.dart'; class BaseApiService { final ServerConfigService _serverConfigService = ServerConfigService(); final DatabaseHelper _dbHelper = DatabaseHelper(); // --- ADDED: Instance of DatabaseHelper to get all configs --- Future> _getHeaders() async { final prefs = await SharedPreferences.getInstance(); final String? token = prefs.getString(AuthProvider.tokenKey); return { if (token != null) 'Authorization': 'Bearer $token', }; } Future> _getJsonHeaders() async { final headers = await _getHeaders(); headers['Content-Type'] = 'application/json'; return headers; } // Generic GET request handler (remains unchanged) Future> get(String endpoint) async { try { final baseUrl = await _serverConfigService.getActiveApiUrl(); final url = Uri.parse('$baseUrl/$endpoint'); final response = await http.get(url, headers: await _getJsonHeaders()) .timeout(const Duration(seconds: 60)); return _handleResponse(response); } catch (e) { debugPrint('GET request failed: $e'); return {'success': false, 'message': 'Network error or timeout: $e'}; } } // --- MODIFIED: Generic POST request handler now attempts multiple servers --- Future> post(String endpoint, Map body) async { final configs = await _dbHelper.loadApiConfigs() ?? []; // Get all API configs // --- ADDED: Handle case where local configs are empty --- if (configs.isEmpty) { debugPrint('No local API configs found. Attempting to use default bootstrap URL.'); final baseUrl = await _serverConfigService.getActiveApiUrl(); try { final url = Uri.parse('$baseUrl/$endpoint'); debugPrint('Attempting POST to: $url'); final response = await http.post( url, headers: await _getJsonHeaders(), body: jsonEncode(body), ).timeout(const Duration(seconds: 60)); return _handleResponse(response); } catch (e) { debugPrint('POST to default URL failed. Error: $e'); return {'success': false, 'message': 'API connection failed. Request has been queued for manual retry.'}; } } // If configs exist, try them (up to the two latest) final latestConfigs = configs.take(2).toList(); debugPrint('Debug: Loaded API configs: $latestConfigs'); for (final config in latestConfigs) { debugPrint('Debug: Current config item: $config (Type: ${config.runtimeType})'); // --- REVISED: The null check logic is now more specific --- if (config == null || config['api_url'] == null) { debugPrint('Skipping null or invalid API configuration.'); continue; } try { final baseUrl = config['api_url']; final url = Uri.parse('$baseUrl/$endpoint'); debugPrint('Attempting POST to: $url'); final response = await http.post( url, headers: await _getJsonHeaders(), body: jsonEncode(body), ).timeout(const Duration(seconds: 60)); final result = _handleResponse(response); if (result['success'] == true) { debugPrint('POST to $baseUrl succeeded.'); return result; } else { debugPrint('POST to $baseUrl failed with an API error. Trying next server if available. Error: ${result['message']}'); } } catch (e) { debugPrint('POST to this server failed. Trying next server if available. Error: $e'); } } // If all attempts fail, queue for manual retry final retryService = RetryService(); await retryService.addApiToQueue( endpoint: endpoint, method: 'POST', body: body, ); return {'success': false, 'message': 'All API attempts failed. Request has been queued for manual retry.'}; } // --- MODIFIED: Generic multipart handler now attempts multiple servers --- Future> postMultipart({ required String endpoint, required Map fields, required Map files, }) async { final configs = await _dbHelper.loadApiConfigs() ?? []; // Get all API configs // --- ADDED: Handle case where local configs are empty --- if (configs.isEmpty) { debugPrint('No local API configs found. Attempting to use default bootstrap URL.'); final baseUrl = await _serverConfigService.getActiveApiUrl(); try { final url = Uri.parse('$baseUrl/$endpoint'); debugPrint('Attempting multipart upload to: $url'); var request = http.MultipartRequest('POST', url); final headers = await _getHeaders(); request.headers.addAll(headers); if (fields.isNotEmpty) { request.fields.addAll(fields); } for (var entry in files.entries) { if (await entry.value.exists()) { request.files.add(await http.MultipartFile.fromPath( entry.key, entry.value.path, filename: path.basename(entry.value.path), )); } } var streamedResponse = await request.send().timeout(const Duration(seconds: 60)); final responseBody = await streamedResponse.stream.bytesToString(); return _handleResponse(http.Response(responseBody, streamedResponse.statusCode)); } catch (e) { debugPrint('Multipart upload to default URL failed. Error: $e'); return {'success': false, 'message': 'API connection failed. Upload has been queued for manual retry.'}; } } final latestConfigs = configs.take(2).toList(); // Limit to the two latest configs debugPrint('Debug: Loaded API configs: $latestConfigs'); for (final config in latestConfigs) { debugPrint('Debug: Current config item: $config (Type: ${config.runtimeType})'); // --- REVISED: The null check logic is now more specific --- if (config == null || config['api_url'] == null) { debugPrint('Skipping null or invalid API configuration.'); continue; } try { final baseUrl = config['api_url']; final url = Uri.parse('$baseUrl/$endpoint'); debugPrint('Attempting multipart upload to: $url'); var request = http.MultipartRequest('POST', url); final headers = await _getHeaders(); request.headers.addAll(headers); if (fields.isNotEmpty) { request.fields.addAll(fields); } for (var entry in files.entries) { if (await entry.value.exists()) { // Check if the file exists before adding request.files.add(await http.MultipartFile.fromPath( entry.key, entry.value.path, filename: path.basename(entry.value.path), )); } else { debugPrint('File does not exist: ${entry.value.path}. Skipping this file.'); } } var streamedResponse = await request.send().timeout(const Duration(seconds: 60)); final responseBody = await streamedResponse.stream.bytesToString(); final result = _handleResponse(http.Response(responseBody, streamedResponse.statusCode)); if (result['success'] == true) { debugPrint('Multipart upload to $baseUrl succeeded.'); return result; } else { debugPrint('Multipart upload to $baseUrl failed with an API error. Trying next server if available. Error: ${result['message']}'); } } catch (e, s) { debugPrint('Multipart upload to this server failed. Trying next server if available. Error: $e'); debugPrint('Stack trace: $s'); } } // If all attempts fail, queue for manual retry final retryService = RetryService(); await retryService.addApiToQueue( endpoint: endpoint, method: 'POST_MULTIPART', fields: fields, files: files, ); return {'success': false, 'message': 'All API attempts failed. Upload has been queued for manual retry.'}; } Map _handleResponse(http.Response response) { debugPrint('Handling response. Status: ${response.statusCode}, Body: ${response.body}'); try { final Map responseData = jsonDecode(response.body); if (response.statusCode >= 200 && response.statusCode < 300) { if (responseData['status'] == 'success' || responseData['success'] == true) { return {'success': true, 'data': responseData['data'], 'message': responseData['message']}; } else { return {'success': false, 'message': responseData['message'] ?? 'An unknown API error occurred.'}; } } else { return {'success': false, 'message': responseData['message'] ?? 'Server error: ${response.statusCode}'}; } } catch (e) { debugPrint('Failed to parse server response: $e'); return {'success': false, 'message': 'Failed to parse server response. Body: ${response.body}'}; } } }