// lib/services/api_service.dart

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

import 'package:environment_monitoring_app/services/base_api_service.dart';
import 'package:environment_monitoring_app/services/telegram_service.dart';
import 'package:environment_monitoring_app/models/in_situ_sampling_data.dart';
import 'package:environment_monitoring_app/models/tarball_data.dart';
import 'package:environment_monitoring_app/models/air_collection_data.dart';
import 'package:environment_monitoring_app/models/air_installation_data.dart';
import 'package:environment_monitoring_app/models/river_in_situ_sampling_data.dart';
import 'package:environment_monitoring_app/services/server_config_service.dart';

// =======================================================================
// Part 1: Unified API Service
// =======================================================================

class ApiService {
  final BaseApiService _baseService = BaseApiService();
  final DatabaseHelper dbHelper = DatabaseHelper();
  final ServerConfigService _serverConfigService = ServerConfigService();

  late final MarineApiService marine;
  late final RiverApiService river;
  late final AirApiService air;

  static const String imageBaseUrl = 'https://mms-apiv4.pstw.com.my/';

  ApiService({required TelegramService telegramService}) {
    marine = MarineApiService(_baseService, telegramService, _serverConfigService, dbHelper);
    river = RiverApiService(_baseService, telegramService, _serverConfigService, dbHelper);
    air = AirApiService(_baseService, telegramService, _serverConfigService);
  }
  // --- END: FIX FOR CONSTRUCTOR ERROR ---

  // --- Core API Methods ---

  Future<Map<String, dynamic>> login(String email, String password) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.post(baseUrl, 'auth/login', {'email': email, 'password': password});
  }

  Future<Map<String, dynamic>> register({
    required String username,
    String? firstName,
    String? lastName,
    required String email,
    required String password,
    String? phoneNumber,
    int? departmentId,
    int? companyId,
    int? positionId,
  }) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    final Map<String, dynamic> body = {
      'username': username,
      'email': email,
      'password': password,
      'first_name': firstName ?? '',
      'last_name': lastName ?? '',
      'phone_number': phoneNumber ?? '',
      'department_id': departmentId,
      'company_id': companyId,
      'position_id': positionId,
    };
    body.removeWhere((key, value) => value == null);
    return _baseService.post(baseUrl, 'auth/register', body);
  }

  Future<Map<String, dynamic>> post(String endpoint, Map<String, dynamic> data) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.post(baseUrl, endpoint, data);
  }

  Future<Map<String, dynamic>> getProfile() async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.get(baseUrl, 'profile');
  }

  Future<Map<String, dynamic>> getAllUsers() async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.get(baseUrl, 'users');
  }

  Future<Map<String, dynamic>> getAllDepartments() async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.get(baseUrl, 'departments');
  }

  Future<Map<String, dynamic>> getAllCompanies() async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.get(baseUrl, 'companies');
  }

  Future<Map<String, dynamic>> getAllPositions() async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.get(baseUrl, 'positions');
  }

  Future<Map<String, dynamic>> getAllStates() async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.get(baseUrl, 'states');
  }

  Future<Map<String, dynamic>> sendTelegramAlert({
    required String chatId,
    required String message,
  }) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.post(baseUrl, 'marine/telegram-alert', {
      'chat_id': chatId,
      'message': message,
    });
  }

  Future<File?> downloadProfilePicture(String imageUrl, String localPath) async {
    try {
      final response = await http.get(Uri.parse(imageUrl));
      if (response.statusCode == 200) {
        final File file = File(localPath);
        await file.parent.create(recursive: true);
        await file.writeAsBytes(response.bodyBytes);
        return file;
      }
    } catch (e) {
      debugPrint('Error downloading profile picture: $e');
    }
    return null;
  }

  Future<Map<String, dynamic>> uploadProfilePicture(File imageFile) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.postMultipart(
        baseUrl: baseUrl,
        endpoint: 'profile/upload-picture',
        fields: {},
        files: {'profile_picture': imageFile});
  }

  Future<Map<String, dynamic>> refreshProfile() async {
    debugPrint('ApiService: Refreshing profile data from server...');
    final result = await getProfile();
    if (result['success'] == true && result['data'] != null) {
      await dbHelper.saveProfile(result['data']);
      debugPrint('ApiService: Profile data refreshed and saved to local DB.');
    }
    return result;
  }

  /// Validates the current session token by making a lightweight API call.
  /// Throws [SessionExpiredException] if the token is invalid (401).
  Future<void> validateToken() async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    // A simple GET request to an authenticated endpoint like /profile is perfect for validation.
    // The underlying _handleResponse in BaseApiService will automatically throw the exception on 401.
    await _baseService.get(baseUrl, 'profile');
  }

  // --- REWRITTEN FOR DELTA SYNC ---

  /// Helper method to make a delta-sync API call.
  Future<Map<String, dynamic>> _fetchDelta(String endpoint, String? lastSyncTimestamp) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    String url = endpoint;
    if (lastSyncTimestamp != null) {
      url += '?since=$lastSyncTimestamp';
    }
    return _baseService.get(baseUrl, url);
  }

  /// Orchestrates a full DELTA sync from the server to the local database.
  Future<Map<String, dynamic>> syncAllData({String? lastSyncTimestamp}) async {
    debugPrint('ApiService: Starting DELTA data sync. Since: $lastSyncTimestamp');
    try {
      final syncTasks = {
        'profile': {
          'endpoint': 'profile',
          'handler': (d, id) async {
            if (d.isNotEmpty) await dbHelper.saveProfile(d.first);
          }
        },
        'allUsers': {
          'endpoint': 'users',
          'handler': (d, id) async {
            // START CHANGE: Use custom upsert method for users
            await dbHelper.upsertUsers(d);
            await dbHelper.deleteUsers(id);
            // END CHANGE
          }
        },
        'documents': {
          'endpoint': 'documents',
          'handler': (d, id) async {
            await dbHelper.upsertDocuments(d);
            await dbHelper.deleteDocuments(id);
          }
        },
        'tarballStations': {
          'endpoint': 'marine/tarball/stations',
          'handler': (d, id) async {
            await dbHelper.upsertTarballStations(d);
            await dbHelper.deleteTarballStations(id);
          }
        },
        'manualStations': {
          'endpoint': 'marine/manual/stations',
          'handler': (d, id) async {
            await dbHelper.upsertManualStations(d);
            await dbHelper.deleteManualStations(id);
          }
        },
        'tarballClassifications': {
          'endpoint': 'marine/tarball/classifications',
          'handler': (d, id) async {
            await dbHelper.upsertTarballClassifications(d);
            await dbHelper.deleteTarballClassifications(id);
          }
        },
        'riverManualStations': {
          'endpoint': 'river/manual-stations',
          'handler': (d, id) async {
            await dbHelper.upsertRiverManualStations(d);
            await dbHelper.deleteRiverManualStations(id);
          }
        },
        'riverTriennialStations': {
          'endpoint': 'river/triennial-stations',
          'handler': (d, id) async {
            await dbHelper.upsertRiverTriennialStations(d);
            await dbHelper.deleteRiverTriennialStations(id);
          }
        },
        'departments': {
          'endpoint': 'departments',
          'handler': (d, id) async {
            await dbHelper.upsertDepartments(d);
            await dbHelper.deleteDepartments(id);
          }
        },
        'companies': {
          'endpoint': 'companies',
          'handler': (d, id) async {
            await dbHelper.upsertCompanies(d);
            await dbHelper.deleteCompanies(id);
          }
        },
        'positions': {
          'endpoint': 'positions',
          'handler': (d, id) async {
            await dbHelper.upsertPositions(d);
            await dbHelper.deletePositions(id);
          }
        },
        'airManualStations': {
          'endpoint': 'air/manual-stations',
          'handler': (d, id) async {
            await dbHelper.upsertAirManualStations(d);
            await dbHelper.deleteAirManualStations(id);
          }
        },
        'airClients': {
          'endpoint': 'air/clients',
          'handler': (d, id) async {
            await dbHelper.upsertAirClients(d);
            await dbHelper.deleteAirClients(id);
          }
        },
        'states': {
          'endpoint': 'states',
          'handler': (d, id) async {
            await dbHelper.upsertStates(d);
            await dbHelper.deleteStates(id);
          }
        },
        'appSettings': {
          'endpoint': 'settings',
          'handler': (d, id) async {
            await dbHelper.upsertAppSettings(d);
            await dbHelper.deleteAppSettings(id);
          }
        },
        // --- START: REPLACED GENERIC LIMITS WITH SPECIFIC SYNC TASKS ---
        'npeParameterLimits': {
          'endpoint': 'npe-parameter-limits',
          'handler': (d, id) async {
            await dbHelper.upsertNpeParameterLimits(d);
            await dbHelper.deleteNpeParameterLimits(id);
          }
        },
        'marineParameterLimits': {
          'endpoint': 'marine-parameter-limits',
          'handler': (d, id) async {
            await dbHelper.upsertMarineParameterLimits(d);
            await dbHelper.deleteMarineParameterLimits(id);
          }
        },
        'riverParameterLimits': {
          'endpoint': 'river-parameter-limits',
          'handler': (d, id) async {
            await dbHelper.upsertRiverParameterLimits(d);
            await dbHelper.deleteRiverParameterLimits(id);
          }
        },
        // --- END: REPLACED GENERIC LIMITS WITH SPECIFIC SYNC TASKS ---
        'apiConfigs': {
          'endpoint': 'api-configs',
          'handler': (d, id) async {
            await dbHelper.upsertApiConfigs(d);
            await dbHelper.deleteApiConfigs(id);
          }
        },
        'ftpConfigs': {
          'endpoint': 'ftp-configs',
          'handler': (d, id) async {
            await dbHelper.upsertFtpConfigs(d);
            await dbHelper.deleteFtpConfigs(id);
          }
        },
      };

      // Fetch all deltas in parallel
      final fetchFutures = syncTasks.map((key, value) =>
          MapEntry(key, _fetchDelta(value['endpoint'] as String, lastSyncTimestamp)));
      final results = await Future.wait(fetchFutures.values);
      final resultData = Map.fromIterables(fetchFutures.keys, results);

      // Process and save all changes
      for (var entry in resultData.entries) {
        final key = entry.key;
        final result = entry.value;

        if (result['success'] == true && result['data'] != null) {
          if (key == 'profile') {
            await (syncTasks[key]!['handler'] as Function)([result['data']], []);
          } else {
            final updated = List<Map<String, dynamic>>.from(result['data']['updated'] ?? []);
            final deleted = List<dynamic>.from(result['data']['deleted'] ?? []);
            await (syncTasks[key]!['handler'] as Function)(updated, deleted);
          }
        } else {
          debugPrint('ApiService: Failed to sync $key. Message: ${result['message']}');
        }
      }

      debugPrint('ApiService: Delta sync complete.');
      return {'success': true, 'message': 'Delta sync successful.'};
    } catch (e) {
      debugPrint('ApiService: Delta data sync failed: $e');
      // Re-throw the original exception so AuthProvider can catch specific types like SessionExpiredException
      rethrow;
    }
  }

  // --- START: NEW METHOD FOR REGISTRATION SCREEN ---
  /// Fetches only the public master data required for the registration screen.
  Future<Map<String, dynamic>> syncRegistrationData() async {
    debugPrint('ApiService: Starting registration data sync...');
    try {
      // Define only the tasks needed for registration
      final syncTasks = {
        'departments': {
          'endpoint': 'departments',
          'handler': (d, id) async {
            await dbHelper.upsertDepartments(d);
            await dbHelper.deleteDepartments(id);
          }
        },
        'companies': {
          'endpoint': 'companies',
          'handler': (d, id) async {
            await dbHelper.upsertCompanies(d);
            await dbHelper.deleteCompanies(id);
          }
        },
        'positions': {
          'endpoint': 'positions',
          'handler': (d, id) async {
            await dbHelper.upsertPositions(d);
            await dbHelper.deletePositions(id);
          }
        },
      };

      // Fetch all deltas in parallel, always a full fetch (since = null)
      final fetchFutures = syncTasks.map((key, value) =>
          MapEntry(key, _fetchDelta(value['endpoint'] as String, null)));
      final results = await Future.wait(fetchFutures.values);
      final resultData = Map.fromIterables(fetchFutures.keys, results);

      // Process and save all changes
      for (var entry in resultData.entries) {
        final key = entry.key;
        final result = entry.value;

        if (result['success'] == true && result['data'] != null) {
          final updated = List<Map<String, dynamic>>.from(result['data']['updated'] ?? []);
          final deleted = List<dynamic>.from(result['data']['deleted'] ?? []);
          await (syncTasks[key]!['handler'] as Function)(updated, deleted);
        } else {
          debugPrint('ApiService: Failed to sync $key. Message: ${result['message']}');
        }
      }

      debugPrint('ApiService: Registration data sync complete.');
      return {'success': true, 'message': 'Registration data sync successful.'};
    } catch (e) {
      debugPrint('ApiService: Registration data sync failed: $e');
      return {'success': false, 'message': 'Registration data sync failed: $e'};
    }
  }
// --- END: NEW METHOD FOR REGISTRATION SCREEN ---

}

// =======================================================================
// Part 2: Feature-Specific API Services (Refactored to include Telegram)
// =======================================================================

class AirApiService {
  final BaseApiService _baseService;
  final TelegramService? _telegramService;
  final ServerConfigService _serverConfigService;

  AirApiService(this._baseService, this._telegramService, this._serverConfigService);

  Future<Map<String, dynamic>> getManualStations() async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.get(baseUrl, 'air/manual-stations');
  }

  Future<Map<String, dynamic>> getClients() async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.get(baseUrl, 'air/clients');
  }

  Future<Map<String, dynamic>> submitInstallation(AirInstallationData data) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.post(baseUrl, 'air/manual/installation', data.toJsonForApi());
  }

  Future<Map<String, dynamic>> submitCollection(AirCollectionData data) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.post(baseUrl, 'air/manual/collection', data.toJson());
  }

  Future<Map<String, dynamic>> uploadInstallationImages({
    required String airManId,
    required Map<String, File> files,
  }) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.postMultipart(
      baseUrl: baseUrl,
      endpoint: 'air/manual/installation-images',
      fields: {'air_man_id': airManId},
      files: files,
    );
  }

  Future<Map<String, dynamic>> uploadCollectionImages({
    required String airManId,
    required Map<String, File> files,
  }) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.postMultipart(
      baseUrl: baseUrl,
      endpoint: 'air/manual/collection-images',
      fields: {'air_man_id': airManId},
      files: files,
    );
  }
}

class MarineApiService {
  final BaseApiService _baseService;
  final TelegramService _telegramService;
  final ServerConfigService _serverConfigService;
  final DatabaseHelper _dbHelper;

  MarineApiService(this._baseService, this._telegramService, this._serverConfigService, this._dbHelper);

  Future<Map<String, dynamic>> sendImageRequestEmail({
    required String recipientEmail,
    required List<String> imageUrls,
    required String stationName,
    required String samplingDate,
  }) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();

    final Map<String, String> fields = {
      'recipientEmail': recipientEmail,
      'imageUrls': jsonEncode(imageUrls),
      'stationName': stationName,
      'samplingDate': samplingDate,
    };

    return _baseService.postMultipart(
      baseUrl: baseUrl,
      endpoint: 'marine/images/send-email',
      fields: fields,
      files: {},
    );
  }

  // --- START: FIX - Replaced mock with a real API call ---
  Future<Map<String, dynamic>> getManualSamplingImages({
    required int stationId,
    required DateTime samplingDate,
    required String samplingType,
  }) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    final String dateStr = DateFormat('yyyy-MM-dd').format(samplingDate);

    final String endpoint = 'marine/manual/records-by-station?station_id=$stationId&date=$dateStr';

    debugPrint("ApiService: Calling real API endpoint: $endpoint");

    final response = await _baseService.get(baseUrl, endpoint);

    // The backend now returns a root 'data' key which the base service handles.
    // However, the PHP controller wraps the results again in a 'data' key inside the main data object.
    // We need to extract this nested list.
    if (response['success'] == true && response['data'] is Map && response['data']['data'] is List) {
      return {
        'success': true,
        'data': response['data']['data'],
        'message': response['message'],
      };
    }

    // Return the original response if the structure isn't as expected, or if it's an error.
    return response;
  }
  // --- END: FIX ---

  Future<Map<String, dynamic>> getTarballStations() async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.get(baseUrl, 'marine/tarball/stations');
  }

  Future<Map<String, dynamic>> getManualStations() async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.get(baseUrl, 'marine/manual/stations');
  }

  Future<Map<String, dynamic>> getTarballClassifications() async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.get(baseUrl, 'marine/tarball/classifications');
  }

  Future<Map<String, dynamic>> submitInSituSample({
    required Map<String, String> formData,
    required Map<String, File?> imageFiles,
    required InSituSamplingData inSituData,
    required List<Map<String, dynamic>>? appSettings,
  }) async {
    debugPrint("Step 1: Submitting in-situ form data to the server...");
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    final dataResult = await _baseService.post(baseUrl, 'marine/manual/sample', formData);

    if (dataResult['success'] != true) {
      debugPrint("API submission failed for In-Situ. Message: ${dataResult['message']}");
      return {
        'status': 'L1',
        'success': false,
        'message': 'Failed to submit in-situ data: ${dataResult['message']}',
        'reportId': null,
      };
    }
    debugPrint("Step 1 successful. In-situ data submitted. Report ID: ${dataResult['data']?['man_id']}");

    final recordId = dataResult['data']?['man_id'];
    if (recordId == null) {
      debugPrint("API submitted, but no record ID returned.");
      return {
        'status': 'L2',
        'success': false,
        'message': 'In-situ data submitted, but failed to get a record ID for images.',
        'reportId': null,
      };
    }

    final filesToUpload = <String, File>{};
    imageFiles.forEach((key, value) {
      if (value != null) filesToUpload[key] = value;
    });

    if (filesToUpload.isEmpty) {
      debugPrint("No images to upload. Finalizing submission.");
      _handleInSituSuccessAlert(inSituData, appSettings, isDataOnly: true); // Uses the inSituData object
      return {
        'status': 'L3',
        'success': true,
        'message': 'In-situ data submitted successfully. No images were attached.',
        'reportId': recordId.toString(),
      };
    }

    debugPrint("Step 2: Uploading ${filesToUpload.length} in-situ images for record ID: $recordId");
    final imageResult = await _baseService.postMultipart(
      baseUrl: baseUrl,
      endpoint: 'marine/manual/images',
      fields: {'man_id': recordId.toString()},
      files: filesToUpload,
    );

    if (imageResult['success'] != true) {
      debugPrint("Image upload failed for In-Situ. Message: ${imageResult['message']}");
      return {
        'status': 'L2',
        'success': false,
        'message': 'In-situ data submitted, but image upload failed: ${imageResult['message']}',
        'reportId': recordId.toString(),
      };
    }

    debugPrint("Step 2 successful. All images uploaded.");
    _handleInSituSuccessAlert(inSituData, appSettings, isDataOnly: false);
    return {
      'status': 'L3',
      'success': true,
      'message': 'Data and images submitted to server successfully.',
      'reportId': recordId.toString(),
    };
  }

  Future<void> _handleInSituSuccessAlert(InSituSamplingData data,
      List<Map<String, dynamic>>? appSettings, {required bool isDataOnly}) async {
    try {
      final message = await _generateInSituAlertMessage(data, isDataOnly: isDataOnly);
      final bool wasSent = await _telegramService.sendAlertImmediately('marine_in_situ', message, appSettings);
      if (!wasSent) {
        await _telegramService.queueMessage('marine_in_situ', message, appSettings);
      }
    } catch (e) {
      debugPrint("Failed to handle In-Situ Telegram alert: $e");
    }
  }

  Future<String> _generateInSituAlertMessage(InSituSamplingData data, {required bool isDataOnly}) async {
    final submissionType = isDataOnly ? "(Data Only)" : "(Data & Images)";
    final stationName = data.selectedStation?['man_station_name'] ?? 'N/A';
    final stationCode = data.selectedStation?['man_station_code'] ?? 'N/A';
    final distanceKm = data.distanceDifferenceInKm ?? 0;
    final distanceMeters = (distanceKm * 1000).toStringAsFixed(0);
    final distanceRemarks = data.distanceDifferenceRemarks ?? 'N/A';

    final buffer = StringBuffer()
      ..writeln('✅ *Marine In-Situ Sample ${submissionType} Submitted:*')
      ..writeln()
      ..writeln('*Station Name & Code:* $stationName ($stationCode)')
      ..writeln('*Date of Submitted:* ${data.samplingDate}')
      ..writeln('*Submitted by User:* ${data.firstSamplerName}')
      ..writeln('*Sonde ID:* ${data.sondeId ?? 'N/A'}')
      ..writeln('*Status of Submission:* Successful');

    if (distanceKm > 0 || (distanceRemarks.isNotEmpty && distanceRemarks != 'N/A')) {
      buffer
        ..writeln()
        ..writeln('🔔 *Distance Alert:*')
        ..writeln('*Distance from station:* $distanceMeters meters');
      if (distanceRemarks.isNotEmpty && distanceRemarks != 'N/A') {
        buffer.writeln('*Remarks for distance:* $distanceRemarks');
      }
    }

    final outOfBoundsAlert = await _getOutOfBoundsAlertSection(data);
    if (outOfBoundsAlert.isNotEmpty) {
      buffer.write(outOfBoundsAlert);
    }

    return buffer.toString();
  }

  Future<String> _getOutOfBoundsAlertSection(InSituSamplingData data) async {
    const Map<String, String> _parameterKeyToLimitName = {
      'oxygenConcentration': 'Oxygen Conc', 'oxygenSaturation': 'Oxygen Sat', 'ph': 'pH',
      'salinity': 'Salinity', 'electricalConductivity': 'Conductivity', 'temperature': 'Temperature',
      'tds': 'TDS', 'turbidity': 'Turbidity', 'tss': 'TSS', 'batteryVoltage': 'Battery',
    };

    final allLimits = await _dbHelper.loadMarineParameterLimits() ?? [];
    if (allLimits.isEmpty) return "";

    final int? stationId = data.selectedStation?['station_id'];
    final readings = {
      'oxygenConcentration': data.oxygenConcentration, 'oxygenSaturation': data.oxygenSaturation,
      'ph': data.ph, 'salinity': data.salinity, 'electricalConductivity': data.electricalConductivity,
      'temperature': data.temperature, 'tds': data.tds, 'turbidity': data.turbidity,
      'tss': data.tss, 'batteryVoltage': data.batteryVoltage,
    };

    final List<String> outOfBoundsMessages = [];

    double? parseLimitValue(dynamic value) {
      if (value == null) return null;
      if (value is num) return value.toDouble();
      if (value is String) return double.tryParse(value);
      return null;
    }

    readings.forEach((key, value) {
      if (value == null || value == -999.0) return;

      final limitName = _parameterKeyToLimitName[key];
      if (limitName == null) return;

      // START MODIFICATION: Only check for station-specific limits
      Map<String, dynamic> limitData = {};

      if (stationId != null) {
        limitData = allLimits.firstWhere(
              (l) => l['param_parameter_list'] == limitName && l['station_id'] == stationId,
          orElse: () => {},
        );
      }
      // END MODIFICATION

      if (limitData.isNotEmpty) {
        final lowerLimit = parseLimitValue(limitData['param_lower_limit']);
        final upperLimit = parseLimitValue(limitData['param_upper_limit']);

        if ((lowerLimit != null && value < lowerLimit) || (upperLimit != null && value > upperLimit)) {
          final valueStr = value.toStringAsFixed(5);
          final lowerStr = lowerLimit?.toStringAsFixed(5) ?? 'N/A';
          final upperStr = upperLimit?.toStringAsFixed(5) ?? 'N/A';
          outOfBoundsMessages.add('- *$limitName*: `$valueStr` (Limit: `$lowerStr` - `$upperStr`)');
        }
      }
    });

    if (outOfBoundsMessages.isEmpty) {
      return "";
    }

    final buffer = StringBuffer()
      ..writeln()
      ..writeln('⚠️ *Parameter Limit Alert:*')
      ..writeln('The following parameters were outside their defined limits:');
    buffer.writeAll(outOfBoundsMessages, '\n');

    return buffer.toString();
  }

  Future<Map<String, dynamic>> submitTarballSample({
    required Map<String, String> formData,
    required Map<String, File?> imageFiles,
    required List<Map<String, dynamic>>? appSettings,
  }) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    final dataResult = await _baseService.post(baseUrl, 'marine/tarball/sample', formData);
    if (dataResult['success'] != true)
      return {'status': 'L1', 'success': false, 'message': 'Failed to submit data: ${dataResult['message']}'};

    final recordId = dataResult['data']?['autoid'];
    if (recordId == null) return {'status': 'L2', 'success': false, 'message': 'Data submitted, but failed to get a record ID.'};

    final filesToUpload = <String, File>{};
    imageFiles.forEach((key, value) {
      if (value != null) filesToUpload[key] = value;
    });

    if (filesToUpload.isEmpty) {
      _handleTarballSuccessAlert(formData, appSettings, isDataOnly: true);
      return {'status': 'L3', 'success': true, 'message': 'Data submitted successfully.', 'reportId': recordId};
    }

    final imageResult = await _baseService.postMultipart(
        baseUrl: baseUrl, endpoint: 'marine/tarball/images', fields: {'autoid': recordId.toString()}, files: filesToUpload);
    if (imageResult['success'] != true) {
      _handleTarballSuccessAlert(formData, appSettings, isDataOnly: true);
      return {
        'status': 'L2',
        'success': false,
        'message': 'Data submitted, but image upload failed: ${imageResult['message']}',
        'reportId': recordId
      };
    }

    _handleTarballSuccessAlert(formData, appSettings, isDataOnly: false);
    return {'status': 'L3', 'success': true, 'message': 'Data and images submitted successfully.', 'reportId': recordId};
  }

  Future<void> _handleTarballSuccessAlert(
      Map<String, String> formData, List<Map<String, dynamic>>? appSettings, {required bool isDataOnly}) async {
    debugPrint("Triggering Telegram alert logic...");
    try {
      final message = _generateTarballAlertMessage(formData, isDataOnly: isDataOnly);
      final bool wasSent = await _telegramService.sendAlertImmediately('marine_tarball', message, appSettings);
      if (!wasSent) {
        await _telegramService.queueMessage('marine_tarball', message, appSettings);
      }
    } catch (e) {
      debugPrint("Failed to handle Tarball Telegram alert: $e");
    }
  }

  String _generateTarballAlertMessage(Map<String, String> formData, {required bool isDataOnly}) {
    final submissionType = isDataOnly ? "(Data Only)" : "(Data & Images)";
    final stationName = formData['tbl_station_name'] ?? 'N/A';
    final stationCode = formData['tbl_station_code'] ?? 'N/A';
    final classification = formData['classification_name'] ?? formData['classification_id'] ?? 'N/A';

    final buffer = StringBuffer()
      ..writeln('✅ *Tarball Sample $submissionType Submitted:*')
      ..writeln()
      ..writeln('*Station Name & Code:* $stationName ($stationCode)')
      ..writeln('*Date of Submission:* ${formData['sampling_date']}')
      ..writeln('*Submitted by User:* ${formData['first_sampler_name'] ?? 'N/A'}')
      ..writeln('*Classification:* $classification')
      ..writeln('*Status of Submission:* Successful');

    if (formData['distance_difference'] != null &&
        double.tryParse(formData['distance_difference']!) != null &&
        double.parse(formData['distance_difference']!) > 0) {
      buffer
        ..writeln()
        ..writeln('🔔 *Alert:*')
        ..writeln('*Distance from station:* ${(double.parse(formData['distance_difference']!) * 1000).toStringAsFixed(0)} meters');

      if (formData['distance_difference_remarks'] != null && formData['distance_difference_remarks']!.isNotEmpty) {
        buffer.writeln('*Remarks for distance:* ${formData['distance_difference_remarks']}');
      }
    }

    return buffer.toString();
  }
}

class RiverApiService {
  final BaseApiService _baseService;
  final TelegramService _telegramService;
  final ServerConfigService _serverConfigService;
  final DatabaseHelper _dbHelper;

  RiverApiService(this._baseService, this._telegramService, this._serverConfigService, this._dbHelper);

  Future<Map<String, dynamic>> getManualStations() async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.get(baseUrl, 'river/manual-stations');
  }

  Future<Map<String, dynamic>> getTriennialStations() async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    return _baseService.get(baseUrl, 'river/triennial-stations');
  }

  Future<Map<String, dynamic>> getRiverSamplingImages({
    required int stationId,
    required DateTime samplingDate,
    required String samplingType,
  }) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    final String dateStr = DateFormat('yyyy-MM-dd').format(samplingDate);
    final String endpoint = 'river/manual/images-by-station?station_id=$stationId&date=$dateStr';

    debugPrint("ApiService: Calling river image request API endpoint: $endpoint");

    final response = await _baseService.get(baseUrl, endpoint);

    // The backend now returns the data directly, so we just pass the response along.
    return response;
  }

  Future<Map<String, dynamic>> sendImageRequestEmail({
    required String recipientEmail,
    required List<String> imageUrls,
    required String stationName,
    required String samplingDate,
  }) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    final Map<String, String> fields = {
      'recipientEmail': recipientEmail,
      'imageUrls': jsonEncode(imageUrls),
      'stationName': stationName,
      'samplingDate': samplingDate,
    };

    return _baseService.postMultipart(
      baseUrl: baseUrl,
      endpoint: 'river/images/send-email', // Endpoint for river email requests
      fields: fields,
      files: {},
    );
  }

  Future<Map<String, dynamic>> submitInSituSample({
    required Map<String, String> formData,
    required Map<String, File?> imageFiles,
    required List<Map<String, dynamic>>? appSettings,
  }) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    final dataResult = await _baseService.post(baseUrl, 'river/manual/sample', formData);

    if (dataResult['success'] != true) {
      return {
        'status': 'L1',
        'success': false,
        'message': 'Failed to submit river in-situ data: ${dataResult['message']}',
        'reportId': null
      };
    }

    final recordId = dataResult['data']?['r_man_id'];
    if (recordId == null) {
      return {
        'status': 'L2',
        'success': false,
        'message': 'Data submitted, but failed to get a record ID for images.',
        'reportId': null
      };
    }

    final filesToUpload = <String, File>{};
    imageFiles.forEach((key, value) {
      if (value != null) filesToUpload[key] = value;
    });

    if (filesToUpload.isEmpty) {
      _handleInSituSuccessAlert(formData, appSettings, isDataOnly: true);
      return {
        'status': 'L3',
        'success': true,
        'message': 'Data submitted successfully. No images were attached.',
        'reportId': recordId.toString()
      };
    }

    final imageResult = await _baseService.postMultipart(
      baseUrl: baseUrl,
      endpoint: 'river/manual/images', // Separate endpoint for images
      fields: {'r_man_id': recordId.toString()}, // Link images to the submitted record ID
      files: filesToUpload,
    );

    if (imageResult['success'] != true) {
      return {
        'status': 'L2',
        'success': false,
        'message': 'Data submitted, but image upload failed: ${imageResult['message']}',
        'reportId': recordId.toString()
      };
    }

    _handleInSituSuccessAlert(formData, appSettings, isDataOnly: false);
    return {
      'status': 'L3',
      'success': true,
      'message': 'Data and images submitted successfully.',
      'reportId': recordId.toString()
    };
  }

  Future<Map<String, dynamic>> submitTriennialSample({
    required Map<String, String> formData,
    required Map<String, File?> imageFiles,
    required List<Map<String, dynamic>>? appSettings,
  }) async {
    final baseUrl = await _serverConfigService.getActiveApiUrl();
    final dataResult = await _baseService.post(baseUrl, 'river/triennial/sample', formData);

    if (dataResult['success'] != true) {
      return {
        'status': 'L1',
        'success': false,
        'message': 'Failed to submit triennial data: ${dataResult['message']}',
        'reportId': null
      };
    }

    final recordId = dataResult['data']?['r_tri_id'];
    if (recordId == null) {
      return {
        'status': 'L2',
        'success': false,
        'message': 'Data submitted, but failed to get a record ID for images.',
        'reportId': null
      };
    }

    final filesToUpload = <String, File>{};
    imageFiles.forEach((key, value) {
      if (value != null) filesToUpload[key] = value;
    });

    if (filesToUpload.isEmpty) {
      _handleTriennialSuccessAlert(formData, appSettings, isDataOnly: true);
      return {
        'status': 'L3',
        'success': true,
        'message': 'Triennial data submitted successfully. No images were attached.',
        'reportId': recordId.toString()
      };
    }

    final imageResult = await _baseService.postMultipart(
      baseUrl: baseUrl,
      endpoint: 'river/triennial/images',
      fields: {'r_tri_id': recordId.toString()},
      files: filesToUpload,
    );

    if (imageResult['success'] != true) {
      return {
        'status': 'L2',
        'success': false,
        'message': 'Data submitted, but image upload failed: ${imageResult['message']}',
        'reportId': recordId.toString()
      };
    }

    _handleTriennialSuccessAlert(formData, appSettings, isDataOnly: false);
    return {
      'status': 'S4',
      'success': true,
      'message': 'Triennial data and images submitted successfully.',
      'reportId': recordId.toString()
    };
  }

  Future<void> _handleTriennialSuccessAlert(
      Map<String, String> formData, List<Map<String, dynamic>>? appSettings, {required bool isDataOnly}) async {
    try {
      final submissionType = isDataOnly ? "(Data Only)" : "(Data & Images)";
      final stationName = formData['r_tri_station_name'] ?? 'N/A';
      final stationCode = formData['r_tri_station_code'] ?? 'N/A';

      final message = '✅ *River Triennial Sample ${submissionType} Submitted:*\n\n'
          '*Station:* $stationName ($stationCode)\n'
          '*Date:* ${formData['r_tri_date']}\n'
          '*User:* ${formData['first_sampler_name'] ?? 'N/A'}';

      final bool wasSent = await _telegramService.sendAlertImmediately('river_triennial', message, appSettings);
      if (!wasSent) {
        await _telegramService.queueMessage('river_triennial', message, appSettings);
      }
    } catch (e) {
      debugPrint("Failed to handle River Triennial Telegram alert: $e");
    }
  }

  Future<void> _handleInSituSuccessAlert(
      Map<String, String> formData, List<Map<String, dynamic>>? appSettings, {required bool isDataOnly}) async {
    try {
      final String message = await _generateInSituAlertMessage(formData, isDataOnly: isDataOnly);
      final bool wasSent = await _telegramService.sendAlertImmediately('river_in_situ', message, appSettings);
      if (!wasSent) {
        await _telegramService.queueMessage('river_in_situ', message, appSettings);
      }
    } catch (e) {
      debugPrint("Failed to handle River Telegram alert: $e");
    }
  }

  Future<String> _generateInSituAlertMessage(Map<String, String> formData, {required bool isDataOnly}) async {
    final submissionType = isDataOnly ? "(Data Only)" : "(Data & Images)";
    final stationName = formData['r_man_station_name'] ?? 'N/A';
    final stationCode = formData['r_man_station_code'] ?? 'N/A';
    final submissionDate = formData['r_man_date'] ?? DateFormat('yyyy-MM-dd').format(DateTime.now());
    final submitter = formData['first_sampler_name'] ?? 'N/A';
    final sondeID = formData['r_man_sondeID'] ?? 'N/A';
    final distanceKm = double.tryParse(formData['r_man_distance_difference'] ?? '0') ?? 0;
    final distanceMeters = (distanceKm * 1000).toStringAsFixed(0);
    final distanceRemarks = formData['r_man_distance_difference_remarks'] ?? 'N/A';

    final buffer = StringBuffer()
      ..writeln('✅ *River In-Situ Sample ${submissionType} Submitted:*')
      ..writeln()
      ..writeln('*Station Name & Code:* $stationName ($stationCode)')
      ..writeln('*Date of Submitted:* $submissionDate')
      ..writeln('*Submitted by User:* $submitter')
      ..writeln('*Sonde ID:* $sondeID')
      ..writeln('*Status of Submission:* Successful');

    if (distanceKm > 0 || (distanceRemarks.isNotEmpty && distanceRemarks != 'N/A')) {
      buffer
        ..writeln()
        ..writeln('🔔 *Distance Alert:*')
        ..writeln('*Distance from station:* $distanceMeters meters');
      if (distanceRemarks.isNotEmpty && distanceRemarks != 'N/A') {
        buffer.writeln('*Remarks for distance:* $distanceRemarks');
      }
    }

    final outOfBoundsAlert = await _getOutOfBoundsAlertSection(formData);
    if (outOfBoundsAlert.isNotEmpty) {
      buffer.write(outOfBoundsAlert);
    }

    return buffer.toString();
  }

  Future<String> _getOutOfBoundsAlertSection(Map<String, String> formData) async {
    const Map<String, String> _formKeyToLimitName = {
      'r_man_ph': 'pH',
      'r_man_temperature': 'Temperature',
      'r_man_dissolved_oxygen': 'Dissolved Oxygen',
      'r_man_conductivity': 'Conductivity',
      'r_man_salinity': 'Salinity',
      'r_man_turbidity': 'Turbidity',
      'r_man_tds': 'TDS',
      'r_man_sonde_battery': 'Sonde Battery',
    };

    final allLimits = await _dbHelper.loadRiverParameterLimits() ?? [];
    if (allLimits.isEmpty) return "";

    final int? stationId = int.tryParse(formData['r_man_station_id'] ?? '');
    final List<String> outOfBoundsMessages = [];

    double? parseLimitValue(dynamic value) {
      if (value == null) return null;
      if (value is num) return value.toDouble();
      if (value is String) return double.tryParse(value);
      return null;
    }

    formData.forEach((key, valueStr) {
      final double? value = double.tryParse(valueStr);
      if (value == null || value == -999.0) return;

      final limitName = _formKeyToLimitName[key];
      if (limitName == null) return;

      Map<String, dynamic> limitData = {};
      if (stationId != null) {
        limitData = allLimits.firstWhere(
              (l) => l['param_parameter_list'] == limitName && l['r_man_station_id'] == stationId,
          orElse: () => {},
        );
      }
      if (limitData.isEmpty) {
        limitData = allLimits.firstWhere(
              (l) => l['param_parameter_list'] == limitName && l['r_man_station_id'] == null,
          orElse: () => {},
        );
      }

      if (limitData.isNotEmpty) {
        final lowerLimit = parseLimitValue(limitData['param_lower_limit']);
        final upperLimit = parseLimitValue(limitData['param_upper_limit']);

        if ((lowerLimit != null && value < lowerLimit) || (upperLimit != null && value > upperLimit)) {
          final valueFmt = value.toStringAsFixed(5);
          final lowerFmt = lowerLimit?.toStringAsFixed(5) ?? 'N/A';
          final upperFmt = upperLimit?.toStringAsFixed(5) ?? 'N/A';
          outOfBoundsMessages.add('- *$limitName*: `$valueFmt` (Limit: `$lowerFmt` - `$upperFmt`)');
        }
      }
    });

    if (outOfBoundsMessages.isEmpty) return "";

    final buffer = StringBuffer()
      ..writeln()
      ..writeln('⚠️ *Parameter Limit Alert:*')
      ..writeln('The following parameters were outside their defined limits:');
    buffer.writeAll(outOfBoundsMessages, '\n');

    return buffer.toString();
  }
}

// =======================================================================
// Part 3: Local Database Helper (Refactored for Delta Sync)
// =======================================================================

class DatabaseHelper {
  static Database? _database;
  static const String _dbName = 'app_data.db';
  static const int _dbVersion = 23;
  static const String _profileTable = 'user_profile';
  static const String _usersTable = 'all_users';
  static const String _tarballStationsTable = 'marine_tarball_stations';
  static const String _manualStationsTable = 'marine_manual_stations';
  static const String _riverManualStationsTable = 'river_manual_stations';
  static const String _riverTriennialStationsTable = 'river_triennial_stations';
  static const String _tarballClassificationsTable = 'marine_tarball_classifications';
  static const String _departmentsTable = 'departments';
  static const String _companiesTable = 'companies';
  static const String _positionsTable = 'positions';
  static const String _alertQueueTable = 'alert_queue';
  static const String _airManualStationsTable = 'air_manual_stations';
  static const String _airClientsTable = 'air_clients';
  static const String _statesTable = 'states';
  static const String _appSettingsTable = 'app_settings';
  static const String _parameterLimitsTable = 'manual_parameter_limits';
  static const String _npeParameterLimitsTable = 'npe_parameter_limits';
  static const String _marineParameterLimitsTable = 'marine_parameter_limits';
  static const String _riverParameterLimitsTable = 'river_parameter_limits';
  static const String _apiConfigsTable = 'api_configurations';
  static const String _ftpConfigsTable = 'ftp_configurations';
  static const String _retryQueueTable = 'retry_queue';
  static const String _submissionLogTable = 'submission_log';
  static const String _documentsTable = 'documents';

  static const String _modulePreferencesTable = 'module_preferences';
  static const String _moduleApiLinksTable = 'module_api_links';
  static const String _moduleFtpLinksTable = 'module_ftp_links';

  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDB();
    return _database!;
  }

  Future<Database> _initDB() async {
    String dbPath = p.join(await getDatabasesPath(), _dbName);
    return await openDatabase(dbPath, version: _dbVersion, onCreate: _onCreate, onUpgrade: _onUpgrade);
  }

  Future _onCreate(Database db, int version) async {
    await db.execute('CREATE TABLE $_profileTable(user_id INTEGER PRIMARY KEY, profile_json TEXT)');
    await db.execute('''
      CREATE TABLE $_usersTable(
        user_id INTEGER PRIMARY KEY,
        email TEXT UNIQUE,
        password_hash TEXT,
        user_json TEXT
      )
    ''');
    await db.execute('CREATE TABLE $_tarballStationsTable(station_id INTEGER PRIMARY KEY, station_json TEXT)');
    await db.execute('CREATE TABLE $_manualStationsTable(station_id INTEGER PRIMARY KEY, station_json TEXT)');
    await db.execute('CREATE TABLE $_riverManualStationsTable(station_id INTEGER PRIMARY KEY, station_json TEXT)');
    await db.execute('CREATE TABLE $_riverTriennialStationsTable(station_id INTEGER PRIMARY KEY, station_json TEXT)');
    await db.execute('CREATE TABLE $_tarballClassificationsTable(classification_id INTEGER PRIMARY KEY, classification_json TEXT)');
    await db.execute('CREATE TABLE $_departmentsTable(department_id INTEGER PRIMARY KEY, department_json TEXT)');
    await db.execute('CREATE TABLE $_companiesTable(company_id INTEGER PRIMARY KEY, company_json TEXT)');
    await db.execute('CREATE TABLE $_positionsTable(position_id INTEGER PRIMARY KEY, position_json TEXT)');
    await db.execute('''CREATE TABLE $_alertQueueTable (id INTEGER PRIMARY KEY AUTOINCREMENT, chat_id TEXT NOT NULL, message TEXT NOT NULL, created_at TEXT NOT NULL)''');
    await db.execute('CREATE TABLE $_airManualStationsTable(station_id INTEGER PRIMARY KEY, station_json TEXT)');
    await db.execute('CREATE TABLE $_airClientsTable(client_id INTEGER PRIMARY KEY, client_json TEXT)');
    await db.execute('CREATE TABLE $_statesTable(state_id INTEGER PRIMARY KEY, state_json TEXT)');
    await db.execute('CREATE TABLE $_appSettingsTable(setting_id INTEGER PRIMARY KEY, setting_json TEXT)');
    await db.execute('CREATE TABLE $_parameterLimitsTable(param_autoid INTEGER PRIMARY KEY, limit_json TEXT)');
    await db.execute('CREATE TABLE $_npeParameterLimitsTable(param_autoid INTEGER PRIMARY KEY, limit_json TEXT)');
    await db.execute('CREATE TABLE $_marineParameterLimitsTable(param_autoid INTEGER PRIMARY KEY, limit_json TEXT)');
    await db.execute('CREATE TABLE $_riverParameterLimitsTable(param_autoid INTEGER PRIMARY KEY, limit_json TEXT)');
    await db.execute('CREATE TABLE $_apiConfigsTable(api_config_id INTEGER PRIMARY KEY, config_json TEXT)');
    await db.execute('CREATE TABLE $_ftpConfigsTable(ftp_config_id INTEGER PRIMARY KEY, config_json TEXT)');
    await db.execute('''
      CREATE TABLE $_retryQueueTable(
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        type TEXT NOT NULL,
        endpoint_or_path TEXT NOT NULL,
        payload TEXT,
        timestamp TEXT NOT NULL,
        status TEXT NOT NULL
      )
    ''');
    await db.execute('''
      CREATE TABLE $_submissionLogTable (
        submission_id TEXT PRIMARY KEY,
        module TEXT NOT NULL,
        type TEXT NOT NULL,
        status TEXT NOT NULL,
        message TEXT,
        report_id TEXT,
        created_at TEXT NOT NULL,
        form_data TEXT,
        image_data TEXT,
        server_name TEXT,
        api_status TEXT,
        ftp_status TEXT
      )
    ''');
    await db.execute('''
      CREATE TABLE $_modulePreferencesTable (
        module_name TEXT PRIMARY KEY,
        is_api_enabled INTEGER NOT NULL DEFAULT 1,
        is_ftp_enabled INTEGER NOT NULL DEFAULT 1
      )
    ''');
    await db.execute('''
      CREATE TABLE $_moduleApiLinksTable (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        module_name TEXT NOT NULL,
        api_config_id INTEGER NOT NULL,
        is_enabled INTEGER NOT NULL DEFAULT 1
      )
    ''');
    await db.execute('''
      CREATE TABLE $_moduleFtpLinksTable (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        module_name TEXT NOT NULL,
        ftp_config_id INTEGER NOT NULL,
        is_enabled INTEGER NOT NULL DEFAULT 1
      )
    ''');
    await db.execute('CREATE TABLE $_documentsTable(id INTEGER PRIMARY KEY, document_json TEXT)');
  }

  Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
    if (oldVersion < 11) {
      await db.execute('CREATE TABLE IF NOT EXISTS $_airManualStationsTable(station_id INTEGER PRIMARY KEY, station_json TEXT)');
      await db.execute('CREATE TABLE IF NOT EXISTS $_airClientsTable(client_id INTEGER PRIMARY KEY, client_json TEXT)');
    }
    if (oldVersion < 12) {
      await db.execute('CREATE TABLE IF NOT EXISTS $_statesTable(state_id INTEGER PRIMARY KEY, state_json TEXT)');
    }
    if (oldVersion < 13) {
      await db.execute('CREATE TABLE IF NOT EXISTS $_appSettingsTable(setting_id INTEGER PRIMARY KEY, setting_json TEXT)');
      await db.execute('CREATE TABLE IF NOT EXISTS $_parameterLimitsTable(param_autoid INTEGER PRIMARY KEY, limit_json TEXT)');
    }
    if (oldVersion < 16) {
      await db.execute('CREATE TABLE IF NOT EXISTS $_apiConfigsTable(api_config_id INTEGER PRIMARY KEY, config_json TEXT)');
      await db.execute('CREATE TABLE IF NOT EXISTS $_ftpConfigsTable(ftp_config_id INTEGER PRIMARY KEY, config_json TEXT)');
    }
    if (oldVersion < 17) {
      await db.execute('''
        CREATE TABLE IF NOT EXISTS $_retryQueueTable(
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          type TEXT NOT NULL,
          endpoint_or_path TEXT NOT NULL,
          payload TEXT,
          timestamp TEXT NOT NULL,
          status TEXT NOT NULL
        )
      ''');
    }
    if (oldVersion < 18) {
      await db.execute('''
        CREATE TABLE IF NOT EXISTS $_submissionLogTable (
          submission_id TEXT PRIMARY KEY,
          module TEXT NOT NULL,
          type TEXT NOT NULL,
          status TEXT NOT NULL,
          message TEXT,
          report_id TEXT,
          created_at TEXT NOT NULL,
          form_data TEXT,
          image_data TEXT,
          server_name TEXT
        )
      ''');
    }
    if (oldVersion < 19) {
      try {
        await db.execute("ALTER TABLE $_submissionLogTable ADD COLUMN api_status TEXT");
        await db.execute("ALTER TABLE $_submissionLogTable ADD COLUMN ftp_status TEXT");
      } catch (_) {}
      await db.execute('''
        CREATE TABLE IF NOT EXISTS $_modulePreferencesTable (
          module_name TEXT PRIMARY KEY,
          is_api_enabled INTEGER NOT NULL DEFAULT 1,
          is_ftp_enabled INTEGER NOT NULL DEFAULT 1
        )
      ''');
      await db.execute('''
        CREATE TABLE IF NOT EXISTS $_moduleApiLinksTable (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          module_name TEXT NOT NULL,
          api_config_id INTEGER NOT NULL,
          is_enabled INTEGER NOT NULL DEFAULT 1
        )
      ''');
      await db.execute('''
        CREATE TABLE IF NOT EXISTS $_moduleFtpLinksTable (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          module_name TEXT NOT NULL,
          ftp_config_id INTEGER NOT NULL,
          is_enabled INTEGER NOT NULL DEFAULT 1
        )
      ''');
    }
    if (oldVersion < 20) {
      await db.execute('CREATE TABLE IF NOT EXISTS $_documentsTable(id INTEGER PRIMARY KEY, document_json TEXT)');
    }

    if (oldVersion < 21) {
      try {
        await db.execute("ALTER TABLE $_usersTable ADD COLUMN email TEXT");
      } catch (e) {
        debugPrint("Upgrade warning: Failed to add email column to users table (may already exist): $e");
      }
      try {
        await db.execute("ALTER TABLE $_usersTable ADD COLUMN password_hash TEXT");
      } catch (e) {
        debugPrint("Upgrade warning: Failed to add password_hash column to users table (may already exist): $e");
      }
    }
    if (oldVersion < 23) {
      await db.execute('CREATE TABLE IF NOT EXISTS $_npeParameterLimitsTable(param_autoid INTEGER PRIMARY KEY, limit_json TEXT)');
      await db.execute('CREATE TABLE IF NOT EXISTS $_marineParameterLimitsTable(param_autoid INTEGER PRIMARY KEY, limit_json TEXT)');
      await db.execute('CREATE TABLE IF NOT EXISTS $_riverParameterLimitsTable(param_autoid INTEGER PRIMARY KEY, limit_json TEXT)');
    }
  }

  Future<void> _upsertData(String table, String idKeyName, List<Map<String, dynamic>> data, String jsonKeyName) async {
    if (data.isEmpty) return;
    final db = await database;
    final batch = db.batch();
    for (var item in data) {
      batch.insert(
        table,
        {idKeyName: item[idKeyName], '${jsonKeyName}_json': jsonEncode(item)},
        conflictAlgorithm: ConflictAlgorithm.replace,
      );
    }
    await batch.commit(noResult: true);
    debugPrint("Upserted ${data.length} items into $table");
  }

  Future<void> _deleteData(String table, String idKeyName, List<dynamic> ids) async {
    if (ids.isEmpty) return;
    final db = await database;
    final placeholders = List.filled(ids.length, '?').join(', ');
    await db.delete(
      table,
      where: '$idKeyName IN ($placeholders)',
      whereArgs: ids,
    );
    debugPrint("Deleted ${ids.length} items from $table");
  }

  Future<List<Map<String, dynamic>>?> _loadData(String table, String jsonKey) async {
    final db = await database;
    final List<Map<String, dynamic>> maps = await db.query(table);
    if (maps.isNotEmpty) {
      return maps.map((map) => jsonDecode(map['${jsonKey}_json']) as Map<String, dynamic>).toList();
    }
    return null;
  }

  Future<void> saveProfile(Map<String, dynamic> profile) async {
    final db = await database;
    await db.insert(_profileTable, {'user_id': profile['user_id'], 'profile_json': jsonEncode(profile)},
        conflictAlgorithm: ConflictAlgorithm.replace);
  }

  Future<Map<String, dynamic>?> loadProfile() async {
    final db = await database;
    final List<Map<String, dynamic>> maps = await db.query(_profileTable);
    if (maps.isNotEmpty) return jsonDecode(maps.first['profile_json']);
    return null;
  }

  Future<Map<String, dynamic>?> loadProfileByEmail(String email) async {
    final db = await database;
    final List<Map<String, dynamic>> maps = await db.query(
      _usersTable,
      columns: ['user_json'],
      where: 'email = ?',
      whereArgs: [email],
    );
    if (maps.isNotEmpty) {
      try {
        return jsonDecode(maps.first['user_json']) as Map<String, dynamic>;
      } catch (e) {
        debugPrint("Error decoding profile for email $email: $e");
        return null;
      }
    }
    return null;
  }

  Future<void> upsertUserWithCredentials({
    required Map<String, dynamic> profile,
    required String passwordHash,
  }) async {
    final db = await database;
    await db.insert(
      _usersTable,
      {
        'user_id': profile['user_id'],
        'email': profile['email'],
        'password_hash': passwordHash,
        'user_json': jsonEncode(profile)
      },
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
    debugPrint("Upserted user credentials for ${profile['email']}");
  }

  Future<String?> getUserPasswordHashByEmail(String email) async {
    final db = await database;
    final List<Map<String, dynamic>> result = await db.query(
      _usersTable,
      columns: ['password_hash'],
      where: 'email = ?',
      whereArgs: [email],
    );
    if (result.isNotEmpty && result.first['password_hash'] != null) {
      return result.first['password_hash'] as String;
    }
    return null;
  }

  Future<void> upsertUsers(List<Map<String, dynamic>> data) async {
    if (data.isEmpty) return;
    final db = await database;
    for (var item in data) {
      final updateData = {
        'user_json': jsonEncode(item),
      };

      int count = await db.update(
        _usersTable,
        updateData,
        where: 'user_id = ?',
        whereArgs: [item['user_id']],
      );

      if (count == 0) {
        await db.insert(
          _usersTable,
          {
            'user_id': item['user_id'],
            'email': item['email'],
            'user_json': jsonEncode(item),
          },
          conflictAlgorithm: ConflictAlgorithm.ignore,
        );
      }
    }
    debugPrint("Upserted ${data.length} user items in custom upsert method.");
  }

  Future<void> deleteUsers(List<dynamic> ids) => _deleteData(_usersTable, 'user_id', ids);
  Future<List<Map<String, dynamic>>?> loadUsers() => _loadData(_usersTable, 'user');

  Future<void> upsertDocuments(List<Map<String, dynamic>> data) => _upsertData(_documentsTable, 'id', data, 'document');
  Future<void> deleteDocuments(List<dynamic> ids) => _deleteData(_documentsTable, 'id', ids);
  Future<List<Map<String, dynamic>>?> loadDocuments() => _loadData(_documentsTable, 'document');

  Future<void> upsertTarballStations(List<Map<String, dynamic>> data) =>
      _upsertData(_tarballStationsTable, 'station_id', data, 'station');
  Future<void> deleteTarballStations(List<dynamic> ids) => _deleteData(_tarballStationsTable, 'station_id', ids);
  Future<List<Map<String, dynamic>>?> loadTarballStations() => _loadData(_tarballStationsTable, 'station');

  Future<void> upsertManualStations(List<Map<String, dynamic>> data) =>
      _upsertData(_manualStationsTable, 'station_id', data, 'station');
  Future<void> deleteManualStations(List<dynamic> ids) => _deleteData(_manualStationsTable, 'station_id', ids);
  Future<List<Map<String, dynamic>>?> loadManualStations() => _loadData(_manualStationsTable, 'station');

  Future<void> upsertRiverManualStations(List<Map<String, dynamic>> data) =>
      _upsertData(_riverManualStationsTable, 'station_id', data, 'station');
  Future<void> deleteRiverManualStations(List<dynamic> ids) => _deleteData(_riverManualStationsTable, 'station_id', ids);
  Future<List<Map<String, dynamic>>?> loadRiverManualStations() => _loadData(_riverManualStationsTable, 'station');

  Future<void> upsertRiverTriennialStations(List<Map<String, dynamic>> data) =>
      _upsertData(_riverTriennialStationsTable, 'station_id', data, 'station');
  Future<void> deleteRiverTriennialStations(List<dynamic> ids) => _deleteData(_riverTriennialStationsTable, 'station_id', ids);
  Future<List<Map<String, dynamic>>?> loadRiverTriennialStations() => _loadData(_riverTriennialStationsTable, 'station');

  Future<void> upsertTarballClassifications(List<Map<String, dynamic>> data) =>
      _upsertData(_tarballClassificationsTable, 'classification_id', data, 'classification');
  Future<void> deleteTarballClassifications(List<dynamic> ids) => _deleteData(_tarballClassificationsTable, 'classification_id', ids);
  Future<List<Map<String, dynamic>>?> loadTarballClassifications() => _loadData(_tarballClassificationsTable, 'classification');

  Future<void> upsertDepartments(List<Map<String, dynamic>> data) => _upsertData(_departmentsTable, 'department_id', data, 'department');
  Future<void> deleteDepartments(List<dynamic> ids) => _deleteData(_departmentsTable, 'department_id', ids);
  Future<List<Map<String, dynamic>>?> loadDepartments() => _loadData(_departmentsTable, 'department');

  Future<void> upsertCompanies(List<Map<String, dynamic>> data) => _upsertData(_companiesTable, 'company_id', data, 'company');
  Future<void> deleteCompanies(List<dynamic> ids) => _deleteData(_companiesTable, 'company_id', ids);
  Future<List<Map<String, dynamic>>?> loadCompanies() => _loadData(_companiesTable, 'company');

  Future<void> upsertPositions(List<Map<String, dynamic>> data) => _upsertData(_positionsTable, 'position_id', data, 'position');
  Future<void> deletePositions(List<dynamic> ids) => _deleteData(_positionsTable, 'position_id', ids);
  Future<List<Map<String, dynamic>>?> loadPositions() => _loadData(_positionsTable, 'position');

  Future<void> upsertAirManualStations(List<Map<String, dynamic>> data) =>
      _upsertData(_airManualStationsTable, 'station_id', data, 'station');
  Future<void> deleteAirManualStations(List<dynamic> ids) => _deleteData(_airManualStationsTable, 'station_id', ids);
  Future<List<Map<String, dynamic>>?> loadAirManualStations() => _loadData(_airManualStationsTable, 'station');

  Future<void> upsertAirClients(List<Map<String, dynamic>> data) => _upsertData(_airClientsTable, 'client_id', data, 'client');
  Future<void> deleteAirClients(List<dynamic> ids) => _deleteData(_airClientsTable, 'client_id', ids);
  Future<List<Map<String, dynamic>>?> loadAirClients() => _loadData(_airClientsTable, 'client');

  Future<void> upsertStates(List<Map<String, dynamic>> data) => _upsertData(_statesTable, 'state_id', data, 'state');
  Future<void> deleteStates(List<dynamic> ids) => _deleteData(_statesTable, 'state_id', ids);
  Future<List<Map<String, dynamic>>?> loadStates() => _loadData(_statesTable, 'state');

  Future<void> upsertAppSettings(List<Map<String, dynamic>> data) => _upsertData(_appSettingsTable, 'setting_id', data, 'setting');
  Future<void> deleteAppSettings(List<dynamic> ids) => _deleteData(_appSettingsTable, 'setting_id', ids);
  Future<List<Map<String, dynamic>>?> loadAppSettings() => _loadData(_appSettingsTable, 'setting');

  Future<void> upsertParameterLimits(List<Map<String, dynamic>> data) => _upsertData(_parameterLimitsTable, 'param_autoid', data, 'limit');
  Future<void> deleteParameterLimits(List<dynamic> ids) => _deleteData(_parameterLimitsTable, 'param_autoid', ids);
  Future<List<Map<String, dynamic>>?> loadParameterLimits() => _loadData(_parameterLimitsTable, 'limit');

  Future<void> upsertNpeParameterLimits(List<Map<String, dynamic>> data) => _upsertData(_npeParameterLimitsTable, 'param_autoid', data, 'limit');
  Future<void> deleteNpeParameterLimits(List<dynamic> ids) => _deleteData(_npeParameterLimitsTable, 'param_autoid', ids);
  Future<List<Map<String, dynamic>>?> loadNpeParameterLimits() => _loadData(_npeParameterLimitsTable, 'limit');

  Future<void> upsertMarineParameterLimits(List<Map<String, dynamic>> data) => _upsertData(_marineParameterLimitsTable, 'param_autoid', data, 'limit');
  Future<void> deleteMarineParameterLimits(List<dynamic> ids) => _deleteData(_marineParameterLimitsTable, 'param_autoid', ids);
  Future<List<Map<String, dynamic>>?> loadMarineParameterLimits() => _loadData(_marineParameterLimitsTable, 'limit');

  Future<void> upsertRiverParameterLimits(List<Map<String, dynamic>> data) => _upsertData(_riverParameterLimitsTable, 'param_autoid', data, 'limit');
  Future<void> deleteRiverParameterLimits(List<dynamic> ids) => _deleteData(_riverParameterLimitsTable, 'param_autoid', ids);
  Future<List<Map<String, dynamic>>?> loadRiverParameterLimits() => _loadData(_riverParameterLimitsTable, 'limit');

  Future<void> upsertApiConfigs(List<Map<String, dynamic>> data) => _upsertData(_apiConfigsTable, 'api_config_id', data, 'config');
  Future<void> deleteApiConfigs(List<dynamic> ids) => _deleteData(_apiConfigsTable, 'api_config_id', ids);
  Future<List<Map<String, dynamic>>?> loadApiConfigs() => _loadData(_apiConfigsTable, 'config');

  Future<void> upsertFtpConfigs(List<Map<String, dynamic>> data) => _upsertData(_ftpConfigsTable, 'ftp_config_id', data, 'config');
  Future<void> deleteFtpConfigs(List<dynamic> ids) => _deleteData(_ftpConfigsTable, 'ftp_config_id', ids);
  Future<List<Map<String, dynamic>>?> loadFtpConfigs() => _loadData(_ftpConfigsTable, 'config');

  Future<int> queueFailedRequest(Map<String, dynamic> data) async {
    final db = await database;
    return await db.insert(_retryQueueTable, data, conflictAlgorithm: ConflictAlgorithm.replace);
  }

  Future<List<Map<String, dynamic>>> getPendingRequests() async {
    final db = await database;
    return await db.query(_retryQueueTable, where: 'status = ?', whereArgs: ['pending']);
  }

  Future<Map<String, dynamic>?> getRequestById(int id) async {
    final db = await database;
    final results = await db.query(_retryQueueTable, where: 'id = ?', whereArgs: [id]);
    return results.isNotEmpty ? results.first : null;
  }

  Future<void> deleteRequestFromQueue(int id) async {
    final db = await database;
    await db.delete(_retryQueueTable, where: 'id = ?', whereArgs: [id]);
  }

  Future<void> saveSubmissionLog(Map<String, dynamic> data) async {
    final db = await database;
    await db.insert(
      _submissionLogTable,
      data,
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  Future<List<Map<String, dynamic>>?> loadSubmissionLogs({String? module}) async {
    final db = await database;
    List<Map<String, dynamic>> maps;

    if (module != null && module.isNotEmpty) {
      maps = await db.query(
        _submissionLogTable,
        where: 'module = ?',
        whereArgs: [module],
        orderBy: 'created_at DESC',
      );
    } else {
      maps = await db.query(
        _submissionLogTable,
        orderBy: 'created_at DESC',
      );
    }

    if (maps.isNotEmpty) return maps;
    return null;
  }

  Future<void> saveModulePreference({
    required String moduleName,
    required bool isApiEnabled,
    required bool isFtpEnabled,
  }) async {
    final db = await database;
    await db.insert(
      _modulePreferencesTable,
      {
        'module_name': moduleName,
        'is_api_enabled': isApiEnabled ? 1 : 0,
        'is_ftp_enabled': isFtpEnabled ? 1 : 0,
      },
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  Future<Map<String, dynamic>?> getModulePreference(String moduleName) async {
    final db = await database;
    final result = await db.query(
      _modulePreferencesTable,
      where: 'module_name = ?',
      whereArgs: [moduleName],
    );
    if (result.isNotEmpty) {
      final row = result.first;
      return {
        'module_name': row['module_name'],
        'is_api_enabled': (row['is_api_enabled'] as int) == 1,
        'is_ftp_enabled': (row['is_ftp_enabled'] as int) == 1,
      };
    }
    return null;
  }

  Future<void> saveApiLinksForModule(String moduleName, List<Map<String, dynamic>> links) async {
    final db = await database;
    await db.transaction((txn) async {
      await txn.delete(_moduleApiLinksTable, where: 'module_name = ?', whereArgs: [moduleName]);
      for (final link in links) {
        await txn.insert(_moduleApiLinksTable, {
          'module_name': moduleName,
          'api_config_id': link['api_config_id'],
          'is_enabled': (link['is_enabled'] as bool? ?? true) ? 1 : 0,
        });
      }
    });
  }

  Future<void> saveFtpLinksForModule(String moduleName, List<Map<String, dynamic>> links) async {
    final db = await database;
    await db.transaction((txn) async {
      await txn.delete(_moduleFtpLinksTable, where: 'module_name = ?', whereArgs: [moduleName]);
      for (final link in links) {
        await txn.insert(_moduleFtpLinksTable, {
          'module_name': moduleName,
          'ftp_config_id': link['ftp_config_id'],
          'is_enabled': (link['is_enabled'] as bool? ?? true) ? 1 : 0,
        });
      }
    });
  }

  Future<List<Map<String, dynamic>>> getAllApiLinksForModule(String moduleName) async {
    final db = await database;
    final result = await db.query(_moduleApiLinksTable, where: 'module_name = ?', whereArgs: [moduleName]);
    return result.map((row) => {
      'api_config_id': row['api_config_id'],
      'is_enabled': (row['is_enabled'] as int) == 1,
    }).toList();
  }

  Future<List<Map<String, dynamic>>> getAllFtpLinksForModule(String moduleName) async {
    final db = await database;
    final result = await db.query(_moduleFtpLinksTable, where: 'module_name = ?', whereArgs: [moduleName]);
    return result.map((row) => {
      'ftp_config_id': row['ftp_config_id'],
      'is_enabled': (row['is_enabled'] as int) == 1,
    }).toList();
  }
}