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/notification_model.dart'; /// Source de données distante pour les notifications. /// /// Cette classe gère toutes les opérations liées aux notifications /// via l'API backend. Elle inclut la gestion d'erreurs, les timeouts, /// et la validation des réponses. /// /// **Usage:** /// ```dart /// final dataSource = NotificationRemoteDataSource(http.Client()); /// final notifications = await dataSource.getNotifications(userId); /// ``` class NotificationRemoteDataSource { /// Crée une nouvelle instance de [NotificationRemoteDataSource]. /// /// [client] Le client HTTP à utiliser pour les requêtes NotificationRemoteDataSource(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: 'NotificationRemoteDataSource'); return response; } on SocketException catch (e, stackTrace) { AppLogger.e('Erreur de connexion réseau', error: e, stackTrace: stackTrace, tag: 'NotificationRemoteDataSource'); 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: 'NotificationRemoteDataSource'); throw ServerException('Erreur client HTTP: ${e.message}'); } catch (e, stackTrace) { AppLogger.e('Erreur inattendue', error: e, stackTrace: stackTrace, tag: 'NotificationRemoteDataSource'); 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: 'NotificationRemoteDataSource'); switch (response.statusCode) { case 401: throw UnauthorizedException(errorMessage); case 404: throw ServerException('Notifications non trouvées: $errorMessage'); default: throw ServerException('Erreur serveur (${response.statusCode}): $errorMessage'); } } } // ============================================================================ // MÉTHODES PUBLIQUES // ============================================================================ /// Récupère toutes les notifications d'un utilisateur. /// /// [userId] L'identifiant de l'utilisateur /// /// Returns une liste de [NotificationModel] /// /// Throws [ServerException] en cas d'erreur Future> getNotifications(String userId) async { AppLogger.d('Récupération des notifications pour $userId', tag: 'NotificationRemoteDataSource'); try { final uri = Uri.parse('${Urls.baseUrl}/notifications/user/$userId'); final response = await _performRequest('GET', uri); final jsonList = _parseJsonResponse(response, [200]) as List; return jsonList.map((json) => NotificationModel.fromJson(json as Map)).toList(); } catch (e, stackTrace) { AppLogger.e('Erreur lors de la récupération des notifications', error: e, stackTrace: stackTrace, tag: 'NotificationRemoteDataSource'); rethrow; } } /// Marque une notification comme lue. /// /// [notificationId] L'identifiant de la notification /// /// Throws [ServerException] en cas d'erreur Future markAsRead(String notificationId) async { AppLogger.d('Marquage comme lue: $notificationId', tag: 'NotificationRemoteDataSource'); try { final uri = Uri.parse('${Urls.baseUrl}/notifications/$notificationId/read'); final response = await _performRequest('PUT', uri); _parseJsonResponse(response, [200, 204]); } catch (e, stackTrace) { AppLogger.e('Erreur lors du marquage comme lue', error: e, stackTrace: stackTrace, tag: 'NotificationRemoteDataSource'); rethrow; } } /// Marque toutes les notifications comme lues. /// /// [userId] L'identifiant de l'utilisateur /// /// Throws [ServerException] en cas d'erreur Future markAllAsRead(String userId) async { AppLogger.d('Marquage toutes comme lues pour $userId', tag: 'NotificationRemoteDataSource'); try { final uri = Uri.parse('${Urls.baseUrl}/notifications/user/$userId/mark-all-read'); final response = await _performRequest('PUT', uri); _parseJsonResponse(response, [200, 204]); } catch (e, stackTrace) { AppLogger.e('Erreur lors du marquage toutes comme lues', error: e, stackTrace: stackTrace, tag: 'NotificationRemoteDataSource'); rethrow; } } /// Supprime une notification. /// /// [notificationId] L'identifiant de la notification /// /// Throws [ServerException] en cas d'erreur Future deleteNotification(String notificationId) async { AppLogger.i('Suppression: $notificationId', tag: 'NotificationRemoteDataSource'); try { final uri = Uri.parse('${Urls.baseUrl}/notifications/$notificationId'); final response = await _performRequest('DELETE', uri); _parseJsonResponse(response, [200, 204]); } catch (e, stackTrace) { AppLogger.e('Erreur lors de la suppression', error: e, stackTrace: stackTrace, tag: 'NotificationRemoteDataSource'); rethrow; } } }