import 'dart:convert'; import 'dart:io'; import 'package:async/async.dart'; // Used for TimeoutException 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'; // --- ADDED: Import for the service that manages active server configurations --- import 'package:environment_monitoring_app/services/server_config_service.dart'; // --- ADDED: Import for the new service that manages the retry queue --- import 'package:environment_monitoring_app/services/retry_service.dart'; class BaseApiService { // --- ADDED: An instance of the service to get the active URL dynamically --- final ServerConfigService _serverConfigService = ServerConfigService(); // --- REMOVED: This creates an infinite loop with RetryService --- // final RetryService _retryService = RetryService(); // Private helper to construct headers with the auth token Future> _getHeaders() async { final prefs = await SharedPreferences.getInstance(); final String? token = prefs.getString(AuthProvider.tokenKey); // For multipart requests, 'Content-Type' is set by the http client. // We only need Authorization here. return { if (token != null) 'Authorization': 'Bearer $token', }; } // Private helper for JSON headers Future> _getJsonHeaders() async { final headers = await _getHeaders(); headers['Content-Type'] = 'application/json'; return headers; } // Generic GET request handler Future> get(String endpoint) async { try { // --- MODIFIED: Fetches the active base URL before making the request --- 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)); // --- MODIFIED: Added 60 second timeout --- return _handleResponse(response); } catch (e) { debugPrint('GET request failed: $e'); return {'success': false, 'message': 'Network error or timeout: $e'}; } } // Generic POST request handler for JSON data Future> post(String endpoint, Map body) async { try { // --- MODIFIED: Fetches the active base URL before making the request --- final baseUrl = await _serverConfigService.getActiveApiUrl(); final url = Uri.parse('$baseUrl/$endpoint'); final response = await http.post( url, headers: await _getJsonHeaders(), body: jsonEncode(body), ).timeout(const Duration(seconds: 60)); // --- MODIFIED: Added 60 second timeout --- return _handleResponse(response); } catch (e) { // --- MODIFIED: Create a local instance of RetryService to break the circular dependency --- final retryService = RetryService(); debugPrint('POST request to $endpoint failed, queueing for retry. Error: $e'); await retryService.addApiToQueue( endpoint: endpoint, method: 'POST', body: body, ); return {'success': false, 'message': 'Request failed and has been queued for manual retry.'}; } } // Generic handler for multipart (file upload) requests Future> postMultipart({ required String endpoint, required Map fields, required Map files, }) async { try { // --- MODIFIED: Fetches the active base URL before making the request --- final baseUrl = await _serverConfigService.getActiveApiUrl(); final url = Uri.parse('$baseUrl/$endpoint'); debugPrint('Starting multipart upload to: $url'); var request = http.MultipartRequest('POST', url); // Get and add headers (Authorization token) final headers = await _getHeaders(); request.headers.addAll(headers); debugPrint('Headers added to multipart request.'); // --- CORRECTED --- // This adds each field directly to the request body, which is the standard // for multipart/form-data and matches what the PHP backend expects. if (fields.isNotEmpty) { request.fields.addAll(fields); debugPrint('Fields added directly to multipart request: $fields'); } // Add files for (var entry in files.entries) { debugPrint('Adding file: ${entry.key}, path: ${entry.value.path}'); request.files.add(await http.MultipartFile.fromPath( entry.key, entry.value.path, filename: path.basename(entry.value.path), )); } debugPrint('${files.length} files added to the request.'); debugPrint('Sending multipart request...'); // --- MODIFIED: Added 60 second timeout --- var streamedResponse = await request.send().timeout(const Duration(seconds: 60)); debugPrint('Received response with status code: ${streamedResponse.statusCode}'); final responseBody = await streamedResponse.stream.bytesToString(); // We create a standard http.Response to use our standard handler return _handleResponse(http.Response(responseBody, streamedResponse.statusCode)); } catch (e, s) { // Catching both Exception and Error (e.g., OutOfMemoryError) // --- MODIFIED: Create a local instance of RetryService to break the circular dependency --- final retryService = RetryService(); debugPrint('Multipart request to $endpoint failed, queueing for retry. Error: $e'); debugPrint('Stack trace: $s'); await retryService.addApiToQueue( endpoint: endpoint, method: 'POST_MULTIPART', fields: fields, files: files, ); return {'success': false, 'message': 'Upload failed and has been queued for manual retry.'}; } } // Centralized response handling 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) { // Assuming the API returns a 'status' field in the JSON body 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 { // Handle server errors (4xx, 5xx) return {'success': false, 'message': responseData['message'] ?? 'Server error: ${response.statusCode}'}; } } catch (e) { // Handle cases where the response body is not valid JSON debugPrint('Failed to parse server response: $e'); return {'success': false, 'message': 'Failed to parse server response. Body: ${response.body}'}; } } }