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/establishment_model.dart'; /// Source de données distante pour les établissements. /// /// Cette classe gère toutes les opérations liées aux établissements /// via l'API backend. class EstablishmentRemoteDataSource { EstablishmentRemoteDataSource(this.client); final http.Client client; static const Map _defaultHeaders = { 'Content-Type': 'application/json', 'Accept': 'application/json', }; Duration get _timeout => Duration(seconds: EnvConfig.networkTimeout); 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; 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: 'EstablishmentRemoteDataSource'); return response; } on SocketException catch (e, stackTrace) { AppLogger.e('Erreur de connexion réseau', error: e, stackTrace: stackTrace, tag: 'EstablishmentRemoteDataSource'); 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: 'EstablishmentRemoteDataSource'); throw ServerException('Erreur client HTTP: ${e.message}'); } catch (e, stackTrace) { AppLogger.e('Erreur inattendue', error: e, stackTrace: stackTrace, tag: 'EstablishmentRemoteDataSource'); rethrow; } } 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: 'EstablishmentRemoteDataSource'); switch (response.statusCode) { case 401: throw UnauthorizedException(errorMessage); case 404: throw ServerException('Établissement non trouvé: $errorMessage'); default: throw ServerException('Erreur serveur (${response.statusCode}): $errorMessage'); } } } /// Récupère tous les établissements. Future> getAllEstablishments() async { AppLogger.d('Récupération de tous les établissements', tag: 'EstablishmentRemoteDataSource'); try { final uri = Uri.parse('${Urls.baseUrl}/establishments'); final response = await _performRequest('GET', uri); final jsonList = _parseJsonResponse(response, [200]) as List; return jsonList.map((json) => EstablishmentModel.fromJson(json as Map)).toList(); } catch (e, stackTrace) { AppLogger.e('Erreur lors de la récupération des établissements', error: e, stackTrace: stackTrace, tag: 'EstablishmentRemoteDataSource'); rethrow; } } /// Recherche des établissements par nom ou ville. Future> searchEstablishments(String query) async { AppLogger.d('Recherche: $query', tag: 'EstablishmentRemoteDataSource'); try { final uri = Uri.parse('${Urls.baseUrl}/establishments/search').replace( queryParameters: {'q': query}, ); final response = await _performRequest('GET', uri); final jsonList = _parseJsonResponse(response, [200]) as List; return jsonList.map((json) => EstablishmentModel.fromJson(json as Map)).toList(); } catch (e, stackTrace) { AppLogger.e('Erreur lors de la recherche', error: e, stackTrace: stackTrace, tag: 'EstablishmentRemoteDataSource'); rethrow; } } /// Filtre les établissements par type et/ou fourchette de prix. Future> filterEstablishments({ String? type, String? priceRange, String? city, }) async { AppLogger.d('Filtrage: type=$type, priceRange=$priceRange, city=$city', tag: 'EstablishmentRemoteDataSource'); try { final queryParams = {}; if (type != null) queryParams['type'] = type; if (priceRange != null) queryParams['priceRange'] = priceRange; if (city != null) queryParams['city'] = city; final uri = Uri.parse('${Urls.baseUrl}/establishments').replace(queryParameters: queryParams); final response = await _performRequest('GET', uri); final jsonList = _parseJsonResponse(response, [200]) as List; return jsonList.map((json) => EstablishmentModel.fromJson(json as Map)).toList(); } catch (e, stackTrace) { AppLogger.e('Erreur lors du filtrage', error: e, stackTrace: stackTrace, tag: 'EstablishmentRemoteDataSource'); rethrow; } } /// Récupère un établissement par son ID. Future getEstablishmentById(String id) async { AppLogger.d('Récupération établissement: $id', tag: 'EstablishmentRemoteDataSource'); try { final uri = Uri.parse('${Urls.baseUrl}/establishments/$id'); final response = await _performRequest('GET', uri); final jsonResponse = _parseJsonResponse(response, [200]) as Map; return EstablishmentModel.fromJson(jsonResponse); } catch (e, stackTrace) { AppLogger.e('Erreur lors de la récupération de l\'établissement', error: e, stackTrace: stackTrace, tag: 'EstablishmentRemoteDataSource'); rethrow; } } }