// lib/services/local_storage_service.dart

import 'dart:io';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:path/path.dart' as p;

import '../models/air_installation_data.dart';
import '../models/air_collection_data.dart';
import '../models/tarball_data.dart';
import '../models/in_situ_sampling_data.dart';
import '../models/river_in_situ_sampling_data.dart';

/// A comprehensive service for handling all local data storage for offline submissions.
class LocalStorageService {

  // =======================================================================
  // Part 1: Public Storage Setup
  // =======================================================================

  Future<bool> _requestPermissions() async {
    var status = await Permission.manageExternalStorage.request();
    return status.isGranted;
  }

  Future<Directory?> _getPublicMMSV4Directory() async {
    if (await _requestPermissions()) {
      final Directory? externalDir = await getExternalStorageDirectory();
      if (externalDir != null) {
        final publicRootPath = externalDir.path.split('/Android/')[0];
        final mmsv4Dir = Directory(p.join(publicRootPath, 'MMSV4'));
        if (!await mmsv4Dir.exists()) {
          await mmsv4Dir.create(recursive: true);
        }
        return mmsv4Dir;
      }
    }
    debugPrint("LocalStorageService: Manage External Storage permission was not granted.");
    return null;
  }

  // =======================================================================
  // Part 2: Air Manual Sampling Methods
  // =======================================================================

  Future<Directory?> _getAirManualBaseDir() async {
    final mmsv4Dir = await _getPublicMMSV4Directory();
    if (mmsv4Dir == null) return null;

    final airDir = Directory(p.join(mmsv4Dir.path, 'air', 'air_manual_sampling'));
    if (!await airDir.exists()) {
      await airDir.create(recursive: true);
    }
    return airDir;
  }

  /// Saves or updates an air sampling record, including copying all associated images to permanent local storage.
  /// CORRECTED: This now robustly handles maps with File objects from both installation and collection,
  /// preventing type errors that caused the installation screen to freeze.
  Future<String?> saveAirSamplingRecord(Map<String, dynamic> data, String refID) async {
    final baseDir = await _getAirManualBaseDir();
    if (baseDir == null) {
      debugPrint("Could not get public storage directory for Air Manual. Check permissions.");
      return null;
    }

    try {
      final eventDir = Directory(p.join(baseDir.path, refID));
      if (!await eventDir.exists()) {
        await eventDir.create(recursive: true);
      }

      // Helper function to copy a file and return its new, permanent path
      Future<String?> copyImageToLocal(dynamic imageFile) async {
        if (imageFile is! File) return null; // Gracefully handle non-File types
        try {
          // Check if the file is already in the permanent directory to avoid re-copying
          if (p.dirname(imageFile.path) == eventDir.path) {
            return imageFile.path;
          }
          final String fileName = p.basename(imageFile.path);
          final File newFile = await imageFile.copy(p.join(eventDir.path, fileName));
          return newFile.path;
        } catch (e) {
          debugPrint("Error copying file ${imageFile.path}: $e");
          return null;
        }
      }

      // Create a mutable copy of the data map to avoid modifying the original
      final Map<String, dynamic> serializableData = Map.from(data);

      // Define the keys for installation images to look for in the map
      final installationImageKeys = ['imageFront', 'imageBack', 'imageLeft', 'imageRight', 'optionalImage1', 'optionalImage2', 'optionalImage3', 'optionalImage4'];

      // Process top-level (installation) images
      for (final key in installationImageKeys) {
        // Check if the key exists and the value is a File object
        if (serializableData.containsKey(key) && serializableData[key] is File) {
          final newPath = await copyImageToLocal(serializableData[key]);
          serializableData['${key}Path'] = newPath; // Creates 'imageFrontPath', etc.
          // ** THE FIX **: Only remove the key if it was a File object that we have processed.
          serializableData.remove(key);
        }
      }

      // Process nested collection images, if they exist
      if (serializableData['collectionData'] is Map) {
        final collectionMap = Map<String, dynamic>.from(serializableData['collectionData']);
        final collectionImageKeys = ['imageFront', 'imageBack', 'imageLeft', 'imageRight', 'imageChart', 'imageFilterPaper', 'optionalImage1', 'optionalImage2', 'optionalImage3', 'optionalImage4'];

        for (final key in collectionImageKeys) {
          // Check if the key exists and the value is a File object
          if (collectionMap.containsKey(key) && collectionMap[key] is File) {
            final newPath = await copyImageToLocal(collectionMap[key]);
            collectionMap['${key}Path'] = newPath;
            // ** THE FIX **: Only remove the key if it was a File object that we have processed.
            collectionMap.remove(key);
          }
        }
        // Put the cleaned, serializable collection map back into the main data object
        serializableData['collectionData'] = collectionMap;
      }

      // Now that all File objects have been replaced with String paths, save the clean data.
      final jsonFile = File(p.join(eventDir.path, 'data.json'));
      await jsonFile.writeAsString(jsonEncode(serializableData));
      debugPrint("Air sampling log and images saved to: ${eventDir.path}");

      return eventDir.path;

    } catch (e, s) {
      debugPrint("Error saving air sampling log to local storage: $e");
      debugPrint("Stack trace: $s");
      return null;
    }
  }


  Future<List<Map<String, dynamic>>> getAllAirSamplingLogs() async {
    final baseDir = await _getAirManualBaseDir();
    if (baseDir == null || !await baseDir.exists()) return [];

    try {
      final List<Map<String, dynamic>> logs = [];
      final entities = baseDir.listSync();

      for (var entity in entities) {
        if (entity is Directory) {
          final jsonFile = File(p.join(entity.path, 'data.json'));
          if (await jsonFile.exists()) {
            final content = await jsonFile.readAsString();
            final data = jsonDecode(content) as Map<String, dynamic>;
            data['logDirectory'] = entity.path;
            logs.add(data);
          }
        }
      }
      return logs;
    } catch (e) {
      debugPrint("Error getting all air sampling logs: $e");
      return [];
    }
  }

  // =======================================================================
  // Part 3: Tarball Specific Methods
  // =======================================================================

  Future<Directory?> _getTarballBaseDir() async {
    final mmsv4Dir = await _getPublicMMSV4Directory();
    if (mmsv4Dir == null) return null;

    final tarballDir = Directory(p.join(mmsv4Dir.path, 'marine', 'marine_tarball_sampling'));
    if (!await tarballDir.exists()) {
      await tarballDir.create(recursive: true);
    }
    return tarballDir;
  }

  Future<String?> saveTarballSamplingData(TarballSamplingData data) async {
    final baseDir = await _getTarballBaseDir();
    if (baseDir == null) {
      debugPrint("Could not get public storage directory. Check permissions.");
      return null;
    }

    try {
      final stationCode = data.selectedStation?['tbl_station_code'] ?? 'UNKNOWN_STATION';
      final timestamp = "${data.samplingDate}_${data.samplingTime?.replaceAll(':', '-')}";
      final eventFolderName = "${stationCode}_$timestamp";
      final eventDir = Directory(p.join(baseDir.path, eventFolderName));

      if (!await eventDir.exists()) {
        await eventDir.create(recursive: true);
      }

      final Map<String, dynamic> jsonData = { ...data.toFormData(), 'submissionStatus': data.submissionStatus, 'submissionMessage': data.submissionMessage, 'reportId': data.reportId };

      jsonData['selectedStation'] = data.selectedStation;

      final imageFiles = data.toImageFiles();

      for (var entry in imageFiles.entries) {
        final File? imageFile = entry.value;
        if (imageFile != null) {
          final String originalFileName = p.basename(imageFile.path);
          final File newFile = await imageFile.copy(p.join(eventDir.path, originalFileName));
          jsonData[entry.key] = newFile.path;
        }
      }

      final jsonFile = File(p.join(eventDir.path, 'data.json'));
      await jsonFile.writeAsString(jsonEncode(jsonData));
      debugPrint("Tarball log saved to: ${jsonFile.path}");

      return eventDir.path;

    } catch (e) {
      debugPrint("Error saving tarball log to local storage: $e");
      return null;
    }
  }

  Future<List<Map<String, dynamic>>> getAllTarballLogs() async {
    final baseDir = await _getTarballBaseDir();
    if (baseDir == null || !await baseDir.exists()) return [];

    try {
      final List<Map<String, dynamic>> logs = [];
      final entities = baseDir.listSync();

      for (var entity in entities) {
        if (entity is Directory) {
          final jsonFile = File(p.join(entity.path, 'data.json'));
          if (await jsonFile.exists()) {
            final content = await jsonFile.readAsString();
            final data = jsonDecode(content) as Map<String, dynamic>;
            data['logDirectory'] = entity.path;
            logs.add(data);
          }
        }
      }
      return logs;
    } catch (e) {
      debugPrint("Error getting all tarball logs: $e");
      return [];
    }
  }

  Future<void> updateTarballLog(Map<String, dynamic> updatedLogData) async {
    final logDir = updatedLogData['logDirectory'];
    if (logDir == null) {
      debugPrint("Cannot update log: logDirectory key is missing.");
      return;
    }

    try {
      final jsonFile = File(p.join(logDir, 'data.json'));
      if (await jsonFile.exists()) {
        updatedLogData.remove('isResubmitting');
        await jsonFile.writeAsString(jsonEncode(updatedLogData));
        debugPrint("Log updated successfully at: ${jsonFile.path}");
      }
    } catch (e) {
      debugPrint("Error updating tarball log: $e");
    }
  }


  // =======================================================================
  // Part 4: Marine In-Situ Specific Methods
  // =======================================================================

  Future<Directory?> _getInSituBaseDir() async {
    final mmsv4Dir = await _getPublicMMSV4Directory();
    if (mmsv4Dir == null) return null;

    final inSituDir = Directory(p.join(mmsv4Dir.path, 'marine', 'marine_in_situ_sampling'));
    if (!await inSituDir.exists()) {
      await inSituDir.create(recursive: true);
    }
    return inSituDir;
  }

  Future<String?> saveInSituSamplingData(InSituSamplingData data) async {
    final baseDir = await _getInSituBaseDir();
    if (baseDir == null) {
      debugPrint("Could not get public storage directory for In-Situ. Check permissions.");
      return null;
    }

    try {
      final stationCode = data.selectedStation?['man_station_code'] ?? 'UNKNOWN_STATION';
      final timestamp = "${data.samplingDate}_${data.samplingTime?.replaceAll(':', '-')}";
      final eventFolderName = "${stationCode}_$timestamp";
      final eventDir = Directory(p.join(baseDir.path, eventFolderName));

      if (!await eventDir.exists()) {
        await eventDir.create(recursive: true);
      }

      final Map<String, dynamic> jsonData = { ...data.toApiFormData(), 'submissionStatus': data.submissionStatus, 'submissionMessage': data.submissionMessage, 'reportId': data.reportId };

      jsonData['selectedStation'] = data.selectedStation;

      final imageFiles = data.toApiImageFiles();
      for (var entry in imageFiles.entries) {
        final File? imageFile = entry.value;
        if (imageFile != null) {
          final String originalFileName = p.basename(imageFile.path);
          final File newFile = await imageFile.copy(p.join(eventDir.path, originalFileName));
          jsonData[entry.key] = newFile.path;
        }
      }

      final jsonFile = File(p.join(eventDir.path, 'data.json'));
      await jsonFile.writeAsString(jsonEncode(jsonData));
      debugPrint("In-Situ log saved to: ${jsonFile.path}");

      return eventDir.path;

    } catch (e) {
      debugPrint("Error saving In-Situ log to local storage: $e");
      return null;
    }
  }

  Future<List<Map<String, dynamic>>> getAllInSituLogs() async {
    final baseDir = await _getInSituBaseDir();
    if (baseDir == null || !await baseDir.exists()) return [];

    try {
      final List<Map<String, dynamic>> logs = [];
      final entities = baseDir.listSync();

      for (var entity in entities) {
        if (entity is Directory) {
          final jsonFile = File(p.join(entity.path, 'data.json'));
          if (await jsonFile.exists()) {
            final content = await jsonFile.readAsString();
            final data = jsonDecode(content) as Map<String, dynamic>;
            data['logDirectory'] = entity.path;
            logs.add(data);
          }
        }
      }
      return logs;
    } catch (e) {
      debugPrint("Error getting all in-situ logs: $e");
      return [];
    }
  }

  Future<void> updateInSituLog(Map<String, dynamic> updatedLogData) async {
    final logDir = updatedLogData['logDirectory'];
    if (logDir == null) {
      debugPrint("Cannot update log: logDirectory key is missing.");
      return;
    }

    try {
      final jsonFile = File(p.join(logDir, 'data.json'));
      if (await jsonFile.exists()) {
        updatedLogData.remove('isResubmitting');
        await jsonFile.writeAsString(jsonEncode(updatedLogData));
        debugPrint("Log updated successfully at: ${jsonFile.path}");
      }
    } catch (e) {
      debugPrint("Error updating in-situ log: $e");
    }
  }

  // =======================================================================
  // Part 5: River In-Situ Specific Methods
  // =======================================================================

  Future<Directory?> _getRiverInSituBaseDir(String? samplingType) async {
    final mmsv4Dir = await _getPublicMMSV4Directory();
    if (mmsv4Dir == null) return null;

    String subfolderName;
    if (samplingType == 'Schedule' || samplingType == 'Triennial') {
      subfolderName = samplingType!;
    } else {
      subfolderName = 'Others';
    }

    final inSituDir = Directory(p.join(mmsv4Dir.path, 'river', 'river_in_situ_sampling', subfolderName));
    if (!await inSituDir.exists()) {
      await inSituDir.create(recursive: true);
    }
    return inSituDir;
  }

  Future<String?> saveRiverInSituSamplingData(RiverInSituSamplingData data) async {
    final baseDir = await _getRiverInSituBaseDir(data.samplingType);
    if (baseDir == null) {
      debugPrint("Could not get public storage directory for River In-Situ. Check permissions.");
      return null;
    }

    try {
      final stationCode = data.selectedStation?['sampling_station_code'] ?? 'UNKNOWN_STATION';
      final timestamp = "${data.samplingDate}_${data.samplingTime?.replaceAll(':', '-')}";
      final eventFolderName = "${stationCode}_$timestamp";
      final eventDir = Directory(p.join(baseDir.path, eventFolderName));

      if (!await eventDir.exists()) {
        await eventDir.create(recursive: true);
      }

      final Map<String, dynamic> jsonData = { ...data.toApiFormData(), 'submissionStatus': data.submissionStatus, 'submissionMessage': data.submissionMessage, 'reportId': data.reportId };

      jsonData['selectedStation'] = data.selectedStation;

      final imageFiles = data.toApiImageFiles();
      for (var entry in imageFiles.entries) {
        final File? imageFile = entry.value;
        if (imageFile != null) {
          final String originalFileName = p.basename(imageFile.path);
          final File newFile = await imageFile.copy(p.join(eventDir.path, originalFileName));
          jsonData[entry.key] = newFile.path;
        }
      }

      final jsonFile = File(p.join(eventDir.path, 'data.json'));
      await jsonFile.writeAsString(jsonEncode(jsonData));
      debugPrint("River In-Situ log saved to: ${jsonFile.path}");

      return eventDir.path;

    } catch (e) {
      debugPrint("Error saving River In-Situ log to local storage: $e");
      return null;
    }
  }

  Future<List<Map<String, dynamic>>> getAllRiverInSituLogs() async {
    final mmsv4Dir = await _getPublicMMSV4Directory();
    if (mmsv4Dir == null) return [];

    final topLevelDir = Directory(p.join(mmsv4Dir.path, 'river', 'river_in_situ_sampling'));
    if (!await topLevelDir.exists()) return [];

    try {
      final List<Map<String, dynamic>> logs = [];
      final typeSubfolders = topLevelDir.listSync();

      for (var typeSubfolder in typeSubfolders) {
        if (typeSubfolder is Directory) {
          final eventFolders = typeSubfolder.listSync();
          for (var eventFolder in eventFolders) {
            if (eventFolder is Directory) {
              final jsonFile = File(p.join(eventFolder.path, 'data.json'));
              if (await jsonFile.exists()) {
                final content = await jsonFile.readAsString();
                final data = jsonDecode(content) as Map<String, dynamic>;
                data['logDirectory'] = eventFolder.path;
                logs.add(data);
              }
            }
          }
        }
      }
      return logs;
    } catch (e) {
      debugPrint("Error getting all river in-situ logs: $e");
      return [];
    }
  }

  Future<void> updateRiverInSituLog(Map<String, dynamic> updatedLogData) async {
    final logDir = updatedLogData['logDirectory'];
    if (logDir == null) {
      debugPrint("Cannot update log: logDirectory key is missing.");
      return;
    }

    try {
      final jsonFile = File(p.join(logDir, 'data.json'));
      if (await jsonFile.exists()) {
        updatedLogData.remove('isResubmitting');
        await jsonFile.writeAsString(jsonEncode(updatedLogData));
        debugPrint("Log updated successfully at: ${jsonFile.path}");
      }
    } catch (e) {
      debugPrint("Error updating river in-situ log: $e");
    }
  }
}