import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:p_tits_pas/models/user.dart'; import 'package:p_tits_pas/models/parent_model.dart'; import 'package:p_tits_pas/models/assistante_maternelle_model.dart'; import 'package:p_tits_pas/models/pending_family.dart'; import 'package:p_tits_pas/models/dossier_unifie.dart'; import 'package:p_tits_pas/services/api/api_config.dart'; import 'package:p_tits_pas/services/api/tokenService.dart'; class UserService { static Future> _headers() async { final token = await TokenService.getToken(); return token != null ? ApiConfig.authHeaders(token) : Map.from(ApiConfig.headers); } static String? _toStr(dynamic v) { if (v == null) return null; if (v is String) return v; return v.toString(); } static String _errMessage(dynamic err) { if (err == null) return 'Erreur inconnue'; if (err is String) return err; if (err is Map) { final m = err['message']; if (m is String) return m; if (m is Map && m['message'] is String) return m['message'] as String; if (m != null) return _toStr(m) ?? 'Erreur inconnue'; } return _toStr(err) ?? 'Erreur inconnue'; } /// Utilisateurs en attente de validation (GET /users/pending). Ticket #107. static Future> getPendingUsers({String? role}) async { final query = role != null ? '?role=$role' : ''; final response = await http.get( Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/pending$query'), headers: await _headers(), ); if (response.statusCode != 200) { try { final err = jsonDecode(response.body); throw Exception(_errMessage(err is Map ? err['message'] : err)); } catch (e) { if (e is Exception) rethrow; throw Exception('Erreur chargement comptes en attente (${response.statusCode})'); } } try { final decoded = jsonDecode(response.body); final data = decoded is List ? decoded as List : []; return data .where((e) => e is Map) .map((e) => AppUser.fromJson(Map.from(e as Map))) .toList(); } catch (e) { throw Exception('Réponse invalide (comptes en attente): ${e is Exception ? e.toString() : "format inattendu"}'); } } /// Familles en attente (une entrée par famille). GET /parents/pending-families. Ticket #107. static Future> getPendingFamilies() async { final response = await http.get( Uri.parse('${ApiConfig.baseUrl}${ApiConfig.parents}/pending-families'), headers: await _headers(), ); if (response.statusCode != 200) { try { final err = jsonDecode(response.body); throw Exception(_errMessage(err is Map ? err['message'] : err)); } catch (e) { if (e is Exception) rethrow; throw Exception('Erreur chargement familles en attente (${response.statusCode})'); } } try { final decoded = jsonDecode(response.body); final data = decoded is List ? decoded as List : []; return data .where((e) => e is Map) .map((e) => PendingFamily.fromJson(Map.from(e as Map))) .toList(); } catch (e) { throw Exception('Réponse invalide (familles en attente): ${e is Exception ? e.toString() : "format inattendu"}'); } } /// Dossier unifié par numéro (AM ou famille). GET /dossiers/:numeroDossier. Ticket #119, #107. static Future getDossier(String numeroDossier) async { final encoded = Uri.encodeComponent(numeroDossier); final response = await http.get( Uri.parse('${ApiConfig.baseUrl}${ApiConfig.dossiers}/$encoded'), headers: await _headers(), ); if (response.statusCode == 404) { throw Exception('Aucun dossier avec ce numéro.'); } if (response.statusCode != 200) { try { final err = jsonDecode(response.body); throw Exception(_errMessage(err is Map ? err['message'] : err)); } catch (e) { if (e is Exception) rethrow; throw Exception('Erreur chargement dossier (${response.statusCode})'); } } try { final decoded = jsonDecode(response.body); if (decoded is! Map) { throw FormatException('Réponse invalide'); } return DossierUnifie.fromJson(Map.from(decoded)); } catch (e) { if (e is FormatException) rethrow; throw Exception('Réponse invalide (dossier): ${e is Exception ? e.toString() : "format inattendu"}'); } } /// Valider un utilisateur (AM). PATCH /users/:id/valider. Ticket #108. static Future validateUser(String userId, {String? comment}) async { final response = await http.patch( Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/$userId/valider'), headers: await _headers(), body: jsonEncode(comment != null ? {'comment': comment} : {}), ); if (response.statusCode != 200) { try { final err = jsonDecode(response.body); throw Exception(_errMessage(err is Map ? err['message'] : err)); } catch (e) { if (e is Exception) rethrow; throw Exception('Erreur validation (${response.statusCode})'); } } final data = jsonDecode(response.body); return AppUser.fromJson(Map.from(data is Map ? data : {})); } /// Valider tout le dossier famille. POST /parents/:parentId/valider-dossier. Ticket #108. static Future> validerDossierFamille(String parentId, {String? comment}) async { final response = await http.post( Uri.parse('${ApiConfig.baseUrl}${ApiConfig.parents}/$parentId/valider-dossier'), headers: await _headers(), body: jsonEncode(comment != null ? {'comment': comment} : {}), ); if (response.statusCode != 200) { try { final err = jsonDecode(response.body); throw Exception(_errMessage(err is Map ? err['message'] : err)); } catch (e) { if (e is Exception) rethrow; throw Exception('Erreur validation dossier famille (${response.statusCode})'); } } final data = jsonDecode(response.body); final list = data is List ? data : []; return list.map((e) => AppUser.fromJson(Map.from(e as Map))).toList(); } // Récupérer la liste des gestionnaires (endpoint dédié) static Future> getGestionnaires() async { final response = await http.get( Uri.parse('${ApiConfig.baseUrl}${ApiConfig.gestionnaires}'), headers: await _headers(), ); if (response.statusCode != 200) { final err = jsonDecode(response.body) as Map?; throw Exception( _toStr(err?['message']) ?? 'Erreur chargement gestionnaires'); } final List data = jsonDecode(response.body); return data.map((e) => AppUser.fromJson(e)).toList(); } static Future createGestionnaire({ required String nom, required String prenom, required String email, required String password, required String telephone, String? relaisId, }) async { final response = await http.post( Uri.parse('${ApiConfig.baseUrl}${ApiConfig.gestionnaires}'), headers: await _headers(), body: jsonEncode({ 'nom': nom, 'prenom': prenom, 'email': email, 'password': password, 'telephone': telephone, 'cguAccepted': true, 'relaisId': relaisId, }), ); if (response.statusCode != 200 && response.statusCode != 201) { final decoded = jsonDecode(response.body); if (decoded is Map) { final message = decoded['message']; if (message is List && message.isNotEmpty) { throw Exception(message.join(' - ')); } throw Exception(_toStr(message) ?? 'Erreur création gestionnaire'); } throw Exception('Erreur création gestionnaire'); } final data = jsonDecode(response.body) as Map; return AppUser.fromJson(data); } static Future createAdministrateur({ required String nom, required String prenom, required String email, required String password, required String telephone, }) async { final response = await http.post( Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/admin'), headers: await _headers(), body: jsonEncode({ 'nom': nom, 'prenom': prenom, 'email': email, 'password': password, 'telephone': telephone, }), ); if (response.statusCode != 200 && response.statusCode != 201) { final decoded = jsonDecode(response.body); if (decoded is Map) { final message = decoded['message']; if (message is List && message.isNotEmpty) { throw Exception(message.join(' - ')); } throw Exception(_toStr(message) ?? 'Erreur création administrateur'); } throw Exception('Erreur création administrateur'); } final data = jsonDecode(response.body) as Map; return AppUser.fromJson(data); } // Récupérer la liste des parents static Future> getParents() async { final response = await http.get( Uri.parse('${ApiConfig.baseUrl}${ApiConfig.parents}'), headers: await _headers(), ); if (response.statusCode != 200) { final err = jsonDecode(response.body) as Map?; throw Exception(_toStr(err?['message']) ?? 'Erreur chargement parents'); } final List data = jsonDecode(response.body); return data.map((e) => ParentModel.fromJson(e)).toList(); } // Récupérer la liste des assistantes maternelles static Future> getAssistantesMaternelles() async { final response = await http.get( Uri.parse('${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}'), headers: await _headers(), ); if (response.statusCode != 200) { final err = jsonDecode(response.body) as Map?; throw Exception(_toStr(err?['message']) ?? 'Erreur chargement AM'); } final List data = jsonDecode(response.body); return data.map((e) => AssistanteMaternelleModel.fromJson(e)).toList(); } // Récupérer la liste des administrateurs (via /users filtré ou autre) // Pour l'instant on va utiliser /users et filtrer côté client si on est super admin static Future> getAdministrateurs() async { // TODO: Endpoint dédié ou filtrage // En attendant, on retourne une liste vide ou on tente /users try { final response = await http.get( Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}'), headers: await _headers(), ); if (response.statusCode == 200) { final List data = jsonDecode(response.body); return data .map((e) => AppUser.fromJson(e)) .where((u) => u.role == 'administrateur' || u.role == 'super_admin') .toList(); } } catch (e) { // On garde un fallback vide pour ne pas bloquer l'UI admin. } return []; } static Future createAdmin({ required String nom, required String prenom, required String email, required String password, required String telephone, }) async { final response = await http.post( Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/admin'), headers: await _headers(), body: jsonEncode({ 'nom': nom, 'prenom': prenom, 'email': email, 'password': password, 'telephone': telephone, }), ); if (response.statusCode != 200 && response.statusCode != 201) { final decoded = jsonDecode(response.body); if (decoded is Map) { final message = decoded['message']; if (message is List && message.isNotEmpty) { throw Exception(message.join(' - ')); } throw Exception(_toStr(message) ?? 'Erreur création administrateur'); } throw Exception('Erreur création administrateur'); } final data = jsonDecode(response.body) as Map; return AppUser.fromJson(data); } static Future updateAdmin({ required String adminId, required String nom, required String prenom, required String email, required String telephone, String? password, }) async { final body = { 'nom': nom, 'prenom': prenom, 'email': email, 'telephone': telephone, }; if (password != null && password.trim().isNotEmpty) { body['password'] = password.trim(); } final response = await http.patch( Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/$adminId'), headers: await _headers(), body: jsonEncode(body), ); if (response.statusCode != 200) { final decoded = jsonDecode(response.body); if (decoded is Map) { final message = decoded['message']; if (message is List && message.isNotEmpty) { throw Exception(message.join(' - ')); } throw Exception(_toStr(message) ?? 'Erreur modification administrateur'); } throw Exception('Erreur modification administrateur'); } final data = jsonDecode(response.body) as Map; return AppUser.fromJson(data); } static Future updateGestionnaireRelais({ required String gestionnaireId, required String? relaisId, }) async { final response = await http.patch( Uri.parse( '${ApiConfig.baseUrl}${ApiConfig.gestionnaires}/$gestionnaireId'), headers: await _headers(), body: jsonEncode({'relaisId': relaisId}), ); if (response.statusCode != 200 && response.statusCode != 204) { final err = jsonDecode(response.body) as Map?; throw Exception( _toStr(err?['message']) ?? 'Erreur rattachement relais au gestionnaire', ); } } static Future updateGestionnaire({ required String gestionnaireId, required String nom, required String prenom, required String email, String? telephone, required String? relaisId, String? password, }) async { final body = { 'nom': nom, 'prenom': prenom, 'email': email, 'relaisId': relaisId, }; if (telephone != null && telephone.trim().isNotEmpty) { body['telephone'] = telephone.trim(); } if (password != null && password.trim().isNotEmpty) { body['password'] = password.trim(); } final response = await http.patch( Uri.parse('${ApiConfig.baseUrl}${ApiConfig.gestionnaires}/$gestionnaireId'), headers: await _headers(), body: jsonEncode(body), ); if (response.statusCode != 200) { final decoded = jsonDecode(response.body); if (decoded is Map) { final message = decoded['message']; if (message is List && message.isNotEmpty) { throw Exception(message.join(' - ')); } throw Exception(_toStr(message) ?? 'Erreur modification gestionnaire'); } throw Exception('Erreur modification gestionnaire'); } final data = jsonDecode(response.body) as Map; return AppUser.fromJson(data); } static Future updateAdministrateur({ required String adminId, required String nom, required String prenom, required String email, String? telephone, String? password, }) async { final body = { 'nom': nom, 'prenom': prenom, 'email': email, }; if (telephone != null && telephone.trim().isNotEmpty) { body['telephone'] = telephone.trim(); } if (password != null && password.trim().isNotEmpty) { body['password'] = password.trim(); } final response = await http.patch( Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/$adminId'), headers: await _headers(), body: jsonEncode(body), ); if (response.statusCode != 200) { final decoded = jsonDecode(response.body); if (decoded is Map) { final message = decoded['message']; if (message is List && message.isNotEmpty) { throw Exception(message.join(' - ')); } throw Exception(_toStr(message) ?? 'Erreur modification administrateur'); } throw Exception('Erreur modification administrateur'); } final data = jsonDecode(response.body) as Map; return AppUser.fromJson(data); } static Future deleteUser(String userId) async { final response = await http.delete( Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/$userId'), headers: await _headers(), ); if (response.statusCode != 200 && response.statusCode != 204) { final decoded = jsonDecode(response.body); if (decoded is Map) { final message = decoded['message']; if (message is List && message.isNotEmpty) { throw Exception(message.join(' - ')); } throw Exception(_toStr(message) ?? 'Erreur suppression utilisateur'); } throw Exception('Erreur suppression utilisateur'); } } }