import 'dart:convert';
import 'dart:io';

import 'package:http/http.dart' as http;

import '../../core/constants/env_config.dart';
import '../../core/constants/urls.dart';
import '../../core/errors/exceptions.dart';
import '../../domain/entities/friend.dart';
import '../../domain/entities/friend_request.dart';
import 'friends_repository.dart';

/// Implémentation de [FriendsRepository] pour gérer les appels API relatifs aux amis.
///
/// Cette classe gère toutes les opérations sur les amis via l'API backend,
/// avec gestion d'erreurs robuste, timeouts, et validation des réponses.
///
/// **Usage:**
/// ```dart
/// final repository = FriendsRepositoryImpl(client: http.Client());
/// final friends = await repository.fetchFriends('user123', 0, 20);
/// ```
class FriendsRepositoryImpl implements FriendsRepository {
  /// Crée une nouvelle instance de [FriendsRepositoryImpl].
  ///
  /// [client] Le client HTTP à utiliser pour les requêtes
  FriendsRepositoryImpl({required this.client});

  /// Client HTTP pour effectuer les requêtes réseau
  final http.Client client;

  /// Headers par défaut pour les requêtes
  static const Map<String, String> _defaultHeaders = {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  };

  /// Timeout pour les requêtes réseau
  Duration get _timeout => Duration(seconds: EnvConfig.networkTimeout);

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

  /// Effectue une requête HTTP avec gestion d'erreurs et timeout.
  Future<http.Response> _performRequest(
    String method,
    Uri uri, {
    Map<String, String>? headers,
    Object? body,
  }) async {
    try {
      final requestHeaders = {
        ..._defaultHeaders,
        if (headers != null) ...headers,
      };

      http.Response response;

      switch (method.toUpperCase()) {
        case 'GET':
          response = await client
              .get(uri, headers: requestHeaders)
              .timeout(_timeout);
          break;
        case 'POST':
          response = await client
              .post(uri, headers: requestHeaders, body: body)
              .timeout(_timeout);
          break;
        case 'DELETE':
          response = await client
              .delete(uri, headers: requestHeaders)
              .timeout(_timeout);
          break;
        case 'PATCH':
          response = await client
              .patch(uri, headers: requestHeaders, body: body)
              .timeout(_timeout);
          break;
        default:
          throw ArgumentError('Méthode HTTP non supportée: $method');
      }

      return response;
    } on SocketException {
      throw ServerException(
        'Erreur de connexion réseau. Vérifiez votre connexion Internet.',
        statusCode: null,
      );
    } on HttpException catch (e) {
      throw ServerException(
        'Erreur HTTP: ${e.message}',
        statusCode: null,
      );
    } on FormatException catch (e) {
      throw ServerException(
        'Erreur de format de réponse: ${e.message}',
        statusCode: null,
      );
    } catch (e) {
      if (e is ServerException) rethrow;
      throw ServerException(
        'Erreur inattendue: $e',
        statusCode: null,
        originalError: e,
      );
    }
  }

  /// Parse une réponse JSON et gère les erreurs.
  dynamic _parseJsonResponse(
    http.Response response,
    List<int> expectedStatusCodes,
  ) {
    if (!expectedStatusCodes.contains(response.statusCode)) {
      _handleErrorResponse(response);
    }

    try {
      if (response.body.isEmpty) {
        return null;
      }
      return json.decode(response.body);
    } on FormatException catch (e) {
      throw ServerException(
        'Erreur de parsing JSON: ${e.message}',
        statusCode: response.statusCode,
      );
    }
  }

  /// Gère les erreurs de réponse HTTP.
  void _handleErrorResponse(http.Response response) {
    String errorMessage;

    try {
      if (response.body.isNotEmpty) {
        final errorBody = json.decode(response.body);
        
        // Gérer le format Bean Validation (Quarkus)
        if (errorBody is Map && errorBody.containsKey('attributeName')) {
          final attributeName = errorBody['attributeName'] as String? ?? 'champ';
          final value = errorBody['value'] as String?;
          final objectName = errorBody['objectName'] as String? ?? '';
          
          // Construire un message d'erreur plus clair
          if (attributeName == 'friendId' && value != null) {
            errorMessage = 'L\'identifiant "$value" n\'est pas valide. Veuillez utiliser l\'ID utilisateur (UUID) et non l\'email.';
          } else {
            errorMessage = 'Erreur de validation sur le champ "$attributeName"';
            if (value != null) {
              errorMessage += ': valeur "$value" invalide';
            }
          }
        } else {
          // Essayer plusieurs formats de réponse d'erreur standard
          errorMessage = errorBody['message'] as String? ??
              errorBody['error'] as String? ??
              errorBody['errorMessage'] as String? ??
              (errorBody is Map && errorBody.isNotEmpty
                  ? errorBody.values.first.toString()
                  : 'Erreur serveur inconnue');
        }
        
        // Log détaillé pour le débogage
        if (EnvConfig.enableDetailedLogs) {
          _log('Réponse d\'erreur du serveur (${response.statusCode}): ${response.body}');
          _log('Message d\'erreur extrait: $errorMessage');
        }
      } else {
        errorMessage = 'Erreur serveur (${response.statusCode})';
      }
    } catch (e) {
      // Si le parsing JSON échoue, utiliser le body brut
      errorMessage = response.body.isNotEmpty
          ? response.body
          : 'Erreur serveur (${response.statusCode})';
      
      if (EnvConfig.enableDetailedLogs) {
        _log('Erreur lors du parsing de la réponse d\'erreur: $e');
        _log('Body brut: ${response.body}');
      }
    }

    switch (response.statusCode) {
      case 400:
        throw ValidationException(errorMessage);
      case 401:
        throw UnauthorizedException(errorMessage);
      case 404:
        throw ServerException(
          'Ressource non trouvée: $errorMessage',
          statusCode: 404,
        );
      case 409:
        throw ConflictException(errorMessage);
      case 500:
      case 502:
      case 503:
        throw ServerException(
          'Erreur serveur: $errorMessage',
          statusCode: response.statusCode,
        );
      default:
        throw ServerException(
          errorMessage,
          statusCode: response.statusCode,
        );
    }
  }

  /// Vérifie si une chaîne est un UUID valide
  bool _isValidUUID(String value) {
    final uuidRegex = RegExp(
      r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
      caseSensitive: false,
    );
    return uuidRegex.hasMatch(value);
  }

  /// Log un message si le mode debug est activé.
  void _log(String message) {
    if (EnvConfig.enableDetailedLogs) {
      print('[FriendsRepositoryImpl] $message');
    }
  }

  // ============================================================================
  // MÉTHODES PUBLIQUES
  // ============================================================================

  /// Récupère la liste paginée des amis pour un utilisateur donné.
  ///
  /// [userId] L'identifiant unique de l'utilisateur
  /// [page] Le numéro de page (commence à 0)
  /// [size] Le nombre d'amis par page
  ///
  /// Returns une liste d'objets [Friend]
  ///
  /// Throws [ServerException] en cas d'erreur
  ///
  /// **Exemple:**
  /// ```dart
  /// final friends = await repository.fetchFriends('user123', 0, 20);
  /// ```
  @override
  Future<List<Friend>> fetchFriends(String userId, int page, int size) async {
    _log('Récupération des amis pour l\'utilisateur $userId (page $page, taille $size)');

    if (userId.isEmpty) {
      throw ValidationException('L\'ID utilisateur ne peut pas être vide');
    }

    if (page < 0) {
      throw ValidationException('Le numéro de page doit être >= 0');
    }

    if (size <= 0) {
      throw ValidationException('La taille de page doit être > 0');
    }

    try {
      final uri = Uri.parse('${Urls.friendsBase}/list/$userId')
          .replace(queryParameters: {
        'page': page.toString(),
        'size': size.toString(),
      });

      final response = await _performRequest('GET', uri);

      // Gérer le cas 404 comme une liste vide
      if (response.statusCode == 404) {
        _log('Aucun ami trouvé (404) - retour d\'une liste vide');
        return [];
      }

      final jsonResponse = _parseJsonResponse(response, [200]) as List<dynamic>?;

      if (jsonResponse == null) {
        return [];
      }

      final friends = jsonResponse
          .map((json) => Friend.fromJson(json as Map<String, dynamic>))
          .toList();

      _log('${friends.length} amis récupérés avec succès');
      return friends;
    } catch (e) {
      _log('Erreur lors de la récupération des amis: $e');
      rethrow;
    }
  }

  /// Envoie une demande pour ajouter un nouvel ami.
  ///
  /// [userId] L'identifiant unique de l'utilisateur qui envoie la demande
  /// [friendId] L'identifiant unique de l'ami à ajouter
  ///
  /// Throws [ServerException] en cas d'erreur
  ///
  /// **Exemple:**
  /// ```dart
  /// await repository.addFriend('user-uuid', 'friend-uuid');
  /// ```
  @override
  Future<void> addFriend(String userId, String friendId) async {
    _log('Ajout de l\'ami: userId=$userId, friendId=$friendId');

    if (userId.isEmpty) {
      throw ValidationException('L\'ID de l\'utilisateur ne peut pas être vide');
    }

    if (friendId.isEmpty) {
      throw ValidationException('L\'ID de l\'ami ne peut pas être vide');
    }

    try {
      final uri = Uri.parse('${Urls.friendsBase}/send');
      
      // Le backend attend userId et friendId dans FriendshipCreateOneRequestDTO
      final bodyJson = {
        'userId': userId,
        'friendId': friendId,
      };
      final body = jsonEncode(bodyJson);

      // Log détaillé du body envoyé
      if (EnvConfig.enableDetailedLogs) {
        _log('Envoi de la demande d\'ami à: $uri');
        _log('Body JSON: $body');
      }

      final response = await _performRequest(
        'POST',
        uri,
        body: body,
      );

      // Log de la réponse
      if (EnvConfig.enableDetailedLogs) {
        _log('Réponse du serveur (${response.statusCode}): ${response.body}');
      }

      if (![200, 201].contains(response.statusCode)) {
        _handleErrorResponse(response);
      }

      _log('Ami ajouté avec succès: $friendId');
    } catch (e) {
      _log('Erreur lors de l\'ajout de l\'ami: $e');
      rethrow;
    }
  }

  /// Supprime un ami existant.
  ///
  /// [friendId] L'identifiant unique de l'ami à supprimer
  ///
  /// Throws [ServerException] en cas d'erreur
  ///
  /// **Exemple:**
  /// ```dart
  /// await repository.removeFriend('friend123');
  /// ```
  @override
  Future<void> removeFriend(String friendId) async {
    _log('Suppression de l\'ami $friendId');

    if (friendId.isEmpty) {
      throw ValidationException('L\'ID de l\'ami ne peut pas être vide');
    }

    try {
      final uri = Uri.parse('${Urls.friendsBase}/$friendId');
      final response = await _performRequest('DELETE', uri);

      if (![200, 204].contains(response.statusCode)) {
        _handleErrorResponse(response);
      }

      _log('Ami $friendId supprimé avec succès');
    } catch (e) {
      _log('Erreur lors de la suppression de l\'ami $friendId: $e');
      rethrow;
    }
  }

  /// Récupère les détails d'un ami.
  ///
  /// [friendId] L'identifiant unique de l'ami
  /// [userId] L'identifiant unique de l'utilisateur connecté
  ///
  /// Returns un [Friend] avec les informations de l'ami, ou `null` si non trouvé
  ///
  /// **Exemple:**
  /// ```dart
  /// final friend = await repository.getFriendDetails('friend123', 'user123');
  /// ```
  @override
  Future<Friend?> getFriendDetails(String friendId, String userId) async {
    _log('Récupération des détails de l\'ami $friendId pour l\'utilisateur $userId');

    if (friendId.isEmpty || userId.isEmpty) {
      throw ValidationException('Les IDs ne peuvent pas être vides');
    }

    try {
      final uri = Uri.parse('${Urls.friendsBase}/details');
      final body = jsonEncode({
        'friendId': friendId,
        'userId': userId,
      });

      final response = await _performRequest(
        'POST',
        uri,
        body: body,
      );

      // Gérer le cas 404 comme null
      if (response.statusCode == 404) {
        _log('Ami $friendId non trouvé (404)');
        return null;
      }

      final jsonResponse = _parseJsonResponse(response, [200]) as Map<String, dynamic>?;

      if (jsonResponse == null) {
        return null;
      }

      final friend = Friend.fromJson(jsonResponse);
      _log('Détails de l\'ami $friendId récupérés avec succès');
      return friend;
    } catch (e) {
      _log('Erreur lors de la récupération des détails de l\'ami $friendId: $e');
      rethrow;
    }
  }

  /// Met à jour le statut d'un ami (par exemple, "accepté", "bloqué").
  ///
  /// [friendId] L'identifiant unique de l'ami
  /// [status] Le nouveau statut sous forme de chaîne de caractères
  ///
  /// Throws [ServerException] en cas d'erreur
  ///
  /// **Exemple:**
  /// ```dart
  /// await repository.updateFriendStatus('friend123', 'accepted');
  /// ```
  @override
  Future<void> updateFriendStatus(String friendId, String status) async {
    _log('Mise à jour du statut de l\'ami $friendId: $status');

    if (friendId.isEmpty) {
      throw ValidationException('L\'ID de l\'ami ne peut pas être vide');
    }

    if (status.isEmpty) {
      throw ValidationException('Le statut ne peut pas être vide');
    }

    try {
      final uri = Uri.parse('${Urls.friendsBase}/$friendId/status');
      final body = jsonEncode({'status': status});

      final response = await _performRequest(
        'PATCH',
        uri,
        body: body,
      );

      if (response.statusCode != 200) {
        _handleErrorResponse(response);
      }

      _log('Statut de l\'ami $friendId mis à jour avec succès');
    } catch (e) {
      _log('Erreur lors de la mise à jour du statut de l\'ami $friendId: $e');
      rethrow;
    }
  }

  /// Récupère les demandes d'amitié en attente pour un utilisateur.
  ///
  /// [userId] L'identifiant unique de l'utilisateur
  /// [page] Le numéro de la page pour la pagination
  /// [size] La taille de la page
  ///
  /// Retourne une liste de [FriendRequest]
  @override
  Future<List<FriendRequest>> getPendingFriendRequests(String userId, int page, int size) async {
    _log('Récupération des demandes d\'amitié en attente pour l\'utilisateur $userId (page $page, taille $size)');

    try {
      final uri = Uri.parse(Urls.getPendingFriendRequestsWithUserId(userId, page: page, size: size));
      final response = await _performRequest('GET', uri);

      if (response.statusCode != 200) {
        _handleErrorResponse(response);
      }

      final jsonResponse = _parseJsonResponse(response, [200]) as List<dynamic>;
      final requests = jsonResponse
          .map((json) => FriendRequest.fromJson(json as Map<String, dynamic>))
          .toList();

      _log('${requests.length} demandes d\'amitié en attente récupérées avec succès');
      return requests;
    } catch (e) {
      _log('Erreur lors de la récupération des demandes d\'amitié en attente: $e');
      rethrow;
    }
  }

  /// Accepte une demande d'amitié.
  ///
  /// [friendshipId] L'identifiant unique de la relation d'amitié
  ///
  /// Throws [ServerException] en cas d'erreur
  @override
  Future<void> acceptFriendRequest(String friendshipId) async {
    _log('Acceptation de la demande d\'amitié: $friendshipId');

    if (friendshipId.isEmpty) {
      throw ValidationException('L\'ID de la relation d\'amitié ne peut pas être vide');
    }

    try {
      final uri = Uri.parse(Urls.acceptFriendRequestWithId(friendshipId));
      final response = await _performRequest('PATCH', uri);

      if (response.statusCode != 200) {
        _handleErrorResponse(response);
      }

      _log('Demande d\'amitié acceptée avec succès: $friendshipId');
    } catch (e) {
      _log('Erreur lors de l\'acceptation de la demande d\'amitié: $e');
      rethrow;
    }
  }

  /// Rejette une demande d'amitié.
  ///
  /// [friendshipId] L'identifiant unique de la relation d'amitié
  ///
  /// Throws [ServerException] en cas d'erreur
  @override
  Future<void> rejectFriendRequest(String friendshipId) async {
    _log('Rejet de la demande d\'amitié: $friendshipId');

    if (friendshipId.isEmpty) {
      throw ValidationException('L\'ID de la relation d\'amitié ne peut pas être vide');
    }

    try {
      final uri = Uri.parse(Urls.rejectFriendRequestWithId(friendshipId));
      final response = await _performRequest('PATCH', uri);

      if (response.statusCode != 204) {
        _handleErrorResponse(response);
      }

      _log('Demande d\'amitié rejetée avec succès: $friendshipId');
    } catch (e) {
      _log('Erreur lors du rejet de la demande d\'amitié: $e');
      rethrow;
    }
  }

  /// Récupère les demandes d'amitié envoyées par un utilisateur.
  ///
  /// [userId] L'identifiant unique de l'utilisateur
  /// [page] Le numéro de page (commence à 0)
  /// [size] Le nombre de demandes par page
  ///
  /// Returns une liste d'objets [FriendRequest]
  ///
  /// Throws [ServerException] en cas d'erreur
  @override
  Future<List<FriendRequest>> getSentFriendRequests(String userId, int page, int size) async {
    _log('Récupération des demandes d\'amitié envoyées pour l\'utilisateur $userId (page $page, taille $size)');

    try {
      final uri = Uri.parse(Urls.getSentFriendRequestsWithUserId(userId, page: page, size: size));
      final response = await _performRequest('GET', uri);

      if (response.statusCode == 404) {
        _log('Aucune demande envoyée trouvée (404) - retour d\'une liste vide');
        return [];
      }

      final jsonResponse = _parseJsonResponse(response, [200]) as List<dynamic>;
      final requests = jsonResponse
          .map((json) => FriendRequest.fromJson(json as Map<String, dynamic>))
          .toList();

      _log('${requests.length} demandes d\'amitié envoyées récupérées avec succès');
      return requests;
    } catch (e) {
      _log('Erreur lors de la récupération des demandes d\'amitié envoyées: $e');
      rethrow;
    }
  }

  /// Récupère les demandes d'amitié reçues par un utilisateur.
  ///
  /// [userId] L'identifiant unique de l'utilisateur
  /// [page] Le numéro de page (commence à 0)
  /// [size] Le nombre de demandes par page
  ///
  /// Returns une liste d'objets [FriendRequest]
  ///
  /// Throws [ServerException] en cas d'erreur
  @override
  Future<List<FriendRequest>> getReceivedFriendRequests(String userId, int page, int size) async {
    _log('Récupération des demandes d\'amitié reçues pour l\'utilisateur $userId (page $page, taille $size)');

    try {
      final uri = Uri.parse(Urls.getReceivedFriendRequestsWithUserId(userId, page: page, size: size));
      final response = await _performRequest('GET', uri);

      if (response.statusCode == 404) {
        _log('Aucune demande reçue trouvée (404) - retour d\'une liste vide');
        return [];
      }

      final jsonResponse = _parseJsonResponse(response, [200]) as List<dynamic>;
      
      if (EnvConfig.enableDetailedLogs) {
        _log('Réponse JSON brute: $jsonResponse');
      }
      
      final requests = jsonResponse
          .map((json) {
            if (EnvConfig.enableDetailedLogs) {
              _log('Parsing demande: $json');
            }
            return FriendRequest.fromJson(json as Map<String, dynamic>);
          })
          .toList();

      _log('${requests.length} demandes d\'amitié reçues récupérées avec succès');
      if (EnvConfig.enableDetailedLogs && requests.isNotEmpty) {
        _log('Première demande: userId=${requests.first.userId}, friendId=${requests.first.friendId}, userFullName=${requests.first.userFullName}');
      }
      return requests;
    } catch (e) {
      _log('Erreur lors de la récupération des demandes d\'amitié reçues: $e');
      rethrow;
    }
  }

  /// Annule une demande d'amitié envoyée (supprime la relation).
  ///
  /// [friendshipId] L'identifiant unique de la relation d'amitié
  ///
  /// Throws [ServerException] en cas d'erreur
  @override
  Future<void> cancelFriendRequest(String friendshipId) async {
    _log('Annulation de la demande d\'amitié: $friendshipId');

    if (friendshipId.isEmpty) {
      throw ValidationException('L\'ID de la relation d\'amitié ne peut pas être vide');
    }

    try {
      // Utiliser l'endpoint DELETE existant pour supprimer la relation
      final uri = Uri.parse('${Urls.friendsBase}/$friendshipId');
      final response = await _performRequest('DELETE', uri);

      if (response.statusCode != 204) {
        _handleErrorResponse(response);
      }

      _log('Demande d\'amitié annulée avec succès: $friendshipId');
    } catch (e) {
      _log('Erreur lors de l\'annulation de la demande d\'amitié: $e');
      rethrow;
    }
  }
}
