import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:travel_mate/services/error_service.dart'; import '../models/group.dart'; import '../models/group_member.dart'; class GroupRepository { final FirebaseFirestore _firestore = FirebaseFirestore.instance; final FirebaseAuth _auth = FirebaseAuth.instance; final _errorService = ErrorService(); CollectionReference get _groupsCollection => _firestore.collection('groups'); CollectionReference _membersCollection(String groupId) { return _groupsCollection.doc(groupId).collection('members'); } Future createGroupWithMembers({ required Group group, required List members, }) async { try { return await _firestore.runTransaction((transaction) async { final groupRef = _groupsCollection.doc(); // Ajouter les IDs des membres à la liste memberIds final memberIds = members.map((m) => m.userId).toList(); final groupData = group.copyWith(memberIds: memberIds).toMap(); transaction.set(groupRef, groupData); for (var member in members) { final memberRef = groupRef.collection('members').doc(member.userId); transaction.set(memberRef, member.toMap()); } return groupRef.id; }); } catch (e, stackTrace) { _errorService.logError( 'GroupRepository', 'Erreur création groupe: $e', stackTrace, ); throw Exception('Impossible de créer le groupe'); } } Stream> getGroupsByUserId(String userId) { return _groupsCollection .where('memberIds', arrayContains: userId) .snapshots() .map((snapshot) { return snapshot.docs.map((doc) { final groupData = doc.data() as Map; return Group.fromMap(groupData, doc.id); }).toList(); }) .handleError((error, stackTrace) { _errorService.logError( 'GroupRepository', 'Erreur stream groups: $error', stackTrace, ); return []; }); } Future getGroupById(String groupId) async { try { final doc = await _groupsCollection.doc(groupId).get(); if (!doc.exists) return null; final group = Group.fromMap(doc.data() as Map, doc.id); final members = await getGroupMembers(groupId); return group.copyWith(members: members); } catch (e, stackTrace) { _errorService.logError( 'GroupRepository', 'Erreur get group: $e', stackTrace, ); throw Exception('Impossible de récupérer le groupe'); } } Future getGroupByTripId(String tripId) async { try { final userId = _auth.currentUser?.uid; if (userId == null) return null; // Tentative 1: Requête optimisée avec memberIds var querySnapshot = await _groupsCollection .where('tripId', isEqualTo: tripId) .where('memberIds', arrayContains: userId) .limit(1) .get(); // Tentative 2: Fallback pour le créateur (si memberIds est manquant - anciennes données) if (querySnapshot.docs.isEmpty) { querySnapshot = await _groupsCollection .where('tripId', isEqualTo: tripId) .where('createdBy', isEqualTo: userId) .limit(1) .get(); // Si on trouve le groupe via le fallback, on lance une migration if (querySnapshot.docs.isNotEmpty) { _migrateGroupData(querySnapshot.docs.first.id); } } if (querySnapshot.docs.isEmpty) return null; final doc = querySnapshot.docs.first; final group = Group.fromMap(doc.data() as Map, doc.id); final members = await getGroupMembers(doc.id); return group.copyWith(members: members); } catch (e, stackTrace) { _errorService.logError( 'GroupRepository', 'Erreur get group by trip: $e', stackTrace, ); throw Exception('Impossible de récupérer le groupe du voyage'); } } /// Méthode utilitaire pour migrer les anciennes données Future _migrateGroupData(String groupId) async { try { final members = await getGroupMembers(groupId); final memberIds = members.map((m) => m.userId).toList(); if (memberIds.isNotEmpty) { await _groupsCollection.doc(groupId).update({'memberIds': memberIds}); _errorService.logSuccess( 'GroupRepository', 'Migration réussie pour le groupe $groupId', ); } } catch (e, stackTrace) { _errorService.logError( 'GroupRepository', 'Erreur de migration pour le groupe $groupId: $e', stackTrace, ); } } Future> getGroupMembers(String groupId) async { try { final snapshot = await _membersCollection(groupId).get(); return snapshot.docs.map((doc) { return GroupMember.fromMap(doc.data() as Map, doc.id); }).toList(); } catch (e, stackTrace) { _errorService.logError( 'GroupRepository', 'Erreur get members: $e', stackTrace, ); throw Exception('Impossible de récupérer les membres'); } } Future addMember(String groupId, GroupMember member) async { try { // 1. Récupérer le groupe pour avoir le tripId final group = await getGroupById(groupId); if (group == null) throw Exception('Groupe introuvable'); // 2. Ajouter le membre dans la sous-collection members du groupe await _membersCollection(groupId).doc(member.userId).set(member.toMap()); // 3. Mettre à jour la liste memberIds du groupe await _groupsCollection.doc(groupId).update({ 'updatedAt': DateTime.now().millisecondsSinceEpoch, 'memberIds': FieldValue.arrayUnion([member.userId]), }); // 4. Mettre à jour la liste participants du voyage await _firestore.collection('trips').doc(group.tripId).update({ 'participants': FieldValue.arrayUnion([member.userId]), }); } catch (e, stackTrace) { _errorService.logError( 'GroupRepository', 'Erreur add member: $e', stackTrace, ); throw Exception('Impossible d\'ajouter le membre'); } } Future removeMember(String groupId, String userId) async { try { // 1. Récupérer le groupe pour avoir le tripId final group = await getGroupById(groupId); if (group == null) throw Exception('Groupe introuvable'); // 2. Supprimer le membre de la sous-collection members du groupe await _membersCollection(groupId).doc(userId).delete(); // 3. Mettre à jour la liste memberIds du groupe await _groupsCollection.doc(groupId).update({ 'updatedAt': DateTime.now().millisecondsSinceEpoch, 'memberIds': FieldValue.arrayRemove([userId]), }); // 4. Mettre à jour la liste participants du voyage await _firestore.collection('trips').doc(group.tripId).update({ 'participants': FieldValue.arrayRemove([userId]), }); } catch (e, stackTrace) { _errorService.logError( 'GroupRepository', 'Erreur remove member: $e', stackTrace, ); throw Exception('Impossible de supprimer le membre'); } } Future updateGroup(String groupId, Group group) async { try { await _groupsCollection .doc(groupId) .update( group.toMap() ..['updatedAt'] = DateTime.now().millisecondsSinceEpoch, ); } catch (e, stackTrace) { _errorService.logError( 'GroupRepository', 'Erreur update group: $e', stackTrace, ); throw Exception('Impossible de mettre à jour le groupe'); } } Future deleteGroup(String tripId) async { try { final userId = _auth.currentUser?.uid; if (userId == null) throw Exception('Utilisateur non connecté'); final querySnapshot = await _groupsCollection .where('tripId', isEqualTo: tripId) .where('createdBy', isEqualTo: userId) .limit(1) .get(); if (querySnapshot.docs.isEmpty) { throw Exception('Aucun groupe trouvé pour ce voyage'); } final groupDoc = querySnapshot.docs.first; final groupId = groupDoc.id; final membersSnapshot = await _membersCollection(groupId).get(); for (var doc in membersSnapshot.docs) { await doc.reference.delete(); } await _groupsCollection.doc(groupId).delete(); } catch (e, stackTrace) { _errorService.logError( 'GroupRepository', 'Erreur delete group: $e', stackTrace, ); throw Exception('Impossible de supprimer le groupe'); } } Stream> watchGroupMembers(String groupId) { return _membersCollection(groupId).snapshots().map( (snapshot) => snapshot.docs .map( (doc) => GroupMember.fromMap(doc.data() as Map, doc.id), ) .toList(), ); } }