import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as path; import 'package:image/image.dart' as img; import 'package:intl/intl.dart'; import '../models/air_installation_data.dart'; import '../models/air_collection_data.dart'; import 'api_service.dart'; import 'local_storage_service.dart'; /// A dedicated service to handle all business logic for the Air Manual Sampling feature. class AirSamplingService { final ApiService _apiService = ApiService(); final LocalStorageService _localStorageService = LocalStorageService(); /// Picks an image from the specified source, adds a timestamp watermark, /// and saves it to a temporary directory with a standardized name. Future pickAndProcessImage( ImageSource source, { required String stationCode, required String imageInfo, String processType = 'INSTALL', // Defaults to INSTALL for backward compatibility }) async { final picker = ImagePicker(); final XFile? photo = await picker.pickImage( source: source, imageQuality: 85, maxWidth: 1024); if (photo == null) return null; final bytes = await photo.readAsBytes(); img.Image? originalImage = img.decodeImage(bytes); if (originalImage == null) return null; final String watermarkTimestamp = DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now()); final font = img.arial24; final textWidth = watermarkTimestamp.length * 12; img.fillRect(originalImage, x1: 5, y1: 5, x2: textWidth + 15, y2: 35, color: img.ColorRgb8(255, 255, 255)); img.drawString(originalImage, watermarkTimestamp, font: font, x: 10, y: 10, color: img.ColorRgb8(0, 0, 0)); final tempDir = await getTemporaryDirectory(); final fileTimestamp = watermarkTimestamp.replaceAll(':', '-').replaceAll(' ', '_'); final newFileName = "${stationCode}_${fileTimestamp}_${processType.toUpperCase()}_${imageInfo.replaceAll(' ', '')}.jpg"; final filePath = path.join(tempDir.path, newFileName); return File(filePath)..writeAsBytesSync(img.encodeJpg(originalImage)); } /// Orchestrates a two-step submission process for air installation samples. Future> submitInstallation(AirInstallationData data) async { // --- OFFLINE-FIRST HELPER --- Future> saveLocally() async { debugPrint("Saving installation locally..."); data.status = 'L1'; // Mark as Locally Saved, Pending Submission await _localStorageService.saveAirSamplingRecord(data.toMap(), data.refID!); return { 'status': 'L1', 'message': 'No connection or server error. Installation data saved locally.', }; } // --- STEP 1: SUBMIT TEXT DATA --- debugPrint("Step 1: Submitting installation text data..."); final textDataResult = await _apiService.post('air/manual/installation', data.toJsonForApi()); if (textDataResult['success'] != true) { debugPrint("Failed to submit text data. Reason: ${textDataResult['message']}"); return await saveLocally(); } // --- NECESSARY FIX: Safely parse the record ID from the server response --- final dynamic recordIdFromServer = textDataResult['data']?['air_man_id']; if (recordIdFromServer == null) { debugPrint("Text data submitted, but did not receive a record ID."); return await saveLocally(); } debugPrint("Text data submitted successfully. Received record ID: $recordIdFromServer"); // The ID from JSON can be a String or int, but our model needs an int. // Use int.tryParse for safe conversion. final int? parsedRecordId = int.tryParse(recordIdFromServer.toString()); if (parsedRecordId == null) { debugPrint("Could not parse the received record ID: $recordIdFromServer"); return await saveLocally(); // Treat as a failure if ID is invalid } data.airManId = parsedRecordId; // Assign the correctly typed integer ID // --- STEP 2: UPLOAD IMAGE FILES --- final filesToUpload = data.getImagesForUpload(); if (filesToUpload.isEmpty) { debugPrint("No images to upload. Submission complete."); data.status = 'S1'; // Server Pending (no images needed) await _localStorageService.saveAirSamplingRecord(data.toMap(), data.refID!); return {'status': 'S1', 'message': 'Installation data submitted successfully.'}; } debugPrint("Step 2: Uploading ${filesToUpload.length} images for record ID $parsedRecordId..."); final imageUploadResult = await _apiService.air.uploadInstallationImages( airManId: parsedRecordId.toString(), // The API itself needs a string files: filesToUpload, ); if (imageUploadResult['success'] != true) { debugPrint("Image upload failed. Reason: ${imageUploadResult['message']}"); return await saveLocally(); } debugPrint("Images uploaded successfully."); data.status = 'S2'; // Server Pending (images uploaded) await _localStorageService.saveAirSamplingRecord(data.toMap(), data.refID!); return { 'status': 'S2', 'message': 'Installation data and images submitted successfully.', }; } /// Submits only the collection data, linked to a previous installation. Future> submitCollection(AirCollectionData data) async { // --- OFFLINE-FIRST HELPER (CORRECTED) --- Future> updateAndSaveLocally(String newStatus, {String? message}) async { debugPrint("Saving collection data locally with status: $newStatus"); final allLogs = await _localStorageService.getAllAirSamplingLogs(); final logIndex = allLogs.indexWhere((log) => log['refID'] == data.installationRefID); if (logIndex != -1) { final installationLog = allLogs[logIndex]; // FIX: Nest collection data to prevent overwriting installation fields. installationLog['collectionData'] = data.toMap(); installationLog['status'] = newStatus; // Update the overall status await _localStorageService.saveAirSamplingRecord(installationLog, data.installationRefID!); } return { 'status': newStatus, 'message': message ?? 'No connection or server error. Data saved locally.', }; } // --- STEP 1: SUBMIT TEXT DATA --- debugPrint("Step 1: Submitting collection text data..."); final textDataResult = await _apiService.post('air/manual/collection', data.toJson()); if (textDataResult['success'] != true) { debugPrint("Failed to submit collection text data. Reason: ${textDataResult['message']}"); return await updateAndSaveLocally('L3', message: 'No connection or server error. Collection data saved locally.'); } debugPrint("Collection text data submitted successfully."); // --- STEP 2: UPLOAD IMAGE FILES --- final filesToUpload = data.getImagesForUpload(); if (filesToUpload.isEmpty) { debugPrint("No collection images to upload. Submission complete."); await updateAndSaveLocally('S3'); // S3 = Server Completed return {'status': 'S3', 'message': 'Collection data submitted successfully.'}; } debugPrint("Step 2: Uploading ${filesToUpload.length} collection images..."); final imageUploadResult = await _apiService.air.uploadCollectionImages( airManId: data.airManId.toString(), files: filesToUpload, ); if (imageUploadResult['success'] != true) { debugPrint("Image upload failed. Reason: ${imageUploadResult['message']}"); // Use a new status 'L4' to indicate text submitted but images failed return await updateAndSaveLocally('L4', message: 'Data submitted, but image upload failed. Saved locally for retry.'); } debugPrint("Images uploaded successfully."); await updateAndSaveLocally('S3'); // S3 = Server Completed return { 'status': 'S3', 'message': 'Collection data and images submitted successfully.', }; } /// Fetches installations that are pending collection from local storage. Future> getPendingInstallations() async { debugPrint("Fetching pending installations from local storage..."); final logs = await _localStorageService.getAllAirSamplingLogs(); final pendingInstallations = logs .where((log) { final status = log['status']; // --- CORRECTED --- // Only show installations that have been synced to the server (S1, S2). // 'L1' (Local only) records cannot be collected until they are synced. return status == 'S1' || status == 'S2'; }) .map((log) => AirInstallationData.fromJson(log)) .toList(); return pendingInstallations; } void dispose() { // Clean up any resources if necessary } }