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/reservation_model.dart'; /// Source de données distante pour les réservations. /// /// Cette classe gère toutes les opérations liées aux réservations /// via l'API backend. Elle inclut la gestion d'erreurs, les timeouts, /// et la validation des réponses. /// /// **Usage:** /// ```dart /// final dataSource = ReservationRemoteDataSource(http.Client()); /// final reservations = await dataSource.getReservationsByUser(userId); /// ``` class ReservationRemoteDataSource { /// Crée une nouvelle instance de [ReservationRemoteDataSource]. /// /// [client] Le client HTTP à utiliser pour les requêtes ReservationRemoteDataSource(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 _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 _performRequest( String method, Uri uri, { Map? headers, Object? body, }) async { AppLogger.http(method, uri.toString()); try { http.Response response; final requestHeaders = {..._defaultHeaders, ...?headers}; switch (method) { 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 'PUT': response = await client .put(uri, headers: requestHeaders, body: body) .timeout(_timeout); break; case 'DELETE': response = await client .delete(uri, headers: requestHeaders) .timeout(_timeout); break; default: throw ArgumentError('Méthode HTTP non supportée: $method'); } AppLogger.http(method, uri.toString(), statusCode: response.statusCode); AppLogger.d('Réponse: ${response.body}', tag: 'ReservationRemoteDataSource'); return response; } on SocketException catch (e, stackTrace) { AppLogger.e('Erreur de connexion réseau', error: e, stackTrace: stackTrace, tag: 'ReservationRemoteDataSource'); 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: 'ReservationRemoteDataSource'); throw ServerException('Erreur client HTTP: ${e.message}'); } catch (e, stackTrace) { AppLogger.e('Erreur inattendue', error: e, stackTrace: stackTrace, tag: 'ReservationRemoteDataSource'); rethrow; } } /// Parse la réponse JSON et gère les codes de statut HTTP. dynamic _parseJsonResponse(http.Response response, List expectedStatusCodes) { if (expectedStatusCodes.contains(response.statusCode)) { if (response.body.isEmpty) { return []; } return json.decode(response.body); } else { final errorMessage = (json.decode(response.body) as Map?)?['message'] as String? ?? 'Erreur serveur inconnue'; AppLogger.e('Erreur (${response.statusCode}): $errorMessage', tag: 'ReservationRemoteDataSource'); switch (response.statusCode) { case 401: throw UnauthorizedException(errorMessage); case 404: throw ServerException('Réservation non trouvée: $errorMessage'); default: throw ServerException('Erreur serveur (${response.statusCode}): $errorMessage'); } } } // ============================================================================ // MÉTHODES PUBLIQUES // ============================================================================ /// Récupère toutes les réservations d'un utilisateur. /// /// [userId] L'identifiant de l'utilisateur /// /// Returns une liste de [ReservationModel] /// /// Throws [ServerException] en cas d'erreur Future> getReservationsByUser(String userId) async { AppLogger.d('Récupération des réservations pour $userId', tag: 'ReservationRemoteDataSource'); try { final uri = Uri.parse('${Urls.baseUrl}/reservations/user/$userId'); final response = await _performRequest('GET', uri); final jsonList = _parseJsonResponse(response, [200]) as List; return jsonList.map((json) => ReservationModel.fromJson(json as Map)).toList(); } catch (e, stackTrace) { AppLogger.e('Erreur lors de la récupération des réservations', error: e, stackTrace: stackTrace, tag: 'ReservationRemoteDataSource'); rethrow; } } /// Crée une nouvelle réservation. /// /// [reservation] Le modèle de réservation à créer /// /// Returns le [ReservationModel] créé /// /// Throws [ServerException] en cas d'erreur Future createReservation(ReservationModel reservation) async { AppLogger.i('Création réservation: ${reservation.eventTitle}', tag: 'ReservationRemoteDataSource'); try { final uri = Uri.parse('${Urls.baseUrl}/reservations'); final body = jsonEncode(reservation.toJson()); final response = await _performRequest('POST', uri, body: body); final jsonResponse = _parseJsonResponse(response, [200, 201]) as Map; return ReservationModel.fromJson(jsonResponse); } catch (e, stackTrace) { AppLogger.e('Erreur lors de la création de la réservation', error: e, stackTrace: stackTrace, tag: 'ReservationRemoteDataSource'); rethrow; } } /// Met à jour une réservation existante. /// /// [reservation] Le modèle de réservation avec les modifications /// /// Returns le [ReservationModel] mis à jour /// /// Throws [ServerException] en cas d'erreur Future updateReservation(ReservationModel reservation) async { AppLogger.i('Mise à jour réservation: ${reservation.id}', tag: 'ReservationRemoteDataSource'); try { final uri = Uri.parse('${Urls.baseUrl}/reservations/${reservation.id}'); final body = jsonEncode(reservation.toJson()); final response = await _performRequest('PUT', uri, body: body); final jsonResponse = _parseJsonResponse(response, [200]) as Map; return ReservationModel.fromJson(jsonResponse); } catch (e, stackTrace) { AppLogger.e('Erreur lors de la mise à jour de la réservation', error: e, stackTrace: stackTrace, tag: 'ReservationRemoteDataSource'); rethrow; } } /// Annule une réservation. /// /// [reservationId] L'identifiant de la réservation à annuler /// /// Returns le [ReservationModel] avec le statut annulé /// /// Throws [ServerException] en cas d'erreur Future cancelReservation(String reservationId) async { AppLogger.i('Annulation réservation: $reservationId', tag: 'ReservationRemoteDataSource'); try { final uri = Uri.parse('${Urls.baseUrl}/reservations/$reservationId/cancel'); final response = await _performRequest('PUT', uri); final jsonResponse = _parseJsonResponse(response, [200]) as Map; return ReservationModel.fromJson(jsonResponse); } catch (e, stackTrace) { AppLogger.e('Erreur lors de l\'annulation de la réservation', error: e, stackTrace: stackTrace, tag: 'ReservationRemoteDataSource'); rethrow; } } /// Supprime une réservation. /// /// [reservationId] L'identifiant de la réservation à supprimer /// /// Throws [ServerException] en cas d'erreur Future deleteReservation(String reservationId) async { AppLogger.i('Suppression réservation: $reservationId', tag: 'ReservationRemoteDataSource'); try { final uri = Uri.parse('${Urls.baseUrl}/reservations/$reservationId'); final response = await _performRequest('DELETE', uri); _parseJsonResponse(response, [200, 204]); } catch (e, stackTrace) { AppLogger.e('Erreur lors de la suppression de la réservation', error: e, stackTrace: stackTrace, tag: 'ReservationRemoteDataSource'); rethrow; } } }