// lib/services/air_sampling_service.dart

import 'dart:io';
import 'dart:async'; // Added for TimeoutException
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 'dart:convert';

import '../models/air_installation_data.dart';
import '../models/air_collection_data.dart';
import 'api_service.dart';
import 'package:environment_monitoring_app/services/database_helper.dart';
import 'local_storage_service.dart';
import 'telegram_service.dart';
import 'server_config_service.dart';
import 'zipping_service.dart';
import 'submission_api_service.dart';
import 'submission_ftp_service.dart';
import 'retry_service.dart'; // Added for queuing failed tasks

/// A dedicated service for handling all business logic for the Air Manual Sampling feature.
class AirSamplingService {
  final DatabaseHelper _dbHelper;
  final TelegramService _telegramService;
  final SubmissionApiService _submissionApiService = SubmissionApiService();
  final SubmissionFtpService _submissionFtpService = SubmissionFtpService();
  final ServerConfigService _serverConfigService = ServerConfigService();
  final ZippingService _zippingService = ZippingService();
  final LocalStorageService _localStorageService = LocalStorageService();
  final RetryService _retryService = RetryService(); // Added

  AirSamplingService(this._dbHelper, this._telegramService);

  // This helper method remains unchanged as it's for local saving logic
  Map<String, dynamic> _toMapForLocalSave(dynamic data) {
    if (data is AirInstallationData) {
      final map = data.toMap();
      map['imageFront'] = data.imageFront;
      map['imageBack'] = data.imageBack;
      map['imageLeft'] = data.imageLeft;
      map['imageRight'] = data.imageRight;
      map['optionalImage1'] = data.optionalImage1;
      map['optionalImage2'] = data.optionalImage2;
      map['optionalImage3'] = data.optionalImage3;
      map['optionalImage4'] = data.optionalImage4;

      if (data.collectionData != null) {
        map['collectionData'] = _toMapForLocalSave(data.collectionData);
      }
      return map;
    } else if (data is AirCollectionData) {
      final map = data.toMap();
      map['imageFront'] = data.imageFront;
      map['imageBack'] = data.imageBack;
      map['imageLeft'] = data.imageLeft;
      map['imageRight'] = data.imageRight;
      map['imageChart'] = data.imageChart;
      map['imageFilterPaper'] = data.imageFilterPaper;
      map['optionalImage1'] = data.optionalImage1;
      map['optionalImage2'] = data.optionalImage2;
      map['optionalImage3'] = data.optionalImage3;
      map['optionalImage4'] = data.optionalImage4;
      return map;
    }
    return {};
  }

  // This image processing utility remains unchanged
  Future<File?> pickAndProcessImage(
      ImageSource source, {
        required String stationCode,
        required String imageInfo,
        String processType = 'INSTALL',
        required bool isRequired,
      }) 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;

    if (isRequired && originalImage.height > originalImage.width) {
      debugPrint("Image orientation check failed: Image must be in landscape mode.");
      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));
  }

  // --- START: MODIFIED FILENAME LOGIC ---
  /// Generates a unique timestamp ID from the sampling date and time.
  String _generateTimestampId(String? date, String? time) {
    final String dateTimeString = "${date ?? ''} ${time ?? ''}";
    try {
      // Air time format seems to be 'HH:mm'
      final DateTime samplingDateTime = DateFormat('yyyy-MM-dd HH:mm').parse(dateTimeString);
      return samplingDateTime.millisecondsSinceEpoch.toString();
    } catch (e) {
      debugPrint("Could not parse '$dateTimeString' for timestamp ID, using current time. Error: $e");
      return DateTime.now().millisecondsSinceEpoch.toString();
    }
  }
  // --- END: MODIFIED FILENAME LOGIC ---

  // --- REFACTORED submitInstallation method with granular error handling ---
  Future<Map<String, dynamic>> submitInstallation(AirInstallationData data, List<Map<String, dynamic>>? appSettings) async {
    const String moduleName = 'air_installation';
    final activeConfig = await _serverConfigService.getActiveApiConfig();
    final serverName = activeConfig?['config_name'] as String? ?? 'Default';

    // --- START: MODIFIED FILENAME LOGIC ---
    // Generate the unique timestamp ID and base filename FIRST.
    final String timestampId = _generateTimestampId(data.installationDate, data.installationTime);
    final String stationCode = data.stationID ?? 'UNKNOWN';
    final String baseFileName = "${stationCode}_INSTALLATION_${timestampId}";

    // Assign this as the primary refID for the log
    data.refID = baseFileName;
    // --- END: MODIFIED FILENAME LOGIC ---

    bool anyApiSuccess = false;
    Map<String, dynamic> apiDataResult = {};
    Map<String, dynamic> apiImageResult = {};
    final imageFiles = data.getImagesForUpload();
    String? apiRecordId; // Will hold the DB ID (e.g., 102)

    // Step 1: Attempt API Submission
    try {
      apiDataResult = await _submissionApiService.submitPost(
        moduleName: moduleName,
        endpoint: 'air/manual/installation',
        body: data.toJsonForApi(),
      );

      if (apiDataResult['success'] == true) {
        apiRecordId = apiDataResult['data']?['air_man_id']?.toString();
        if (apiRecordId != null) {
          data.airManId = int.tryParse(apiRecordId); // Save the DB ID
          apiImageResult = await _submissionApiService.submitMultipart(
            moduleName: moduleName,
            endpoint: 'air/manual/installation-images',
            fields: {'air_man_id': apiRecordId}, // Use DB ID for image upload
            files: imageFiles,
          );
          anyApiSuccess = apiImageResult['success'] == true;
        } else {
          anyApiSuccess = false;
          apiDataResult['message'] = 'API Error: Missing record ID.';
        }
      }
    } on SocketException catch (e) {
      final errorMessage = "API submission failed with network error: $e";
      debugPrint(errorMessage);
      anyApiSuccess = false;
      apiDataResult = {'success': false, 'message': errorMessage};
      await _retryService.addApiToQueue(endpoint: 'air/manual/installation', method: 'POST', body: data.toJsonForApi());
    } on TimeoutException catch (e) {
      final errorMessage = "API submission timed out: $e";
      debugPrint(errorMessage);
      anyApiSuccess = false;
      apiDataResult = {'success': false, 'message': errorMessage};
      await _retryService.addApiToQueue(endpoint: 'air/manual/installation', method: 'POST', body: data.toJsonForApi());
    }

    // Step 2: Attempt FTP Submission
    Map<String, dynamic> ftpDataResult = {'statuses': []};
    Map<String, dynamic> ftpImageResult = {'statuses': []};
    bool anyFtpSuccess = false;
    try {
      // --- FILENAME LOGIC MOVED ---
      // baseFileName is already generated above.

      final dataZip = await _zippingService.createDataZip(jsonDataMap: {'db.json': jsonEncode(data.toDbJson())}, baseFileName: baseFileName);
      if (dataZip != null) {
        ftpDataResult = await _submissionFtpService.submit(moduleName: moduleName, fileToUpload: dataZip, remotePath: '/air/data/${path.basename(dataZip.path)}');
      }

      final imageZip = await _zippingService.createImageZip(imageFiles: imageFiles.values.toList(), baseFileName: baseFileName);
      if (imageZip != null) {
        ftpImageResult = await _submissionFtpService.submit(moduleName: moduleName, fileToUpload: imageZip, remotePath: '/air/images/${path.basename(imageZip.path)}');
      }
      anyFtpSuccess = !(ftpDataResult['statuses'] as List).any((s) => s['success'] == false) && !(ftpImageResult['statuses'] as List).any((s) => s['success'] == false);
    } on SocketException catch (e) {
      debugPrint("FTP submission failed with network error: $e");
      anyFtpSuccess = false;
    } on TimeoutException catch (e) {
      debugPrint("FTP submission timed out: $e");
      anyFtpSuccess = false;
    }

    // Step 3: Determine Final Status
    String finalStatus;
    String finalMessage;
    if (anyApiSuccess && anyFtpSuccess) {
      finalStatus = 'S4';
      finalMessage = 'Data and files submitted successfully.';
    } else if (anyApiSuccess && !anyFtpSuccess) {
      finalStatus = 'S3';
      finalMessage = 'Data submitted to API, but FTP upload failed and was queued.';
    } else if (!anyApiSuccess && anyFtpSuccess) {
      finalStatus = 'L4';
      finalMessage = 'API submission failed, but files were sent via FTP.';
    } else {
      finalStatus = 'L1';
      finalMessage = 'Both API and FTP submissions failed and were queued.';
    }

    // --- START: MODIFIED LOGGING ---
    await _logAndSave(
      data: data,
      status: finalStatus,
      message: finalMessage,
      apiResults: [apiDataResult, apiImageResult],
      ftpStatuses: [...ftpDataResult['statuses'], ...ftpImageResult['statuses']],
      serverName: serverName,
      type: 'Installation',
      baseFileName: baseFileName, // Pass the generated filename
      apiRecordId: apiRecordId, // Pass the DB ID
    );
    // --- END: MODIFIED LOGGING ---

    if (anyApiSuccess || anyFtpSuccess) {
      _handleInstallationSuccessAlert(data, appSettings, isDataOnly: imageFiles.isEmpty);
    }

    return {'status': finalStatus, 'message': finalMessage};
  }

  // --- REFACTORED submitCollection method with granular error handling ---
  Future<Map<String, dynamic>> submitCollection(AirCollectionData data, AirInstallationData installationData, List<Map<String, dynamic>>? appSettings) async {
    const String moduleName = 'air_collection';
    final activeConfig = await _serverConfigService.getActiveApiConfig();
    final serverName = activeConfig?['config_name'] as String? ?? 'Default';

    // --- START: MODIFIED FILENAME LOGIC ---
    // Generate the unique timestamp ID and base filename FIRST.
    final String timestampId = _generateTimestampId(data.collectionDate, data.collectionTime);
    final String stationCode = installationData.stationID ?? 'UNKNOWN';
    final String baseFileName = "${stationCode}_COLLECTION_${timestampId}";

    // Assign this as the primary refID for the log
    data.installationRefID = baseFileName;
    // --- END: MODIFIED FILENAME LOGIC ---

    bool anyApiSuccess = false;
    Map<String, dynamic> apiDataResult = {};
    Map<String, dynamic> apiImageResult = {};
    final imageFiles = data.getImagesForUpload();
    String? apiRecordId; // Will hold the DB ID

    // Step 1: Attempt API Submission
    try {
      // Use the DB ID from the original installation
      data.airManId = installationData.airManId;
      apiRecordId = data.airManId?.toString(); // Store it for logging

      apiDataResult = await _submissionApiService.submitPost(
        moduleName: moduleName,
        endpoint: 'air/manual/collection',
        body: data.toJson(),
      );

      if (apiDataResult['success'] == true) {
        apiImageResult = await _submissionApiService.submitMultipart(
          moduleName: moduleName,
          endpoint: 'air/manual/collection-images',
          fields: {'air_man_id': data.airManId.toString()}, // Use DB ID
          files: imageFiles,
        );
        anyApiSuccess = apiImageResult['success'] == true;
      }
    } on SocketException catch (e) {
      final errorMessage = "API submission failed with network error: $e";
      debugPrint(errorMessage);
      anyApiSuccess = false;
      apiDataResult = {'success': false, 'message': errorMessage};
      await _retryService.addApiToQueue(endpoint: 'air/manual/collection', method: 'POST', body: data.toJson());
    } on TimeoutException catch (e) {
      final errorMessage = "API submission timed out: $e";
      debugPrint(errorMessage);
      anyApiSuccess = false;
      apiDataResult = {'success': false, 'message': errorMessage};
      await _retryService.addApiToQueue(endpoint: 'air/manual/collection', method: 'POST', body: data.toJson());
    }

    // Step 2: Attempt FTP Submission
    Map<String, dynamic> ftpDataResult = {'statuses': []};
    Map<String, dynamic> ftpImageResult = {'statuses': []};
    bool anyFtpSuccess = false;
    try {
      // --- FILENAME LOGIC MOVED ---
      // baseFileName is already generated above.

      final combinedJson = jsonEncode({"installation": installationData.toDbJson(), "collection": data.toMap()});
      final dataZip = await _zippingService.createDataZip(jsonDataMap: {'db.json': combinedJson}, baseFileName: baseFileName);
      if (dataZip != null) {
        ftpDataResult = await _submissionFtpService.submit(moduleName: moduleName, fileToUpload: dataZip, remotePath: '/air/data/${path.basename(dataZip.path)}');
      }

      final imageZip = await _zippingService.createImageZip(imageFiles: imageFiles.values.toList(), baseFileName: baseFileName);
      if (imageZip != null) {
        ftpImageResult = await _submissionFtpService.submit(moduleName: moduleName, fileToUpload: imageZip, remotePath: '/air/images/${path.basename(imageZip.path)}');
      }
      anyFtpSuccess = !(ftpDataResult['statuses'] as List).any((s) => s['success'] == false) && !(ftpImageResult['statuses'] as List).any((s) => s['success'] == false);
    } on SocketException catch (e) {
      debugPrint("FTP submission failed with network error: $e");
      anyFtpSuccess = false;
    } on TimeoutException catch (e) {
      debugPrint("FTP submission timed out: $e");
      anyFtpSuccess = false;
    }

    // Step 3: Determine Final Status
    String finalStatus;
    String finalMessage;
    if (anyApiSuccess && anyFtpSuccess) {
      finalStatus = 'S4';
      finalMessage = 'Data and files submitted successfully.';
    } else if (anyApiSuccess && !anyFtpSuccess) {
      finalStatus = 'S3';
      finalMessage = 'Data submitted to API, but FTP upload failed and was queued.';
    } else if (!anyApiSuccess && anyFtpSuccess) {
      finalStatus = 'L4';
      finalMessage = 'API submission failed, but files were sent via FTP.';
    } else {
      finalStatus = 'L1';
      finalMessage = 'Both API and FTP submissions failed and were queued.';
    }

    // --- START: MODIFIED LOGGING ---
    await _logAndSave(
      data: data,
      installationData: installationData,
      status: finalStatus,
      message: finalMessage,
      apiResults: [apiDataResult, apiImageResult],
      ftpStatuses: [...ftpDataResult['statuses'], ...ftpImageResult['statuses']],
      serverName: serverName,
      type: 'Collection',
      baseFileName: baseFileName, // Pass the generated filename
      apiRecordId: apiRecordId, // Pass the DB ID
    );
    // --- END: MODIFIED LOGGING ---

    if(anyApiSuccess || anyFtpSuccess) {
      _handleCollectionSuccessAlert(data, installationData, appSettings, isDataOnly: imageFiles.isEmpty);
    }

    return {'status': finalStatus, 'message': finalMessage};
  }

  /// Centralized method for logging and saving data locally.
  Future<void> _logAndSave({
    required dynamic data,
    AirInstallationData? installationData,
    required String status,
    required String message,
    required List<Map<String, dynamic>> apiResults,
    required List<Map<String, dynamic>> ftpStatuses,
    required String serverName,
    required String type,
    // --- START: MODIFIED LOGGING ---
    required String baseFileName, // Use this as the primary ID
    String? apiRecordId, // The ID from the server DB (e.g., 102)
    // --- END: MODIFIED LOGGING ---
  }) async {
    String refID;
    Map<String, dynamic> formData;
    List<String> imagePaths;

    if (type == 'Installation') {
      final installation = data as AirInstallationData;
      installation.status = status;
      refID = installation.refID!; // This is now baseFileName
      formData = installation.toMap();
      imagePaths = _getInstallationImagePaths(installation);
      await _localStorageService.saveAirSamplingRecord(_toMapForLocalSave(installation), refID, serverName: serverName);
    } else {
      final collection = data as AirCollectionData;
      collection.status = status;
      refID = collection.installationRefID!; // This is now baseFileName
      formData = collection.toMap();
      imagePaths = _getCollectionImagePaths(collection);
      await _localStorageService.saveAirSamplingRecord(_toMapForLocalSave(installationData!..collectionData = collection), refID, serverName: serverName);
    }

    final logData = {
      // --- START: MODIFIED LOGGING ---
      'submission_id': baseFileName, // Use the timestamp-based filename as the primary ID
      'module': 'air',
      'type': type,
      'status': status,
      'message': message,
      'report_id': apiRecordId, // Store the server DB ID (e.g., 102) here
      // --- END: MODIFIED LOGGING ---
      'created_at': DateTime.now().toIso8601String(),
      'form_data': jsonEncode(formData),
      'image_data': jsonEncode(imagePaths),
      'server_name': serverName,
      'api_status': jsonEncode(apiResults),
      'ftp_status': jsonEncode(ftpStatuses),
    };
    await _dbHelper.saveSubmissionLog(logData);
  }

  // Helper and Alert methods remain unchanged
  Future<void> _handleInstallationSuccessAlert(AirInstallationData data, List<Map<String, dynamic>>? appSettings, {required bool isDataOnly}) async {
    try {
      final message = data.generateInstallationTelegramAlert(isDataOnly: isDataOnly);
      final bool wasSent = await _telegramService.sendAlertImmediately('air_manual', message, appSettings);
      if (!wasSent) {
        await _telegramService.queueMessage('air_manual', message, appSettings);
      }
    } catch (e) {
      debugPrint("Failed to handle Air Manual Installation Telegram alert: $e");
    }
  }

  Future<void> _handleCollectionSuccessAlert(AirCollectionData data, AirInstallationData installationData, List<Map<String, dynamic>>? appSettings, {required bool isDataOnly}) async {
    try {
      final message = data.generateCollectionTelegramAlert(installationData, isDataOnly: isDataOnly);
      final bool wasSent = await _telegramService.sendAlertImmediately('air_manual', message, appSettings);
      if (!wasSent) {
        await _telegramService.queueMessage('air_manual', message, appSettings);
      }
    } catch (e) {
      debugPrint("Failed to handle Air Manual Collection Telegram alert: $e");
    }
  }

  List<String> _getInstallationImagePaths(AirInstallationData data) {
    final List<File?> files = [
      data.imageFront, data.imageBack, data.imageLeft, data.imageRight,
      data.optionalImage1, data.optionalImage2, data.optionalImage3, data.optionalImage4,
    ];
    return files.where((f) => f != null).map((f) => f!.path).toList();
  }

  List<String> _getCollectionImagePaths(AirCollectionData data) {
    final List<File?> files = [
      data.imageFront, data.imageBack, data.imageLeft, data.imageRight,
      data.imageChart, data.imageFilterPaper,
      data.optionalImage1, data.optionalImage2, data.optionalImage3, data.optionalImage4,
    ];
    return files.where((f) => f != null).map((f) => f!.path).toList();
  }

  // getPendingInstallations can be moved to a different service or screen logic later
  Future<List<AirInstallationData>> getPendingInstallations() async {
    debugPrint("Fetching pending installations from local storage...");
    final logs = await _dbHelper.loadSubmissionLogs(module: 'air');
    final pendingInstallations = logs
        ?.where((log) {
      final status = log['status'];
      return status == 'S1' || status == 'S2';
    })
        .map((log) => AirInstallationData.fromJson(jsonDecode(log['form_data'])))
        .toList() ?? [];
    return pendingInstallations;
  }
}