import 'package:flutter/foundation.dart'; import 'package:hive_ce/hive.dart'; import 'package:worker/core/constants/storage_constants.dart'; import 'package:worker/core/database/hive_service.dart'; /// Database Manager for common Hive operations /// /// Provides high-level database operations and utilities for working /// with Hive boxes across the application. /// /// Features: /// - CRUD operations with error handling /// - Cache management with expiration /// - Bulk operations /// - Data validation /// - Sync state tracking class DatabaseManager { DatabaseManager({HiveService? hiveService}) : _hiveService = hiveService ?? HiveService(); final HiveService _hiveService; /// Get a box safely Box _getBox(String boxName) { if (!_hiveService.isBoxOpen(boxName)) { throw HiveError( 'Box $boxName is not open. Initialize HiveService first.', ); } return _hiveService.getBox(boxName); } // ==================== Generic CRUD Operations ==================== /// Save a value to a box Future save({ required String boxName, required String key, required T value, }) async { try { final box = _getBox(boxName); await box.put(key, value); debugPrint('DatabaseManager: Saved $key to $boxName'); } catch (e, stackTrace) { debugPrint('DatabaseManager: Error saving $key to $boxName: $e'); debugPrint('StackTrace: $stackTrace'); rethrow; } } /// Get a value from a box T? get({required String boxName, required String key, T? defaultValue}) { try { final box = _getBox(boxName); return box.get(key, defaultValue: defaultValue); } catch (e, stackTrace) { debugPrint('DatabaseManager: Error getting $key from $boxName: $e'); debugPrint('StackTrace: $stackTrace'); return defaultValue; } } /// Delete a value from a box Future delete({required String boxName, required String key}) async { try { final box = _getBox(boxName); await box.delete(key); debugPrint('DatabaseManager: Deleted $key from $boxName'); } catch (e, stackTrace) { debugPrint('DatabaseManager: Error deleting $key from $boxName: $e'); debugPrint('StackTrace: $stackTrace'); rethrow; } } /// Check if a key exists in a box bool exists({required String boxName, required String key}) { try { final box = _getBox(boxName); return box.containsKey(key); } catch (e) { debugPrint('DatabaseManager: Error checking $key in $boxName: $e'); return false; } } /// Get all values from a box List getAll({required String boxName}) { try { final box = _getBox(boxName); return box.values.toList(); } catch (e, stackTrace) { debugPrint('DatabaseManager: Error getting all from $boxName: $e'); debugPrint('StackTrace: $stackTrace'); return []; } } /// Save multiple values to a box Future saveAll({ required String boxName, required Map entries, }) async { try { final box = _getBox(boxName); await box.putAll(entries); debugPrint('DatabaseManager: Saved ${entries.length} items to $boxName'); } catch (e, stackTrace) { debugPrint('DatabaseManager: Error saving all to $boxName: $e'); debugPrint('StackTrace: $stackTrace'); rethrow; } } /// Clear all data from a box Future clearBox({required String boxName}) async { try { final box = _getBox(boxName); await box.clear(); debugPrint('DatabaseManager: Cleared $boxName'); } catch (e, stackTrace) { debugPrint('DatabaseManager: Error clearing $boxName: $e'); debugPrint('StackTrace: $stackTrace'); rethrow; } } // ==================== Cache Operations ==================== /// Save data to cache with timestamp Future saveToCache({required String key, required T data}) async { try { final cacheBox = _getBox(HiveBoxNames.cacheBox); await cacheBox.put(key, { 'data': data, 'timestamp': DateTime.now().toIso8601String(), }); debugPrint('DatabaseManager: Cached $key'); } catch (e, stackTrace) { debugPrint('DatabaseManager: Error caching $key: $e'); debugPrint('StackTrace: $stackTrace'); rethrow; } } /// Get data from cache /// /// Returns null if cache is expired or doesn't exist T? getFromCache({required String key, Duration? maxAge}) { try { final cacheBox = _getBox(HiveBoxNames.cacheBox); final cachedData = cacheBox.get(key) as Map?; if (cachedData == null) { debugPrint('DatabaseManager: Cache miss for $key'); return null; } // Check if cache is expired if (maxAge != null) { final timestamp = DateTime.parse(cachedData['timestamp'] as String); final age = DateTime.now().difference(timestamp); if (age > maxAge) { debugPrint('DatabaseManager: Cache expired for $key (age: $age)'); return null; } } debugPrint('DatabaseManager: Cache hit for $key'); return cachedData['data'] as T?; } catch (e, stackTrace) { debugPrint('DatabaseManager: Error getting cache $key: $e'); debugPrint('StackTrace: $stackTrace'); return null; } } /// Check if cache is valid (exists and not expired) bool isCacheValid({required String key, Duration? maxAge}) { try { final cacheBox = _getBox(HiveBoxNames.cacheBox); final cachedData = cacheBox.get(key) as Map?; if (cachedData == null) return false; if (maxAge != null) { final timestamp = DateTime.parse(cachedData['timestamp'] as String); final age = DateTime.now().difference(timestamp); return age <= maxAge; } return true; } catch (e) { debugPrint('DatabaseManager: Error checking cache validity $key: $e'); return false; } } /// Clear expired cache entries Future clearExpiredCache() async { try { final cacheBox = _getBox(HiveBoxNames.cacheBox); final keysToDelete = []; for (final key in cacheBox.keys) { final cachedData = cacheBox.get(key) as Map?; if (cachedData != null) { try { final timestamp = DateTime.parse(cachedData['timestamp'] as String); final age = DateTime.now().difference(timestamp); // Use default max age of 24 hours if (age > const Duration(hours: 24)) { keysToDelete.add(key as String); } } catch (e) { // Invalid cache entry, mark for deletion keysToDelete.add(key as String); } } } for (final key in keysToDelete) { await cacheBox.delete(key); } debugPrint( 'DatabaseManager: Cleared ${keysToDelete.length} expired cache entries', ); } catch (e, stackTrace) { debugPrint('DatabaseManager: Error clearing expired cache: $e'); debugPrint('StackTrace: $stackTrace'); } } // ==================== Sync State Operations ==================== /// Update sync timestamp for a data type Future updateSyncTime(String dataType) async { try { final syncBox = _getBox(HiveBoxNames.syncStateBox); await syncBox.put(dataType, DateTime.now().toIso8601String()); debugPrint('DatabaseManager: Updated sync time for $dataType'); } catch (e, stackTrace) { debugPrint('DatabaseManager: Error updating sync time for $dataType: $e'); debugPrint('StackTrace: $stackTrace'); } } /// Get last sync time for a data type DateTime? getLastSyncTime(String dataType) { try { final syncBox = _getBox(HiveBoxNames.syncStateBox); final timestamp = syncBox.get(dataType); if (timestamp == null) return null; return DateTime.parse(timestamp as String); } catch (e) { debugPrint('DatabaseManager: Error getting sync time for $dataType: $e'); return null; } } /// Check if data needs sync bool needsSync({required String dataType, required Duration syncInterval}) { final lastSync = getLastSyncTime(dataType); if (lastSync == null) return true; final timeSinceSync = DateTime.now().difference(lastSync); return timeSinceSync > syncInterval; } // ==================== Settings Operations ==================== /// Save a setting Future saveSetting({required String key, required T value}) async { await save(boxName: HiveBoxNames.settingsBox, key: key, value: value); } /// Get a setting T? getSetting({required String key, T? defaultValue}) { return get( boxName: HiveBoxNames.settingsBox, key: key, defaultValue: defaultValue, ); } // ==================== Offline Queue Operations ==================== /// Add request to offline queue Future addToOfflineQueue(Map request) async { try { final queueBox = _getBox(HiveBoxNames.offlineQueueBox); // Check queue size limit if (queueBox.length >= HiveDatabaseConfig.maxOfflineQueueSize) { debugPrint( 'DatabaseManager: Offline queue is full, removing oldest item', ); await queueBox.deleteAt(0); } await queueBox.add({ ...request, 'timestamp': DateTime.now().toIso8601String(), }); debugPrint('DatabaseManager: Added request to offline queue'); } catch (e, stackTrace) { debugPrint('DatabaseManager: Error adding to offline queue: $e'); debugPrint('StackTrace: $stackTrace'); rethrow; } } /// Get all offline queue items List> getOfflineQueue() { try { final queueBox = _getBox(HiveBoxNames.offlineQueueBox); return queueBox.values .map((e) => Map.from(e as Map)) .toList(); } catch (e) { debugPrint('DatabaseManager: Error getting offline queue: $e'); return []; } } /// Remove item from offline queue Future removeFromOfflineQueue(int index) async { try { final queueBox = _getBox(HiveBoxNames.offlineQueueBox); await queueBox.deleteAt(index); debugPrint('DatabaseManager: Removed item $index from offline queue'); } catch (e, stackTrace) { debugPrint('DatabaseManager: Error removing from offline queue: $e'); debugPrint('StackTrace: $stackTrace'); rethrow; } } /// Clear offline queue Future clearOfflineQueue() async { await clearBox(boxName: HiveBoxNames.offlineQueueBox); } // ==================== Statistics ==================== /// Get database statistics Map getStatistics() { final stats = {}; for (final boxName in HiveBoxNames.allBoxes) { try { if (_hiveService.isBoxOpen(boxName)) { final box = _getBox(boxName); stats[boxName] = {'count': box.length, 'keys': box.keys.length}; } } catch (e) { stats[boxName] = {'error': e.toString()}; } } return stats; } /// Print database statistics void printStatistics() { final stats = getStatistics(); debugPrint('=== Hive Database Statistics ==='); stats.forEach((boxName, data) { debugPrint('$boxName: $data'); }); debugPrint('================================'); } }