/// Gestionnaire de cache multi-niveaux ultra-performant
/// Cache mémoire + disque avec TTL adaptatif selon les rôles
library dashboard_cache_manager;

import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../features/authentication/data/models/user_role.dart';

/// Gestionnaire de cache intelligent avec stratégie multi-niveaux
/// 
/// Niveaux de cache :
/// 1. Cache mémoire (ultra-rapide, volatile)
/// 2. Cache disque (rapide, persistant)
/// 3. Cache réseau (si applicable)
/// 
/// Fonctionnalités :
/// - TTL adaptatif selon le rôle utilisateur
/// - Compression automatique des données volumineuses
/// - Invalidation intelligente
/// - Métriques de performance
/// - Nettoyage automatique
class DashboardCacheManager {
  static final DashboardCacheManager _instance = DashboardCacheManager._internal();
  factory DashboardCacheManager() => _instance;
  DashboardCacheManager._internal();

  /// Cache mémoire niveau 1 (ultra-rapide)
  static final Map<String, _CachedData> _memoryCache = {};
  
  /// Instance SharedPreferences pour le cache disque
  static SharedPreferences? _prefs;
  
  /// Taille maximale du cache mémoire (en nombre d'entrées)
  static const int _maxMemoryCacheSize = 1000;
  
  /// Taille maximale du cache disque (en MB)
  static const int _maxDiskCacheSizeMB = 50;
  
  /// TTL par défaut selon les rôles
  static const Map<UserRole, Duration> _roleTTL = {
    UserRole.superAdmin: Duration(hours: 2),    // Cache plus long pour les admins
    UserRole.orgAdmin: Duration(hours: 1),      // Cache modéré pour les admins org
    UserRole.moderator: Duration(minutes: 30),  // Cache court pour les modérateurs
    UserRole.activeMember: Duration(minutes: 15), // Cache très court pour les membres
    UserRole.simpleMember: Duration(minutes: 10), // Cache minimal
    UserRole.visitor: Duration(minutes: 5),     // Cache très court pour les visiteurs
  };
  
  /// Compteurs de performance
  static int _memoryHits = 0;
  static int _memoryMisses = 0;
  static int _diskHits = 0;
  static int _diskMisses = 0;
  
  /// Timer pour le nettoyage automatique
  static Timer? _cleanupTimer;

  /// Initialise le gestionnaire de cache
  static Future<void> initialize() async {
    _prefs = await SharedPreferences.getInstance();
    
    // Démarrer le nettoyage automatique toutes les 30 minutes
    _cleanupTimer = Timer.periodic(
      const Duration(minutes: 30),
      (_) => _performAutomaticCleanup(),
    );
    
    debugPrint('DashboardCacheManager initialisé');
  }

  /// Dispose le gestionnaire de cache
  static void dispose() {
    _cleanupTimer?.cancel();
    _memoryCache.clear();
  }

  /// Récupère une donnée du cache avec stratégie multi-niveaux
  /// 
  /// [key] - Clé unique de la donnée
  /// [userRole] - Rôle de l'utilisateur pour le TTL adaptatif
  /// [fromDisk] - Autoriser la récupération depuis le disque
  static Future<T?> get<T>(
    String key,
    UserRole userRole, {
    bool fromDisk = true,
  }) async {
    // Niveau 1 : Cache mémoire
    final memoryData = _getFromMemory<T>(key);
    if (memoryData != null) {
      _memoryHits++;
      return memoryData;
    }
    _memoryMisses++;
    
    // Niveau 2 : Cache disque
    if (fromDisk && _prefs != null) {
      final diskData = await _getFromDisk<T>(key, userRole);
      if (diskData != null) {
        _diskHits++;
        // Remettre en cache mémoire pour les prochains accès
        await _putInMemory(key, diskData, userRole);
        return diskData;
      }
      _diskMisses++;
    }
    
    return null;
  }

  /// Stocke une donnée dans le cache avec stratégie multi-niveaux
  /// 
  /// [key] - Clé unique de la donnée
  /// [data] - Donnée à stocker
  /// [userRole] - Rôle de l'utilisateur pour le TTL adaptatif
  /// [toDisk] - Sauvegarder sur disque
  /// [compress] - Compresser les données volumineuses
  static Future<void> put<T>(
    String key,
    T data,
    UserRole userRole, {
    bool toDisk = true,
    bool compress = false,
  }) async {
    // Niveau 1 : Cache mémoire
    await _putInMemory(key, data, userRole);
    
    // Niveau 2 : Cache disque
    if (toDisk && _prefs != null) {
      await _putOnDisk(key, data, userRole, compress: compress);
    }
  }

  /// Invalide une entrée du cache
  static Future<void> invalidate(String key) async {
    // Supprimer du cache mémoire
    _memoryCache.remove(key);
    
    // Supprimer du cache disque
    if (_prefs != null) {
      await _prefs!.remove('cache_$key');
      await _prefs!.remove('cache_meta_$key');
    }
  }

  /// Invalide toutes les entrées d'un préfixe
  static Future<void> invalidatePrefix(String prefix) async {
    // Cache mémoire
    final keysToRemove = _memoryCache.keys
        .where((key) => key.startsWith(prefix))
        .toList();
    
    for (final key in keysToRemove) {
      _memoryCache.remove(key);
    }
    
    // Cache disque
    if (_prefs != null) {
      final allKeys = _prefs!.getKeys();
      final diskKeysToRemove = allKeys
          .where((key) => key.startsWith('cache_$prefix'))
          .toList();
      
      for (final key in diskKeysToRemove) {
        await _prefs!.remove(key);
      }
    }
  }

  /// Vide complètement le cache
  static Future<void> clear() async {
    _memoryCache.clear();
    
    if (_prefs != null) {
      final allKeys = _prefs!.getKeys();
      final cacheKeys = allKeys.where((key) => key.startsWith('cache_')).toList();
      
      for (final key in cacheKeys) {
        await _prefs!.remove(key);
      }
    }
    
    debugPrint('Cache complètement vidé');
  }

  /// Obtient les statistiques du cache
  static Map<String, dynamic> getStats() {
    final totalMemoryRequests = _memoryHits + _memoryMisses;
    final totalDiskRequests = _diskHits + _diskMisses;
    
    final memoryHitRate = totalMemoryRequests > 0 
        ? (_memoryHits / totalMemoryRequests * 100) 
        : 0.0;
    
    final diskHitRate = totalDiskRequests > 0 
        ? (_diskHits / totalDiskRequests * 100) 
        : 0.0;
    
    return {
      'memoryCache': {
        'hits': _memoryHits,
        'misses': _memoryMisses,
        'hitRate': memoryHitRate.toStringAsFixed(2),
        'size': _memoryCache.length,
        'maxSize': _maxMemoryCacheSize,
      },
      'diskCache': {
        'hits': _diskHits,
        'misses': _diskMisses,
        'hitRate': diskHitRate.toStringAsFixed(2),
        'maxSizeMB': _maxDiskCacheSizeMB,
      },
    };
  }

  /// Effectue un nettoyage manuel du cache
  static Future<void> cleanup() async {
    await _performAutomaticCleanup();
  }

  // === MÉTHODES PRIVÉES ===

  /// Récupère une donnée du cache mémoire
  static T? _getFromMemory<T>(String key) {
    final cached = _memoryCache[key];
    if (cached == null) return null;
    
    // Vérifier l'expiration
    if (cached.expiresAt.isBefore(DateTime.now())) {
      _memoryCache.remove(key);
      return null;
    }
    
    return cached.data as T?;
  }

  /// Stocke une donnée dans le cache mémoire
  static Future<void> _putInMemory<T>(String key, T data, UserRole userRole) async {
    // Vérifier la taille du cache et nettoyer si nécessaire
    if (_memoryCache.length >= _maxMemoryCacheSize) {
      await _cleanOldestMemoryEntries();
    }
    
    final ttl = _roleTTL[userRole] ?? const Duration(minutes: 5);
    
    _memoryCache[key] = _CachedData(
      data: data,
      expiresAt: DateTime.now().add(ttl),
      createdAt: DateTime.now(),
    );
  }

  /// Récupère une donnée du cache disque
  static Future<T?> _getFromDisk<T>(String key, UserRole userRole) async {
    if (_prefs == null) return null;
    
    // Récupérer les métadonnées
    final metaJson = _prefs!.getString('cache_meta_$key');
    if (metaJson == null) return null;
    
    final meta = jsonDecode(metaJson) as Map<String, dynamic>;
    final expiresAt = DateTime.parse(meta['expiresAt']);
    
    // Vérifier l'expiration
    if (expiresAt.isBefore(DateTime.now())) {
      await _prefs!.remove('cache_$key');
      await _prefs!.remove('cache_meta_$key');
      return null;
    }
    
    // Récupérer les données
    final dataJson = _prefs!.getString('cache_$key');
    if (dataJson == null) return null;
    
    try {
      final data = jsonDecode(dataJson);
      return data as T;
    } catch (e) {
      debugPrint('Erreur de désérialisation du cache: $e');
      return null;
    }
  }

  /// Stocke une donnée sur le cache disque
  static Future<void> _putOnDisk<T>(
    String key,
    T data,
    UserRole userRole, {
    bool compress = false,
  }) async {
    if (_prefs == null) return;
    
    try {
      final ttl = _roleTTL[userRole] ?? const Duration(minutes: 5);
      final expiresAt = DateTime.now().add(ttl);
      
      // Sérialiser les données
      final dataJson = jsonEncode(data);
      
      // Métadonnées
      final meta = {
        'expiresAt': expiresAt.toIso8601String(),
        'createdAt': DateTime.now().toIso8601String(),
        'userRole': userRole.name,
        'compressed': compress,
      };
      
      // Sauvegarder
      await _prefs!.setString('cache_$key', dataJson);
      await _prefs!.setString('cache_meta_$key', jsonEncode(meta));
      
    } catch (e) {
      debugPrint('Erreur de sérialisation du cache: $e');
    }
  }

  /// Nettoie les entrées les plus anciennes du cache mémoire
  static Future<void> _cleanOldestMemoryEntries() async {
    if (_memoryCache.isEmpty) return;
    
    // Trier par date de création et supprimer les 10% les plus anciennes
    final entries = _memoryCache.entries.toList();
    entries.sort((a, b) => a.value.createdAt.compareTo(b.value.createdAt));
    
    final toRemove = (entries.length * 0.1).ceil();
    for (int i = 0; i < toRemove && i < entries.length; i++) {
      _memoryCache.remove(entries[i].key);
    }
  }

  /// Effectue un nettoyage automatique
  static Future<void> _performAutomaticCleanup() async {
    final now = DateTime.now();
    
    // Nettoyer le cache mémoire expiré
    _memoryCache.removeWhere((key, cached) => cached.expiresAt.isBefore(now));
    
    // Nettoyer le cache disque expiré
    if (_prefs != null) {
      final allKeys = _prefs!.getKeys();
      final metaKeys = allKeys.where((key) => key.startsWith('cache_meta_')).toList();
      
      for (final metaKey in metaKeys) {
        final metaJson = _prefs!.getString(metaKey);
        if (metaJson != null) {
          try {
            final meta = jsonDecode(metaJson) as Map<String, dynamic>;
            final expiresAt = DateTime.parse(meta['expiresAt']);
            
            if (expiresAt.isBefore(now)) {
              final dataKey = metaKey.replaceFirst('cache_meta_', 'cache_');
              await _prefs!.remove(dataKey);
              await _prefs!.remove(metaKey);
            }
          } catch (e) {
            // Supprimer les métadonnées corrompues
            await _prefs!.remove(metaKey);
          }
        }
      }
    }
    
    debugPrint('Nettoyage automatique du cache effectué');
  }

  /// Invalide le cache pour un rôle spécifique
  static Future<void> invalidateForRole(UserRole role) async {
    debugPrint('🗑️ Invalidation du cache pour le rôle: ${role.displayName}');

    // Invalider le cache mémoire pour ce rôle
    final keysToRemove = <String>[];
    for (final key in _memoryCache.keys) {
      if (key.contains(role.name)) {
        keysToRemove.add(key);
      }
    }

    for (final key in keysToRemove) {
      _memoryCache.remove(key);
    }

    // Invalider le cache disque pour ce rôle
    _prefs ??= await SharedPreferences.getInstance();
    if (_prefs != null) {
      final keys = _prefs!.getKeys();
      final diskKeysToRemove = <String>[];

      for (final key in keys) {
        if (key.startsWith('cache_') && key.contains(role.name)) {
          diskKeysToRemove.add(key);
        }
      }

      for (final key in diskKeysToRemove) {
        await _prefs!.remove(key);
        // Supprimer aussi les métadonnées associées
        final metaKey = key.replaceFirst('cache_', 'cache_meta_');
        await _prefs!.remove(metaKey);
      }
    }

    debugPrint('✅ Cache invalidé pour le rôle: ${role.displayName}');
  }
}

/// Classe pour les données mises en cache
class _CachedData {
  final dynamic data;
  final DateTime expiresAt;
  final DateTime createdAt;
  
  _CachedData({
    required this.data,
    required this.expiresAt,
    required this.createdAt,
  });
}
