import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
import 'package:p_tits_pas/models/user.dart';
import 'package:p_tits_pas/models/enfant_admin_model.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';
import 'package:p_tits_pas/utils/am_vigilance.dart';

class DocumentActifInfo {
  final String id;
  final String type;
  final int version;
  final String url;

  const DocumentActifInfo({
    required this.id,
    required this.type,
    required this.version,
    required this.url,
  });

  factory DocumentActifInfo.fromJson(Map<String, dynamic> json) {
    return DocumentActifInfo(
      id: (json['id'] ?? '').toString(),
      type: (json['type'] ?? '').toString(),
      version: int.tryParse((json['version'] ?? '').toString()) ?? 0,
      url: (json['url'] ?? '').toString(),
    );
  }
}

class DocumentsActifsInfo {
  final DocumentActifInfo cgu;
  final DocumentActifInfo privacy;

  const DocumentsActifsInfo({
    required this.cgu,
    required this.privacy,
  });
}

class UserService {
  static Future<Map<String, String>> _headers() async {
    final token = await TokenService.getToken();
    return token != null
        ? ApiConfig.authHeaders(token)
        : Map<String, String>.from(ApiConfig.headers);
  }

  static String? _toStr(dynamic v) {
    if (v == null) return null;
    if (v is String) return v;
    return v.toString();
  }

  /// MIME pour upload photo enfant (filtre Nest : jpg/jpeg/png/gif).
  static MediaType _imageMediaType(String filename, List<int> bytes) {
    if (bytes.length >= 3 &&
        bytes[0] == 0xFF &&
        bytes[1] == 0xD8 &&
        bytes[2] == 0xFF) {
      return MediaType('image', 'jpeg');
    }
    if (bytes.length >= 8 &&
        bytes[0] == 0x89 &&
        bytes[1] == 0x50 &&
        bytes[2] == 0x4E &&
        bytes[3] == 0x47) {
      return MediaType('image', 'png');
    }
    if (bytes.length >= 6 &&
        bytes[0] == 0x47 &&
        bytes[1] == 0x49 &&
        bytes[2] == 0x46) {
      return MediaType('image', 'gif');
    }
    final lower = filename.toLowerCase();
    if (lower.endsWith('.png')) return MediaType('image', 'png');
    if (lower.endsWith('.gif')) return MediaType('image', 'gif');
    return MediaType('image', 'jpeg');
  }

  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';
  }

  /// Documents légaux actifs (CGU + privacy). GET /documents-legaux/actifs.
  static Future<DocumentsActifsInfo> getDocumentsLegauxActifs() async {
    final response = await http.get(
      Uri.parse('${ApiConfig.baseUrl}/documents-legaux/actifs'),
      headers: ApiConfig.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 documents légaux (${response.statusCode})',
        );
      }
    }

    try {
      final decoded = jsonDecode(response.body);
      if (decoded is! Map<String, dynamic>) {
        throw const FormatException('Réponse invalide');
      }
      final cgu = decoded['cgu'];
      final privacy = decoded['privacy'];
      if (cgu is! Map || privacy is! Map) {
        throw const FormatException('Documents actifs manquants');
      }
      return DocumentsActifsInfo(
        cgu: DocumentActifInfo.fromJson(Map<String, dynamic>.from(cgu)),
        privacy: DocumentActifInfo.fromJson(Map<String, dynamic>.from(privacy)),
      );
    } catch (e) {
      if (e is FormatException) rethrow;
      throw Exception(
        'Réponse invalide (documents légaux): ${e is Exception ? e.toString() : "format inattendu"}',
      );
    }
  }

  /// Utilisateurs en attente de validation (GET /users/pending). Ticket #107.
  static Future<List<AppUser>> 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<dynamic> : <dynamic>[];
      return data
          .where((e) => e is Map<String, dynamic>)
          .map((e) => AppUser.fromJson(Map<String, dynamic>.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<List<PendingFamily>> 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<dynamic> : <dynamic>[];
      return data
          .where((e) => e is Map)
          .map((e) => PendingFamily.fromJson(Map<String, dynamic>.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<DossierUnifie> getDossier(String numeroDossier) async {
    final encoded = Uri.encodeComponent(numeroDossier);
    final uri = Uri.parse('${ApiConfig.baseUrl}${ApiConfig.dossiers}/$encoded');
    final response = await http.get(
      uri,
      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<String, dynamic>) {
        throw FormatException('Réponse invalide');
      }
      final dossier = DossierUnifie.fromJson(Map<String, dynamic>.from(decoded));
      return dossier;
    } 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<AppUser> 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<String, dynamic>.from(data is Map ? data : {}));
  }

  /// Refuser un compte en attente (AM ou parent). PATCH /users/:id/refuser. Ticket #110.
  static Future<AppUser> refuseUser(String userId, {String? comment}) async {
    final response = await http.patch(
      Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/$userId/refuser'),
      headers: await _headers(),
      body: jsonEncode(
        comment != null && comment.trim().isNotEmpty
            ? {'comment': comment.trim()}
            : {},
      ),
    );
    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 refus (${response.statusCode})');
      }
    }
    final data = jsonDecode(response.body);
    return AppUser.fromJson(Map<String, dynamic>.from(data is Map ? data : {}));
  }

  /// Valider tout le dossier famille. POST /parents/:parentId/valider-dossier. Ticket #108.
  static Future<List<AppUser>> 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} : {}),
    );
    // NestJS renvoie souvent 201 Created sur un POST même avec corps JSON — le front ne doit pas traiter ça comme une erreur
    // (sinon validation + emails déjà faits côté serveur mais SnackBar rouge côté UI).
    final ok = response.statusCode == 200 || response.statusCode == 201;
    if (!ok) {
      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<String, dynamic>.from(e as Map))).toList();
  }

  // Récupérer la liste des gestionnaires (endpoint dédié)
  static Future<List<AppUser>> 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<String, dynamic>?;
      throw Exception(
          _toStr(err?['message']) ?? 'Erreur chargement gestionnaires');
    }

    final List<dynamic> data = jsonDecode(response.body);
    return data.map((e) => AppUser.fromJson(e)).toList();
  }

  static Future<AppUser> 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(<String, dynamic>{
        '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<String, dynamic>) {
        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<String, dynamic>;
    return AppUser.fromJson(data);
  }

  static Future<AppUser> 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(<String, dynamic>{
        '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<String, dynamic>) {
        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<String, dynamic>;
    return AppUser.fromJson(data);
  }

  // Récupérer la liste des parents
  static Future<List<ParentModel>> 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<String, dynamic>?;
      throw Exception(_toStr(err?['message']) ?? 'Erreur chargement parents');
    }

    final List<dynamic> data = jsonDecode(response.body);
    return data
        .map((e) => ParentModel.fromJson(Map<String, dynamic>.from(e as Map)))
        .toList();
  }

  static Future<ParentModel> getParent(String userId) async {
    final response = await http.get(
      Uri.parse('${ApiConfig.baseUrl}${ApiConfig.parents}/$userId'),
      headers: await _headers(),
    );
    if (response.statusCode != 200) {
      final err = jsonDecode(response.body) as Map<String, dynamic>?;
      throw Exception(_toStr(err?['message']) ?? 'Erreur chargement parent');
    }
    final decoded = jsonDecode(response.body);
    return ParentModel.fromJson(
      Map<String, dynamic>.from(decoded is Map ? decoded : {}),
    );
  }

  static Future<ParentModel> updateParentFiche({
    required String parentUserId,
    required Map<String, dynamic> body,
  }) async {
    final response = await http.patch(
      Uri.parse('${ApiConfig.baseUrl}${ApiConfig.parents}/$parentUserId/fiche'),
      headers: await _headers(),
      body: jsonEncode(body),
    );
    if (response.statusCode != 200) {
      throw Exception(_extractErrorMessage(response.body, 'Erreur mise à jour parent'));
    }
    return _parentModelFromBody(response.body);
  }

  static Future<ParentModel> attachEnfantToParent({
    required String parentUserId,
    required String enfantId,
  }) async {
    final response = await http.post(
      Uri.parse(
        '${ApiConfig.baseUrl}${ApiConfig.parents}/$parentUserId/enfants/$enfantId',
      ),
      headers: await _headers(),
    );
    if (response.statusCode != 200 && response.statusCode != 201) {
      throw Exception(_extractErrorMessage(response.body, 'Erreur rattachement enfant'));
    }
    return _parentModelFromBody(response.body);
  }

  static Future<ParentModel> detachEnfantFromParent({
    required String parentUserId,
    required String enfantId,
  }) async {
    final response = await http.delete(
      Uri.parse(
        '${ApiConfig.baseUrl}${ApiConfig.parents}/$parentUserId/enfants/$enfantId',
      ),
      headers: await _headers(),
    );
    if (response.statusCode != 200 && response.statusCode != 204) {
      throw Exception(_extractErrorMessage(response.body, 'Erreur détachement enfant'));
    }
    if (response.body.isEmpty) {
      return getParent(parentUserId);
    }
    return _parentModelFromBody(response.body);
  }

  static Future<List<EnfantAdminModel>> getEnfants() async {
    final response = await http.get(
      Uri.parse('${ApiConfig.baseUrl}${ApiConfig.enfants}'),
      headers: await _headers(),
    );
    if (response.statusCode != 200) {
      throw Exception(_extractErrorMessage(response.body, 'Erreur chargement enfants'));
    }
    final List<dynamic> data = jsonDecode(response.body);
    return data
        .whereType<Map<String, dynamic>>()
        .map(EnfantAdminModel.fromJson)
        .toList();
  }

  static Future<EnfantAdminModel> getEnfant(String enfantId) async {
    final response = await http.get(
      Uri.parse('${ApiConfig.baseUrl}${ApiConfig.enfants}/$enfantId'),
      headers: await _headers(),
    );
    if (response.statusCode != 200) {
      throw Exception(_extractErrorMessage(response.body, 'Erreur chargement enfant'));
    }
    final enfant = EnfantAdminModel.fromJson(
      jsonDecode(response.body) as Map<String, dynamic>,
    );
    // GET /enfants/:id ne joint pas toujours parent.user → noms manquants.
    return enrichEnfantParentNames(enfant);
  }

  /// Complète [parentName] via GET parent quand l'API n'a pas fourni le nested user.
  static Future<EnfantAdminModel> enrichEnfantParentNames(
    EnfantAdminModel enfant,
  ) async {
    final links = enfant.parentLinks;
    if (links.isEmpty) return enfant;
    if (links.every((l) => (l.parentName ?? '').trim().isNotEmpty)) {
      return enfant;
    }

    final enriched = await Future.wait(
      links.map((link) async {
        if ((link.parentName ?? '').trim().isNotEmpty) return link;
        final id = link.parentId.trim();
        if (id.isEmpty) return link;
        try {
          final parent = await getParent(id);
          final name = parent.user.fullName.trim();
          if (name.isEmpty) return link;
          return link.copyWith(parentName: name);
        } catch (_) {
          return link;
        }
      }),
    );

    return enfant.copyWith(parentLinks: enriched);
  }

  static Future<EnfantAdminModel> updateEnfant({
    required String enfantId,
    required Map<String, dynamic> body,
  }) async {
    final response = await http.patch(
      Uri.parse('${ApiConfig.baseUrl}${ApiConfig.enfants}/$enfantId'),
      headers: await _headers(),
      body: jsonEncode(body),
    );
    if (response.statusCode != 200) {
      throw Exception(_extractErrorMessage(response.body, 'Erreur mise à jour enfant'));
    }
    final enfant = EnfantAdminModel.fromJson(
      jsonDecode(response.body) as Map<String, dynamic>,
    );
    return enrichEnfantParentNames(enfant);
  }

  /// Création enfant côté staff (#132) — nécessite `POST /enfants` étendu (voir mini-spec).
  /// [parentUserId] : parent pivot du foyer (`parent_user_id`).
  /// Avec [photoBytes] : `multipart/form-data` (champ fichier `photo`), sinon JSON.
  static Future<EnfantAdminModel> createEnfant({
    required String parentUserId,
    required Map<String, dynamic> body,
    List<int>? photoBytes,
    String? photoFilename,
  }) async {
    final hasPhoto = photoBytes != null && photoBytes.isNotEmpty;
    final http.Response response;
    if (hasPhoto) {
      final token = await TokenService.getToken();
      final req = http.MultipartRequest(
        'POST',
        Uri.parse('${ApiConfig.baseUrl}${ApiConfig.enfants}'),
      );
      req.headers['Accept'] = 'application/json';
      if (token != null) {
        req.headers['Authorization'] = 'Bearer $token';
      }
      body.forEach((key, value) {
        if (value == null) return;
        req.fields[key] = value is bool
            ? (value ? 'true' : 'false')
            : value.toString();
      });
      req.fields['parent_user_id'] = parentUserId;
      final name = (photoFilename ?? '').trim();
      final filename = name.isNotEmpty ? name : 'photo.jpg';
      req.files.add(
        http.MultipartFile.fromBytes(
          'photo',
          photoBytes,
          filename: filename,
          contentType: _imageMediaType(filename, photoBytes),
        ),
      );
      final streamed = await req.send();
      response = await http.Response.fromStream(streamed);
    } else {
      final payload = <String, dynamic>{
        ...body,
        'parent_user_id': parentUserId,
      };
      response = await http.post(
        Uri.parse('${ApiConfig.baseUrl}${ApiConfig.enfants}'),
        headers: await _headers(),
        body: jsonEncode(payload),
      );
    }
    if (response.statusCode != 200 && response.statusCode != 201) {
      throw Exception(
        _extractErrorMessage(response.body, 'Erreur création enfant'),
      );
    }
    final enfant = EnfantAdminModel.fromJson(
      jsonDecode(response.body) as Map<String, dynamic>,
    );
    return enrichEnfantParentNames(enfant);
  }

  static Future<void> deleteEnfant(String enfantId) async {
    final response = await http.delete(
      Uri.parse('${ApiConfig.baseUrl}${ApiConfig.enfants}/$enfantId'),
      headers: await _headers(),
    );
    if (response.statusCode != 200 && response.statusCode != 204) {
      throw Exception(_extractErrorMessage(response.body, 'Erreur suppression enfant'));
    }
  }

  /// AM dont la liste d'enfants actifs contient [enfantId] (API actuelle).
  static Future<AssistanteMaternelleModel?> findAmForEnfant(String enfantId) async {
    final ams = await getAssistantesMaternelles();
    for (final am in ams) {
      if (am.children.any((c) => c.id == enfantId)) return am;
    }
    return null;
  }

  static ParentModel _parentModelFromBody(String body) {
    final decoded = jsonDecode(body);
    return ParentModel.fromJson(
      Map<String, dynamic>.from(decoded is Map ? decoded : {}),
    );
  }

  static String _extractErrorMessage(String body, String fallback) {
    try {
      final decoded = jsonDecode(body);
      if (decoded is Map<String, dynamic>) {
        final message = decoded['message'];
        if (message is List && message.isNotEmpty) {
          return message.join(' - ');
        }
        return _toStr(message) ?? fallback;
      }
    } catch (_) {}
    return fallback;
  }

  // Récupérer la liste des assistantes maternelles
  static Future<List<AssistanteMaternelleModel>>
      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<String, dynamic>?;
      throw Exception(_toStr(err?['message']) ?? 'Erreur chargement AM');
    }

    final List<dynamic> data = jsonDecode(response.body);
    return data
        .map((e) => AssistanteMaternelleModel.fromJson(
              Map<String, dynamic>.from(e as Map),
            ))
        .toList();
  }

  static Future<AssistanteMaternelleModel> getAssistanteMaternelle(
    String userId,
  ) async {
    final response = await http.get(
      Uri.parse('${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}/$userId'),
      headers: await _headers(),
    );
    if (response.statusCode == 200) {
      final decoded = jsonDecode(response.body);
      return AssistanteMaternelleModel.fromJson(
        Map<String, dynamic>.from(decoded is Map ? decoded : {}),
      );
    }
    if (response.statusCode == 403 || response.statusCode == 404) {
      final all = await getAssistantesMaternelles();
      return all.firstWhere(
        (a) => a.user.id == userId,
        orElse: () => throw Exception('Assistante maternelle introuvable'),
      );
    }
    final err = jsonDecode(response.body) as Map<String, dynamic>?;
    throw Exception(_toStr(err?['message']) ?? 'Erreur chargement AM');
  }

  /// Mise à jour fiche AM (identité + champs pro). Ticket #131.
  static Future<AssistanteMaternelleModel> updateAmFiche({
    required String amUserId,
    required Map<String, dynamic> body,
  }) async {
    final ficheResponse = await http.patch(
      Uri.parse(
        '${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}/$amUserId/fiche',
      ),
      headers: await _headers(),
      body: jsonEncode(body),
    );
    if (ficheResponse.statusCode == 200) {
      return _amModelFromBody(ficheResponse.body);
    }
    if (ficheResponse.statusCode != 404) {
      throw Exception(
        _extractErrorMessage(ficheResponse.body, 'Erreur mise à jour AM'),
      );
    }

    final userFields = <String, dynamic>{};
    for (final k in [
      'nom',
      'prenom',
      'email',
      'telephone',
      'adresse',
      'ville',
      'code_postal',
      'statut',
      'date_naissance',
      'lieu_naissance_ville',
      'lieu_naissance_pays',
    ]) {
      if (body.containsKey(k)) userFields[k] = body[k];
    }

    final proFields = <String, dynamic>{};
    for (final entry in body.entries) {
      if (!userFields.containsKey(entry.key)) {
        proFields[entry.key] = entry.value;
      }
    }

    if (userFields.isNotEmpty) {
      final userResponse = await http.patch(
        Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/$amUserId'),
        headers: await _headers(),
        body: jsonEncode(userFields),
      );
      if (userResponse.statusCode != 200) {
        throw Exception(
          _extractErrorMessage(userResponse.body, 'Erreur mise à jour identité'),
        );
      }
    }

    if (proFields.isNotEmpty) {
      final proResponse = await http.patch(
        Uri.parse(
          '${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}/$amUserId',
        ),
        headers: await _headers(),
        body: jsonEncode(proFields),
      );
      if (proResponse.statusCode != 200) {
        throw Exception(
          _extractErrorMessage(proResponse.body, 'Erreur mise à jour fiche pro'),
        );
      }
    }

    return getAssistanteMaternelle(amUserId);
  }

  static Future<AssistanteMaternelleModel> attachEnfantToAm({
    required String amUserId,
    required String enfantId,
  }) async {
    final response = await http.post(
      Uri.parse(
        '${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}/$amUserId/enfants/$enfantId',
      ),
      headers: await _headers(),
    );
    if (response.statusCode != 200 && response.statusCode != 201) {
      throw Exception(_extractErrorMessage(response.body, 'Erreur rattachement enfant'));
    }
    final am = _amModelFromBody(response.body);
    // Recalcule toujours places_available (capacité − enfants) après attachement.
    return syncAmPlacesAvailable(am);
  }

  /// Aligne `places_available` sur capacité − enfants rattachés.
  static Future<AssistanteMaternelleModel> syncAmPlacesAvailable(
    AssistanteMaternelleModel am,
  ) async {
    final expected = amExpectedPlacesAvailable(
      maxChildren: am.maxChildren,
      childrenCount: am.children.length,
    );
    if (expected == null || am.placesAvailable == expected) return am;
    return updateAmFiche(
      amUserId: am.user.id,
      body: {'places_available': expected},
    );
  }

  static Future<AssistanteMaternelleModel> detachEnfantFromAm({
    required String amUserId,
    required String enfantId,
  }) async {
    final response = await http.delete(
      Uri.parse(
        '${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}/$amUserId/enfants/$enfantId',
      ),
      headers: await _headers(),
    );
    if (response.statusCode != 200 && response.statusCode != 204) {
      throw Exception(_extractErrorMessage(response.body, 'Erreur détachement enfant'));
    }
    if (response.body.isEmpty) {
      return getAssistanteMaternelle(amUserId);
    }
    return _amModelFromBody(response.body);
  }

  static AssistanteMaternelleModel _amModelFromBody(String body) {
    final decoded = jsonDecode(body);
    return AssistanteMaternelleModel.fromJson(
      Map<String, dynamic>.from(decoded is Map ? decoded : {}),
    );
  }

  // 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<List<AppUser>> 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<dynamic> 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<AppUser> 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(<String, dynamic>{
        '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<String, dynamic>) {
        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<String, dynamic>;
    return AppUser.fromJson(data);
  }

  static Future<AppUser> updateAdmin({
    required String adminId,
    required String nom,
    required String prenom,
    required String email,
    required String telephone,
    String? password,
  }) async {
    final body = <String, dynamic>{
      '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<String, dynamic>) {
        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<String, dynamic>;
    return AppUser.fromJson(data);
  }

  static Future<void> 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(<String, dynamic>{'relaisId': relaisId}),
    );

    if (response.statusCode != 200 && response.statusCode != 204) {
      final err = jsonDecode(response.body) as Map<String, dynamic>?;
      throw Exception(
        _toStr(err?['message']) ?? 'Erreur rattachement relais au gestionnaire',
      );
    }
  }

  static Future<AppUser> updateGestionnaire({
    required String gestionnaireId,
    required String nom,
    required String prenom,
    required String email,
    String? telephone,
    required String? relaisId,
    String? password,
  }) async {
    final body = <String, dynamic>{
      '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<String, dynamic>) {
        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<String, dynamic>;
    return AppUser.fromJson(data);
  }

  static Future<AppUser> updateAdministrateur({
    required String adminId,
    required String nom,
    required String prenom,
    required String email,
    String? telephone,
    String? password,
  }) async {
    final body = <String, dynamic>{
      '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<String, dynamic>) {
        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<String, dynamic>;
    return AppUser.fromJson(data);
  }

  static Future<void> 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<String, dynamic>) {
        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');
    }
  }
}
