import 'package:cloud_firestore/cloud_firestore.dart'; import '../models/activity.dart'; import '../services/error_service.dart'; /// Repository pour la gestion des activités dans Firestore class ActivityRepository { static final ActivityRepository _instance = ActivityRepository._internal(); factory ActivityRepository() => _instance; ActivityRepository._internal(); final FirebaseFirestore _firestore = FirebaseFirestore.instance; final ErrorService _errorService = ErrorService(); static const String _collection = 'activities'; /// Ajoute une nouvelle activité Future addActivity(Activity activity) async { try { _errorService.logInfo( 'ActivityRepository', 'Ajout d\'une activité: ${activity.name}', ); final docRef = await _firestore .collection(_collection) .add(activity.toMap()); // Mettre à jour l'activité avec l'ID généré await docRef.update({'id': docRef.id}); _errorService.logSuccess( 'ActivityRepository', 'Activité ajoutée avec ID: ${docRef.id}', ); return docRef.id; } catch (e) { _errorService.logError( 'ActivityRepository', 'Erreur lors de l\'ajout: $e', ); _errorService.logError( 'activity_repository', 'Erreur ajout activité: $e', ); return null; } } /// Récupère toutes les activités d'un voyage Future> getActivitiesByTrip(String tripId) async { try { _errorService.logInfo( 'ActivityRepository', 'Récupération des activités pour le voyage: $tripId', ); // Modifié pour éviter l'erreur d'index composite // On récupère d'abord par tripId, puis on trie en mémoire final querySnapshot = await _firestore .collection(_collection) .where('tripId', isEqualTo: tripId) .get(); final activities = querySnapshot.docs .map((doc) => Activity.fromSnapshot(doc)) .toList(); // Tri en mémoire par date de mise à jour (plus récent en premier) activities.sort((a, b) => b.updatedAt.compareTo(a.updatedAt)); _errorService.logInfo( 'ActivityRepository', '${activities.length} activités trouvées', ); return activities; } catch (e) { _errorService.logError( 'ActivityRepository', 'Erreur lors de la récupération: $e', ); _errorService.logError( 'activity_repository', 'Erreur récupération activités: $e', ); return []; } } /// Récupère une activité par son ID (alias pour getActivityById pour compatibilité) Future getActivity(String tripId, String activityId) async { return getActivityById(activityId); } /// Récupère une activité par son ID Future getActivityById(String activityId) async { try { final doc = await _firestore .collection(_collection) .doc(activityId) .get(); if (doc.exists) { return Activity.fromSnapshot(doc); } return null; } catch (e) { _errorService.logError( 'ActivityRepository', 'Erreur récupération activité: $e', ); _errorService.logError( 'activity_repository', 'Erreur récupération activité: $e', ); return null; } } /// Met à jour une activité Future updateActivity(Activity activity) async { try { _errorService.logInfo( 'ActivityRepository', 'Mise à jour de l\'activité: ${activity.id}', ); await _firestore .collection(_collection) .doc(activity.id) .update(activity.copyWith(updatedAt: DateTime.now()).toMap()); _errorService.logSuccess( 'ActivityRepository', 'Activité mise à jour avec succès', ); return true; } catch (e) { _errorService.logError( 'ActivityRepository', 'Erreur lors de la mise à jour: $e', ); _errorService.logError( 'activity_repository', 'Erreur mise à jour activité: $e', ); return false; } } /// Supprime une activité Future deleteActivity(String activityId) async { try { _errorService.logInfo( 'ActivityRepository', 'Suppression de l\'activité: $activityId', ); await _firestore.collection(_collection).doc(activityId).delete(); _errorService.logSuccess( 'ActivityRepository', 'Activité supprimée avec succès', ); return true; } catch (e) { _errorService.logError( 'ActivityRepository', 'Erreur lors de la suppression: $e', ); _errorService.logError( 'activity_repository', 'Erreur suppression activité: $e', ); return false; } } /// Vote pour une activité Future voteForActivity( String activityId, String userId, int vote, ) async { try { // Validation des paramètres if (activityId.isEmpty) { _errorService.logError('ActivityRepository', 'ID d\'activité vide'); _errorService.logError( 'activity_repository', 'ID d\'activité vide pour le vote', ); return false; } if (userId.isEmpty) { _errorService.logError('ActivityRepository', 'ID d\'utilisateur vide'); _errorService.logError( 'activity_repository', 'ID d\'utilisateur vide pour le vote', ); return false; } _errorService.logInfo( 'ActivityRepository', 'Vote pour l\'activité $activityId: $vote', ); // vote: 1 pour positif, -1 pour négatif, 0 pour supprimer le vote final activityRef = _firestore.collection(_collection).doc(activityId); await _firestore.runTransaction((transaction) async { final snapshot = await transaction.get(activityRef); if (!snapshot.exists) { throw Exception('Activité non trouvée'); } final activity = Activity.fromSnapshot(snapshot); final newVotes = Map.from(activity.votes); if (vote == 0) { // Supprimer le vote newVotes.remove(userId); } else { // Ajouter ou modifier le vote newVotes[userId] = vote; } transaction.update(activityRef, { 'votes': newVotes, 'updatedAt': Timestamp.fromDate(DateTime.now()), }); }); _errorService.logSuccess( 'ActivityRepository', 'Vote enregistré avec succès', ); return true; } catch (e) { _errorService.logError('ActivityRepository', 'Erreur lors du vote: $e'); _errorService.logError('activity_repository', 'Erreur vote: $e'); return false; } } /// Récupère les activités avec un stream pour les mises à jour en temps réel Stream> getActivitiesStream(String tripId) { try { return _firestore .collection(_collection) .where('tripId', isEqualTo: tripId) .snapshots() .map((snapshot) { final activities = snapshot.docs .map((doc) => Activity.fromSnapshot(doc)) .toList(); // Tri en mémoire par date de mise à jour (plus récent en premier) activities.sort((a, b) => b.updatedAt.compareTo(a.updatedAt)); return activities; }); } catch (e) { _errorService.logError( 'ActivityRepository', 'Erreur stream activités: $e', ); _errorService.logError( 'activity_repository', 'Erreur stream activités: $e', ); return Stream.value([]); } } /// Ajoute plusieurs activités en lot Future> addActivitiesBatch(List activities) async { try { _errorService.logInfo( 'ActivityRepository', 'Ajout en lot de ${activities.length} activités', ); final batch = _firestore.batch(); final addedIds = []; for (final activity in activities) { final docRef = _firestore.collection(_collection).doc(); final activityWithId = activity.copyWith(id: docRef.id); batch.set(docRef, activityWithId.toMap()); addedIds.add(docRef.id); } await batch.commit(); _errorService.logSuccess( 'ActivityRepository', '${addedIds.length} activités ajoutées en lot', ); return addedIds; } catch (e) { _errorService.logError('ActivityRepository', 'Erreur ajout en lot: $e'); _errorService.logError('activity_repository', 'Erreur ajout en lot: $e'); return []; } } /// Recherche des activités par catégorie Future> getActivitiesByCategory( String tripId, String category, ) async { try { _errorService.logInfo( 'ActivityRepository', 'Recherche par catégorie: $category pour le voyage: $tripId', ); // Récupérer toutes les activités du voyage puis filtrer en mémoire final querySnapshot = await _firestore .collection(_collection) .where('tripId', isEqualTo: tripId) .get(); final activities = querySnapshot.docs .map((doc) => Activity.fromSnapshot(doc)) .where((activity) => activity.category == category) .toList(); // Tri en mémoire par date de mise à jour (plus récent en premier) activities.sort((a, b) => b.updatedAt.compareTo(a.updatedAt)); return activities; } catch (e) { _errorService.logError( 'ActivityRepository', 'Erreur recherche par catégorie: $e', ); _errorService.logError( 'activity_repository', 'Erreur recherche par catégorie: $e', ); return []; } } /// Récupère les activités les mieux notées d'un voyage Future> getTopRatedActivities( String tripId, { int limit = 10, }) async { try { final activities = await getActivitiesByTrip(tripId); // Trier par nombre de votes positifs puis par note Google activities.sort((a, b) { final aScore = a.totalVotes; final bScore = b.totalVotes; if (aScore != bScore) { return bScore.compareTo(aScore); } return (b.rating ?? 0).compareTo(a.rating ?? 0); }); return activities.take(limit).toList(); } catch (e) { _errorService.logError( 'ActivityRepository', 'Erreur activités top rated: $e', ); _errorService.logError('activity_repository', 'Erreur top rated: $e'); return []; } } /// Vérifie si une activité existe déjà pour un voyage (basé sur placeId) Future findExistingActivity(String tripId, String placeId) async { try { final querySnapshot = await _firestore .collection(_collection) .where('tripId', isEqualTo: tripId) .where('placeId', isEqualTo: placeId) .limit(1) .get(); if (querySnapshot.docs.isNotEmpty) { return Activity.fromSnapshot(querySnapshot.docs.first); } return null; } catch (e) { _errorService.logError( 'ActivityRepository', 'Erreur recherche activité existante: $e', ); return null; } } }