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/event_model.dart';

/// Source de données distante pour les événements.
///
/// Cette classe gère toutes les opérations CRUD sur les événements
/// via l'API backend. Elle inclut la gestion d'erreurs, les timeouts,
/// et la validation des réponses.
///
/// **Usage:**
/// ```dart
/// final dataSource = EventRemoteDataSource(http.Client());
/// final events = await dataSource.getAllEvents();
/// ```
class EventRemoteDataSource {
  /// Crée une nouvelle instance de [EventRemoteDataSource].
  ///
  /// [client] Le client HTTP à utiliser pour les requêtes
  EventRemoteDataSource(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.
  ///
  /// [method] La méthode HTTP (GET, POST, PUT, DELETE, PATCH)
  /// [uri] L'URI de la requête
  /// [headers] Les headers de la requête
  /// [body] Le corps de la requête (optionnel)
  ///
  /// Returns la réponse HTTP
  ///
  /// Throws [ServerException] en cas d'erreur
  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 '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;
        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.
  ///
  /// [response] La réponse HTTP
  /// [expectedStatusCodes] Les codes de statut attendus (par défaut: [200])
  ///
  /// Returns les données JSON décodées
  ///
  /// Throws [ServerException] si le code de statut n'est pas attendu
  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.
  ///
  /// [response] La réponse HTTP avec erreur
  ///
  /// Throws [ServerException] avec un message approprié
  void _handleErrorResponse(http.Response response) {
    String errorMessage;

    try {
      final errorBody = json.decode(response.body);
      errorMessage = errorBody['message'] as String? ??
          errorBody['error'] as String? ??
          'Erreur serveur inconnue';
    } catch (e) {
      errorMessage = response.body.isNotEmpty
          ? response.body
          : 'Erreur serveur (${response.statusCode})';
    }

    switch (response.statusCode) {
      case 400:
        throw ValidationException(errorMessage);
      case 401:
        throw UnauthorizedException(errorMessage);
      case 404:
        throw ServerException(
          'Ressource non trouvée',
          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,
        );
    }
  }

  /// Log un message si le mode debug est activé.
  ///
  /// [message] Le message à logger
  void _log(String message) {
    AppLogger.d(message, tag: 'EventRemoteDataSource');
  }

  // ============================================================================
  // MÉTHODES PUBLIQUES - CRUD ÉVÉNEMENTS
  // ============================================================================

  /// Récupère tous les événements depuis l'API.
  ///
  /// Returns une liste de [EventModel]
  ///
  /// Throws [ServerException] en cas d'erreur
  ///
  /// **Exemple:**
  /// ```dart
  /// final events = await dataSource.getAllEvents();
  /// ```
  Future<List<EventModel>> getAllEvents() async {
    _log('Récupération de tous les événements');

    try {
      final uri = Uri.parse(Urls.getAllEvents);
      final response = await _performRequest('GET', uri);

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

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

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

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

  /// Récupère les événements créés par un utilisateur et ses amis.
  ///
  /// [userId] L'identifiant de l'utilisateur
  ///
  /// Returns une liste de [EventModel]
  ///
  /// Throws [ServerException] en cas d'erreur
  ///
  /// **Exemple:**
  /// ```dart
  /// final events = await dataSource.getEventsCreatedByUserAndFriends('user123');
  /// ```
  Future<List<EventModel>> getEventsCreatedByUserAndFriends(
    String userId,
  ) async {
    _log('Récupération des événements pour l\'utilisateur $userId et ses amis');

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

    try {
      final uri = Uri.parse(Urls.getEventsCreatedByUserAndFriends);
      final body = jsonEncode({'userId': userId});

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

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

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

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

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

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

  /// Récupère les événements de l'utilisateur et de ses amis (avec pagination).
  ///
  /// [userId] L'identifiant de l'utilisateur
  /// [page] Le numéro de la page (0-indexé)
  /// [size] La taille de la page
  ///
  /// Returns une liste de [EventModel]
  ///
  /// Throws [ServerException] en cas d'erreur
  ///
  /// **Exemple:**
  /// ```dart
  /// final events = await dataSource.getEventsByFriends(
  ///   userId: 'user123',
  ///   page: 0,
  ///   size: 20,
  /// );
  /// ```
  Future<List<EventModel>> getEventsByFriends({
    required String userId,
    int page = 0,
    int size = 20,
  }) async {
    _log('Récupération des événements des amis pour $userId (page: $page, size: $size)');

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

    try {
      final uri = Uri.parse(
        '${Urls.getEventsByFriends(userId)}?page=$page&size=$size',
      );

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

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

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

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

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

  /// Récupère un événement par son ID.
  ///
  /// [id] L'identifiant de l'événement
  ///
  /// Returns un [EventModel]
  ///
  /// Throws [ServerException] si l'événement n'est pas trouvé
  ///
  /// **Exemple:**
  /// ```dart
  /// final event = await dataSource.getEventById('event123');
  /// ```
  Future<EventModel> getEventById(String id) async {
    _log('Récupération de l\'événement $id');

    if (id.isEmpty) {
      throw ValidationException('L\'ID de l\'événement ne peut pas être vide');
    }

    try {
      final uri = Uri.parse(Urls.getEventByIdWithId(id));
      final response = await _performRequest('GET', uri);

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

      final event = EventModel.fromJson(jsonResponse);
      _log('Événement $id récupéré avec succès');
      return event;
    } catch (e) {
      _log('Erreur lors de la récupération de l\'événement $id: $e');
      rethrow;
    }
  }

  /// Crée un nouvel événement.
  ///
  /// [event] Le modèle d'événement à créer
  ///
  /// Returns le [EventModel] créé avec l'ID généré par le serveur
  ///
  /// Throws [ServerException] en cas d'erreur
  ///
  /// **Exemple:**
  /// ```dart
  /// final newEvent = EventModel(...);
  /// final createdEvent = await dataSource.createEvent(newEvent);
  /// ```
  Future<EventModel> createEvent(EventModel event) async {
    _log('Création d\'un nouvel événement: ${event.title}');

    try {
      final uri = Uri.parse(Urls.createEvent);
      final body = jsonEncode(event.toJson());

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

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

      final createdEvent = EventModel.fromJson(jsonResponse);
      _log('Événement créé avec succès: ${createdEvent.id}');
      return createdEvent;
    } catch (e) {
      _log('Erreur lors de la création de l\'événement: $e');
      rethrow;
    }
  }

  /// Met à jour un événement existant.
  ///
  /// [id] L'identifiant de l'événement à mettre à jour
  /// [event] Le modèle d'événement avec les nouvelles données
  ///
  /// Returns le [EventModel] mis à jour
  ///
  /// Throws [ServerException] en cas d'erreur
  ///
  /// **Exemple:**
  /// ```dart
  /// final updatedEvent = event.copyWith(title: 'Nouveau titre');
  /// final result = await dataSource.updateEvent('event123', updatedEvent);
  /// ```
  Future<EventModel> updateEvent(String id, EventModel event) async {
    _log('Mise à jour de l\'événement $id');

    if (id.isEmpty) {
      throw ValidationException('L\'ID de l\'événement ne peut pas être vide');
    }

    try {
      final uri = Uri.parse(Urls.updateEventWithId(id));
      final body = jsonEncode(event.toJson());

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

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

      final updatedEvent = EventModel.fromJson(jsonResponse);
      _log('Événement $id mis à jour avec succès');
      return updatedEvent;
    } catch (e) {
      _log('Erreur lors de la mise à jour de l\'événement $id: $e');
      rethrow;
    }
  }

  /// Supprime un événement.
  ///
  /// [id] L'identifiant de l'événement à supprimer
  ///
  /// Throws [ServerException] en cas d'erreur
  ///
  /// **Exemple:**
  /// ```dart
  /// await dataSource.deleteEvent('event123');
  /// ```
  Future<void> deleteEvent(String id) async {
    _log('Suppression de l\'événement $id');

    if (id.isEmpty) {
      throw ValidationException('L\'ID de l\'événement ne peut pas être vide');
    }

    try {
      final uri = Uri.parse(Urls.deleteEventWithId(id));
      final response = await _performRequest('DELETE', uri);

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

      _log('Événement $id supprimé avec succès');
    } catch (e) {
      _log('Erreur lors de la suppression de l\'événement $id: $e');
      rethrow;
    }
  }

  // ============================================================================
  // MÉTHODES PUBLIQUES - ACTIONS SUR ÉVÉNEMENTS
  // ============================================================================

  /// Participe à un événement (utilise l'endpoint participants du backend).
  ///
  /// [eventId] L'identifiant de l'événement
  /// [userId] L'identifiant de l'utilisateur
  ///
  /// Returns le [EventModel] mis à jour avec le nouveau participant
  ///
  /// Throws [ServerException] en cas d'erreur
  Future<EventModel> participateInEvent(String eventId, String userId) async {
    _log('Participation de l\'utilisateur $userId à l\'événement $eventId');

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

    try {
      // Utiliser l'endpoint participants du backend
      // Le backend attend un objet Users avec l'id
      final uri = Uri.parse(Urls.participateInEventWithId(eventId));
      final body = jsonEncode({
        'id': userId,
        // Le backend peut aussi accepter juste l'id selon l'implémentation
      });

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

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

      final updatedEvent = EventModel.fromJson(jsonResponse);
      _log('Participation réussie');
      return updatedEvent;
    } catch (e) {
      _log('Erreur lors de la participation: $e');
      rethrow;
    }
  }

  /// Réagit à un événement (utilise l'endpoint favorite du backend).
  ///
  /// [eventId] L'identifiant de l'événement
  /// [userId] L'identifiant de l'utilisateur
  ///
  /// Throws [ServerException] en cas d'erreur
  Future<void> reactToEvent(String eventId, String userId) async {
    _log('Réaction de l\'utilisateur $userId à l\'événement $eventId');

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

    try {
      // Utiliser l'endpoint favorite du backend comme réaction
      final uri = Uri.parse(Urls.reactToEventWithId(eventId, userId));

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

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

      _log('Réaction enregistrée avec succès');
    } catch (e) {
      _log('Erreur lors de la réaction: $e');
      rethrow;
    }
  }

  /// Ferme un événement.
  ///
  /// [eventId] L'identifiant de l'événement à fermer
  ///
  /// Throws [ServerException] en cas d'erreur
  ///
  /// **Exemple:**
  /// ```dart
  /// await dataSource.closeEvent('event123');
  /// ```
  Future<void> closeEvent(String eventId) async {
    _log('Fermeture de l\'événement $eventId');

    if (eventId.isEmpty) {
      throw ValidationException('L\'ID de l\'événement ne peut pas être vide');
    }

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

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

      _log('Événement $eventId fermé avec succès');
    } catch (e) {
      _log('Erreur lors de la fermeture de l\'événement $eventId: $e');
      rethrow;
    }
  }

  /// Rouvre un événement.
  ///
  /// [eventId] L'identifiant de l'événement à rouvrir
  ///
  /// Throws [ServerException] en cas d'erreur
  ///
  /// **Exemple:**
  /// ```dart
  /// await dataSource.reopenEvent('event123');
  /// ```
  Future<void> reopenEvent(String eventId) async {
    _log('Réouverture de l\'événement $eventId');

    if (eventId.isEmpty) {
      throw ValidationException('L\'ID de l\'événement ne peut pas être vide');
    }

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

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

      _log('Événement $eventId rouvert avec succès');
    } catch (e) {
      _log('Erreur lors de la réouverture de l\'événement $eventId: $e');
      rethrow;
    }
  }

  /// Recherche des événements par mot-clé.
  ///
  /// [keyword] Le mot-clé à rechercher dans le titre et la description
  ///
  /// Returns une liste de [EventModel] correspondant à la recherche
  ///
  /// Throws [ServerException] en cas d'erreur
  Future<List<EventModel>> searchEvents(String keyword) async {
    _log('Recherche d\'événements avec le mot-clé: $keyword');

    if (keyword.trim().isEmpty) {
      return [];
    }

    try {
      final uri = Uri.parse('${Urls.searchEvents}?keyword=${Uri.encodeComponent(keyword)}');
      final response = await _performRequest('GET', uri);

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

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

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

      _log('${events.length} événements trouvés pour "$keyword"');
      return events;
    } catch (e) {
      _log('Erreur lors de la recherche: $e');
      rethrow;
    }
  }
}
