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 '../../core/utils/app_logger.dart';
import '../models/user_model.dart';

/// Source de données distante pour les opérations liées aux utilisateurs.
///
/// Cette classe gère les appels API pour l'authentification, la récupération,
/// la création, la mise à jour et la suppression des utilisateurs.
/// Elle inclut une gestion robuste des erreurs et des logs détaillés.
class UserRemoteDataSource {
  /// Constructeur avec injection du client HTTP.
  UserRemoteDataSource(this.client);

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

  /// Exécute une requête HTTP générique avec gestion des erreurs et des logs.
  ///
  /// [method] : La méthode HTTP (GET, POST, PUT, DELETE, PATCH).
  /// [uri] : L'URI complète de la requête.
  /// [headers] : Les en-têtes de la requête (optionnel).
  /// [body] : Le corps de la requête (optionnel).
  ///
  /// Retourne la réponse HTTP.
  /// Lève une [ServerException] ou [SocketException] en cas d'erreur.
  Future<http.Response> _performRequest(
    String method,
    Uri uri, {
    Map<String, String>? headers,
    Object? body,
  }) async {
    if (EnvConfig.enableDetailedLogs) {
      AppLogger.http(method, uri.toString());
      AppLogger.d('En-têtes: $headers', tag: 'UserRemoteDataSource');
      AppLogger.d('Corps: $body', tag: 'UserRemoteDataSource');
    }

    try {
      http.Response response;
      switch (method) {
        case 'GET':
          response = await client.get(uri, headers: headers).timeout(
                Duration(seconds: EnvConfig.networkTimeout),
              );
          break;
        case 'POST':
          response = await client
              .post(uri, headers: headers, body: body)
              .timeout(Duration(seconds: EnvConfig.networkTimeout));
          break;
        case 'PUT':
          response = await client
              .put(uri, headers: headers, body: body)
              .timeout(Duration(seconds: EnvConfig.networkTimeout));
          break;
        case 'DELETE':
          response = await client.delete(uri, headers: headers).timeout(
                Duration(seconds: EnvConfig.networkTimeout),
              );
          break;
        case 'PATCH':
          response = await client
              .patch(uri, headers: headers, body: body)
              .timeout(Duration(seconds: EnvConfig.networkTimeout));
          break;
        default:
          throw ArgumentError('Méthode HTTP non supportée: $method');
      }

      if (EnvConfig.enableDetailedLogs) {
        AppLogger.http(method, uri.toString(), statusCode: response.statusCode);
        AppLogger.d('Réponse: ${response.body}', tag: 'UserRemoteDataSource');
      }
      return response;
    } on SocketException catch (e, stackTrace) {
      AppLogger.e('Erreur de connexion réseau', error: e, stackTrace: stackTrace, tag: 'UserRemoteDataSource');
      throw const ServerException('Erreur de connexion réseau. Vérifiez votre connexion internet.');
    } on http.ClientException catch (e, stackTrace) {
      AppLogger.e('Erreur client HTTP', error: e, stackTrace: stackTrace, tag: 'UserRemoteDataSource');
      throw ServerException('Erreur client HTTP: ${e.message}');
    } on FormatException catch (e, stackTrace) {
      AppLogger.e('Erreur de format de réponse JSON', error: e, stackTrace: stackTrace, tag: 'UserRemoteDataSource');
      throw const ServerException('Réponse du serveur mal formatée.');
    } on HandshakeException catch (e, stackTrace) {
      AppLogger.e('Erreur de handshake SSL/TLS', error: e, stackTrace: stackTrace, tag: 'UserRemoteDataSource');
      throw const ServerException('Erreur de sécurité: Problème de certificat SSL/TLS.');
    } catch (e, stackTrace) {
      AppLogger.e('Erreur inattendue lors de la requête', error: e, stackTrace: stackTrace, tag: 'UserRemoteDataSource');
      rethrow; // Rethrow other unexpected exceptions
    }
  }

  /// Parse la réponse JSON et gère les codes de statut HTTP.
  ///
  /// [response] : La réponse HTTP à parser.
  /// [expectedStatusCodes] : Liste des codes de statut HTTP attendus pour une réponse réussie.
  ///
  /// Retourne le corps de la réponse décodé.
  /// Lève des exceptions spécifiques en fonction du code de statut.
  dynamic _parseJsonResponse(
    http.Response response,
    List<int> expectedStatusCodes,
  ) {
    if (expectedStatusCodes.contains(response.statusCode)) {
      if (response.body.isEmpty) {
        return {}; // Retourne un objet vide pour les réponses 204 No Content
      }
      return json.decode(response.body);
    } else {
      final String errorMessage =
          json.decode(response.body)['message'] as String? ??
              'Erreur serveur inconnue';
      AppLogger.e('Erreur API (${response.statusCode}): $errorMessage', tag: 'UserRemoteDataSource');

      switch (response.statusCode) {
        case 401:
          throw UnauthorizedException(errorMessage);
        case 404:
          throw UserNotFoundException(errorMessage);
        case 409:
          throw ConflictException(errorMessage);
        default:
          throw ServerException(
            'Erreur serveur (${response.statusCode}): $errorMessage',
          );
      }
    }
  }

  /// Authentifie un utilisateur avec l'email et le mot de passe.
  ///
  /// [email] : L'email de l'utilisateur.
  /// [password] : Le mot de passe de l'utilisateur.
  ///
  /// Retourne un [UserModel] si l'authentification réussit.
  /// Lève une [UnauthorizedException] si les identifiants sont incorrects.
  /// Lève une [ServerException] pour d'autres erreurs serveur.
  Future<UserModel> authenticateUser(String email, String password) async {
    final uri = Uri.parse(Urls.authenticateUser);
    final headers = {'Content-Type': 'application/json'};
    final body = jsonEncode({'email': email, 'motDePasse': password});

    final response = await _performRequest('POST', uri, headers: headers, body: body);
    final userData = _parseJsonResponse(response, [200]) as Map<String, dynamic>;

    if (userData['userId'] != null && userData['userId'].isNotEmpty) {
      return UserModel.fromJson(userData);
    } else {
      throw const ServerException('ID utilisateur manquant dans la réponse.');
    }
  }

  /// Récupère un utilisateur par son identifiant.
  ///
  /// [id] : L'identifiant unique de l'utilisateur.
  ///
  /// Retourne un [UserModel] si l'utilisateur est trouvé.
  /// Lève une [UserNotFoundException] si l'utilisateur n'existe pas.
  /// Lève une [ServerException] pour d'autres erreurs serveur.
  Future<UserModel> getUser(String id) async {
    final uri = Uri.parse(Urls.getUserByIdWithId(id));
    final response = await _performRequest('GET', uri);
    final jsonResponse = _parseJsonResponse(response, [200]) as Map<String, dynamic>;
    return UserModel.fromJson(jsonResponse);
  }

  /// Recherche un utilisateur par email.
  ///
  /// [email] : L'email de l'utilisateur à rechercher.
  ///
  /// Retourne un [UserModel] si l'utilisateur est trouvé.
  /// Lève une [UserNotFoundException] si l'utilisateur n'existe pas.
  /// Lève une [ServerException] pour d'autres erreurs serveur.
  Future<UserModel> searchUserByEmail(String email) async {
    final uri = Uri.parse(Urls.searchUserByEmail(email));
    final response = await _performRequest('GET', uri);
    
    if (response.statusCode == 404) {
      throw UserNotFoundException('Utilisateur non trouvé avec l\'email : $email');
    }
    
    final jsonResponse = _parseJsonResponse(response, [200]) as Map<String, dynamic>;
    return UserModel.fromJson(jsonResponse);
  }

  /// Crée un nouvel utilisateur dans le backend.
  ///
  /// [user] : Le [UserModel] à créer.
  ///
  /// Retourne le [UserModel] créé avec les données du serveur.
  /// Lève une [ConflictException] si un utilisateur avec le même email existe déjà.
  /// Lève une [ServerException] pour d'autres erreurs serveur.
  Future<UserModel> createUser(UserModel user) async {
    final uri = Uri.parse(Urls.createUser);
    final headers = {'Content-Type': 'application/json'};
    final body = jsonEncode(user.toJson());

    final response = await _performRequest('POST', uri, headers: headers, body: body);
    final jsonResponse = _parseJsonResponse(response, [201]) as Map<String, dynamic>;
    return UserModel.fromJson(jsonResponse);
  }

  /// Met à jour un utilisateur existant.
  ///
  /// [user] : Le [UserModel] avec les données mises à jour.
  ///
  /// Retourne le [UserModel] mis à jour avec les données du serveur.
  /// Lève une [UserNotFoundException] si l'utilisateur n'existe pas.
  /// Lève une [ServerException] pour d'autres erreurs serveur.
  Future<UserModel> updateUser(UserModel user) async {
    final uri = Uri.parse(Urls.updateUserWithId(user.userId));
    final headers = {'Content-Type': 'application/json'};
    final body = jsonEncode(user.toJson());

    final response = await _performRequest('PUT', uri, headers: headers, body: body);
    final jsonResponse = _parseJsonResponse(response, [200]) as Map<String, dynamic>;
    return UserModel.fromJson(jsonResponse);
  }

  /// Supprime un utilisateur par son identifiant.
  ///
  /// [id] : L'identifiant unique de l'utilisateur à supprimer.
  ///
  /// Ne retourne rien en cas de succès.
  /// Lève une [ServerException] pour d'autres erreurs serveur.
  Future<void> deleteUser(String id) async {
    final uri = Uri.parse(Urls.deleteUserWithId(id));
    final response = await _performRequest('DELETE', uri);
    _parseJsonResponse(response, [204]); // 204 No Content
  }

  /// Demande la réinitialisation du mot de passe.
  ///
  /// [email] : L'email de l'utilisateur qui souhaite réinitialiser son mot de passe.
  ///
  /// Ne retourne rien en cas de succès.
  /// Lève une [UserNotFoundException] si l'utilisateur n'existe pas.
  /// Lève une [ServerException] pour d'autres erreurs serveur.
  ///
  /// **Note:** Le backend actuel ne supporte pas encore cette fonctionnalité.
  /// Cette méthode est préparée pour une future implémentation.
  Future<void> requestPasswordReset(String email) async {
    // TODO: Implémenter quand l'endpoint sera disponible dans le backend
    // Le backend actuel a seulement /users/{id}/reset-password qui nécessite l'ID
    throw const ServerException(
      'La réinitialisation du mot de passe par email n\'est pas encore disponible. '
      'Contactez l\'administrateur.',
    );
  }

  /// Réinitialise le mot de passe d'un utilisateur par son ID.
  ///
  /// [userId] : L'ID de l'utilisateur.
  /// [newPassword] : Le nouveau mot de passe.
  ///
  /// Ne retourne rien en cas de succès.
  /// Lève une [UserNotFoundException] si l'utilisateur n'existe pas.
  /// Lève une [ServerException] pour d'autres erreurs serveur.
  Future<void> resetPasswordById(String userId, String newPassword) async {
    final uri = Uri.parse('${Urls.getUserByIdWithId(userId)}/reset-password?newPassword=${Uri.encodeComponent(newPassword)}');
    final response = await _performRequest('PATCH', uri);
    _parseJsonResponse(response, [200, 204]);
  }
}
