import 'dart:async';
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/chat_message_model.dart';
import '../models/conversation_model.dart';

/// Source de données distante pour le chat.
///
/// Cette classe gère toutes les opérations liées au chat
/// via l'API backend (REST) et WebSocket pour le temps réel.
class ChatRemoteDataSource {
  ChatRemoteDataSource(this.client);

  final http.Client client;

  static const Map<String, String> _defaultHeaders = {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  };

  Duration get _timeout => Duration(seconds: EnvConfig.networkTimeout);

  Future<http.Response> _performRequest(
    String method,
    Uri uri, {
    Map<String, String>? 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: 'ChatRemoteDataSource');

      return response;
    } on TimeoutException catch (e, stackTrace) {
      AppLogger.e('Timeout lors de la requête $method $uri', error: e, stackTrace: stackTrace, tag: 'ChatRemoteDataSource');
      throw ServerException('La requête a pris trop de temps. Le serveur ne répond pas.');
    } on SocketException catch (e, stackTrace) {
      AppLogger.e('Erreur de connexion réseau', error: e, stackTrace: stackTrace, tag: 'ChatRemoteDataSource');
      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: 'ChatRemoteDataSource');
      throw ServerException('Erreur client HTTP: ${e.message}');
    } catch (e, stackTrace) {
      AppLogger.e('Erreur inattendue', error: e, stackTrace: stackTrace, tag: 'ChatRemoteDataSource');
      rethrow;
    }
  }

  dynamic _parseJsonResponse(http.Response response, List<int> 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<String, dynamic>?)?['message'] as String? ??
          'Erreur serveur inconnue';

      AppLogger.e('Erreur (${response.statusCode}): $errorMessage', tag: 'ChatRemoteDataSource');

      switch (response.statusCode) {
        case 401:
          throw UnauthorizedException(errorMessage);
        case 404:
          throw ServerException('Ressource non trouvée: $errorMessage');
        default:
          throw ServerException('Erreur serveur (${response.statusCode}): $errorMessage');
      }
    }
  }

  /// Récupère toutes les conversations d'un utilisateur.
  Future<List<ConversationModel>> getConversations(String userId) async {
    AppLogger.d('Récupération des conversations pour: $userId', tag: 'ChatRemoteDataSource');

    try {
      final uri = Uri.parse(Urls.getUserConversations(userId));
      final response = await _performRequest('GET', uri);
      final jsonList = _parseJsonResponse(response, [200]) as List;
      return jsonList.map((json) => ConversationModel.fromJson(json as Map<String, dynamic>)).toList();
    } catch (e, stackTrace) {
      AppLogger.e('Erreur lors de la récupération des conversations', error: e, stackTrace: stackTrace, tag: 'ChatRemoteDataSource');
      rethrow;
    }
  }

  /// Récupère ou crée une conversation avec un utilisateur.
  ///
  /// Si la conversation n'existe pas (404) ou si le backend ne répond pas (timeout),
  /// elle sera créée automatiquement en envoyant un message initial.
  Future<ConversationModel> getOrCreateConversation(String userId, String participantId) async {
    AppLogger.d('Récupération/création conversation: $userId <-> $participantId', tag: 'ChatRemoteDataSource');

    final uri = Uri.parse(Urls.getConversationBetweenUsers(userId, participantId));
    
    try {
      // Essayer de récupérer la conversation existante avec un timeout plus court
      final response = await _performRequestWithTimeout('GET', uri, timeout: const Duration(seconds: 10));
      
      // Si la conversation existe, la retourner
      if (response.statusCode == 200 || response.statusCode == 201) {
        final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
        return ConversationModel.fromJson(jsonResponse);
      }
      
      // Si 404, la conversation n'existe pas, on doit la créer
      if (response.statusCode == 404) {
        AppLogger.i('Conversation non trouvée (404), création en cours...', tag: 'ChatRemoteDataSource');
        return await _createConversationBySendingMessage(userId, participantId, uri);
      }
      
      // Pour les autres codes d'erreur, utiliser la méthode standard de parsing
      final jsonResponse = _parseJsonResponse(response, [200, 201]) as Map<String, dynamic>;
      return ConversationModel.fromJson(jsonResponse);
    } on ServerException catch (e) {
      // Si c'est une ServerException avec 404 ou timeout, on essaie de créer la conversation
      if (e.message.contains('404') || 
          e.message.contains('non trouvée') || 
          e.message.contains('not found') ||
          e.message.contains('trop de temps') ||
          e.message.contains('ne répond pas')) {
        AppLogger.i('Conversation non trouvée ou timeout, création en cours...', tag: 'ChatRemoteDataSource');
        return await _createConversationBySendingMessage(userId, participantId, uri);
      }
      rethrow;
    } on TimeoutException catch (e, stackTrace) {
      // Si timeout, créer directement la conversation
      AppLogger.w('Timeout lors de la récupération de la conversation, création directe...', tag: 'ChatRemoteDataSource');
      return await _createConversationBySendingMessage(userId, participantId, uri);
    } catch (e, stackTrace) {
      AppLogger.e('Erreur lors de la récupération/création de conversation', error: e, stackTrace: stackTrace, tag: 'ChatRemoteDataSource');
      rethrow;
    }
  }

  /// Effectue une requête HTTP avec un timeout personnalisé.
  Future<http.Response> _performRequestWithTimeout(
    String method,
    Uri uri, {
    Map<String, String>? headers,
    Object? body,
    Duration? timeout,
  }) async {
    AppLogger.http(method, uri.toString());

    try {
      http.Response response;
      final requestHeaders = {..._defaultHeaders, ...?headers};
      final requestTimeout = timeout ?? _timeout;

      switch (method) {
        case 'GET':
          response = await client.get(uri, headers: requestHeaders).timeout(requestTimeout);
          break;
        case 'POST':
          response = await client.post(uri, headers: requestHeaders, body: body).timeout(requestTimeout);
          break;
        case 'PUT':
          response = await client.put(uri, headers: requestHeaders, body: body).timeout(requestTimeout);
          break;
        case 'DELETE':
          response = await client.delete(uri, headers: requestHeaders).timeout(requestTimeout);
          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: 'ChatRemoteDataSource');

      return response;
    } on TimeoutException catch (e, stackTrace) {
      AppLogger.e('Timeout lors de la requête $method $uri', error: e, stackTrace: stackTrace, tag: 'ChatRemoteDataSource');
      throw ServerException('La requête a pris trop de temps. Le serveur ne répond pas.');
    } on SocketException catch (e, stackTrace) {
      AppLogger.e('Erreur de connexion réseau', error: e, stackTrace: stackTrace, tag: 'ChatRemoteDataSource');
      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: 'ChatRemoteDataSource');
      throw ServerException('Erreur client HTTP: ${e.message}');
    } catch (e, stackTrace) {
      AppLogger.e('Erreur inattendue', error: e, stackTrace: stackTrace, tag: 'ChatRemoteDataSource');
      rethrow;
    }
  }

  /// Crée une conversation en envoyant un message initial, puis récupère la conversation créée.
  Future<ConversationModel> _createConversationBySendingMessage(
    String userId,
    String participantId,
    Uri conversationUri,
  ) async {
    try {
      // Créer la conversation en envoyant un message initial
      // Le backend créera automatiquement la conversation lors de l'envoi du premier message
      AppLogger.i('Création de la conversation en envoyant un message initial...', tag: 'ChatRemoteDataSource');
      
      await sendMessage(
        senderId: userId,
        recipientId: participantId,
        content: '👋', // Message initial pour créer la conversation
        messageType: 'text',
      );
      
      AppLogger.i('Message initial envoyé, attente de la création de la conversation...', tag: 'ChatRemoteDataSource');
      
      // Attendre un peu pour que le backend crée la conversation
      await Future.delayed(const Duration(milliseconds: 1000));
      
      // Récupérer la conversation nouvellement créée avec plusieurs tentatives et timeout court
      for (int attempt = 0; attempt < 3; attempt++) {
        try {
          AppLogger.d('Tentative ${attempt + 1}/3 pour récupérer la conversation créée...', tag: 'ChatRemoteDataSource');
          final retryResponse = await _performRequestWithTimeout(
            'GET',
            conversationUri,
            timeout: const Duration(seconds: 5),
          );
          
          if (retryResponse.statusCode == 200 || retryResponse.statusCode == 201) {
            final jsonResponse = json.decode(retryResponse.body) as Map<String, dynamic>;
            AppLogger.i('Conversation créée avec succès', tag: 'ChatRemoteDataSource');
            return ConversationModel.fromJson(jsonResponse);
          }
        } on TimeoutException {
          AppLogger.w('Timeout lors de la tentative ${attempt + 1}, nouvelle tentative...', tag: 'ChatRemoteDataSource');
        }
        
        // Attendre un peu plus avant la prochaine tentative
        if (attempt < 2) {
          await Future.delayed(Duration(milliseconds: 500 * (attempt + 1)));
        }
      }
      
      // Si après 3 tentatives on n'a toujours pas la conversation, lever une exception
      throw ServerException(
        'Impossible de créer ou récupérer la conversation après plusieurs tentatives. '
        'Le serveur ne répond pas correctement.',
      );
    } catch (e, stackTrace) {
      AppLogger.e(
        'Erreur lors de la création de la conversation via message initial',
        error: e,
        stackTrace: stackTrace,
        tag: 'ChatRemoteDataSource',
      );
      rethrow;
    }
  }

  /// Récupère tous les messages d'une conversation.
  Future<List<ChatMessageModel>> getMessages(String conversationId, {int page = 0, int size = 50}) async {
    AppLogger.d('Récupération des messages: $conversationId (page: $page)', tag: 'ChatRemoteDataSource');

    try {
      final uri = Uri.parse(Urls.getConversationMessages(conversationId, page: page, size: size));
      final response = await _performRequest('GET', uri);
      final jsonList = _parseJsonResponse(response, [200]) as List;
      return jsonList.map((json) => ChatMessageModel.fromJson(json as Map<String, dynamic>)).toList();
    } catch (e, stackTrace) {
      AppLogger.e('Erreur lors de la récupération des messages', error: e, stackTrace: stackTrace, tag: 'ChatRemoteDataSource');
      rethrow;
    }
  }

  /// Envoie un nouveau message dans une conversation.
  Future<ChatMessageModel> sendMessage({
    required String senderId,
    required String recipientId,
    required String content,
    String? messageType,
    String? mediaUrl,
  }) async {
    AppLogger.i('Envoi message de $senderId à $recipientId', tag: 'ChatRemoteDataSource');

    try {
      final uri = Uri.parse(Urls.sendMessage);
      final body = json.encode({
        'senderId': senderId,
        'recipientId': recipientId,
        'content': content,
        'messageType': messageType ?? 'text',
        if (mediaUrl != null) 'mediaUrl': mediaUrl,
      });
      final response = await _performRequest('POST', uri, body: body);
      final jsonResponse = _parseJsonResponse(response, [200, 201]) as Map<String, dynamic>;
      return ChatMessageModel.fromJson(jsonResponse);
    } catch (e, stackTrace) {
      AppLogger.e('Erreur lors de l\'envoi du message', error: e, stackTrace: stackTrace, tag: 'ChatRemoteDataSource');
      rethrow;
    }
  }

  /// Marque un message comme lu.
  Future<void> markMessageAsRead(String messageId) async {
    AppLogger.d('Marquer message comme lu: $messageId', tag: 'ChatRemoteDataSource');

    try {
      final uri = Uri.parse(Urls.markMessageAsRead(messageId));
      await _performRequest('PUT', uri);
    } catch (e, stackTrace) {
      AppLogger.e('Erreur lors du marquage comme lu', error: e, stackTrace: stackTrace, tag: 'ChatRemoteDataSource');
      rethrow;
    }
  }

  /// Marque tous les messages d'une conversation comme lus.
  Future<void> markConversationAsRead(String conversationId, String userId) async {
    AppLogger.d('Marquer conversation comme lue: $conversationId', tag: 'ChatRemoteDataSource');

    try {
      final uri = Uri.parse(Urls.markAllMessagesAsRead(conversationId, userId));
      await _performRequest('PUT', uri);
    } catch (e, stackTrace) {
      AppLogger.e('Erreur lors du marquage de la conversation', error: e, stackTrace: stackTrace, tag: 'ChatRemoteDataSource');
      rethrow;
    }
  }

  /// Supprime un message.
  Future<void> deleteMessage(String messageId) async {
    AppLogger.i('Suppression message: $messageId', tag: 'ChatRemoteDataSource');

    try {
      final uri = Uri.parse(Urls.deleteMessage(messageId));
      await _performRequest('DELETE', uri);
    } catch (e, stackTrace) {
      AppLogger.e('Erreur lors de la suppression', error: e, stackTrace: stackTrace, tag: 'ChatRemoteDataSource');
      rethrow;
    }
  }

  /// Supprime une conversation.
  Future<void> deleteConversation(String conversationId) async {
    AppLogger.i('Suppression conversation: $conversationId', tag: 'ChatRemoteDataSource');

    try {
      final uri = Uri.parse(Urls.deleteConversation(conversationId));
      await _performRequest('DELETE', uri);
    } catch (e, stackTrace) {
      AppLogger.e('Erreur lors de la suppression de la conversation', error: e, stackTrace: stackTrace, tag: 'ChatRemoteDataSource');
      rethrow;
    }
  }

  /// Récupère le nombre de messages non lus pour un utilisateur.
  Future<int> getUnreadMessagesCount(String userId) async {
    AppLogger.d('Récupération du nombre de messages non lus pour: $userId', tag: 'ChatRemoteDataSource');

    try {
      final uri = Uri.parse(Urls.getUnreadMessagesCount(userId));
      final response = await _performRequest('GET', uri);
      final jsonResponse = _parseJsonResponse(response, [200]) as Map<String, dynamic>;
      return jsonResponse['unreadCount'] as int? ?? 0;
    } catch (e, stackTrace) {
      AppLogger.e('Erreur lors de la récupération du nombre de messages non lus', error: e, stackTrace: stackTrace, tag: 'ChatRemoteDataSource');
      rethrow;
    }
  }

  /// Envoie un indicateur de frappe (typing indicator) via WebSocket.
  /// Cette méthode est maintenant gérée par le WebSocket, pas par REST.
  Future<void> sendTypingIndicator(String recipientId, String userId, bool isTyping) async {
    AppLogger.d('Indicateur de frappe envoyé via WebSocket pour: $recipientId ($isTyping)', tag: 'ChatRemoteDataSource');
    // Cette fonctionnalité sera gérée par ChatWebSocketService
  }
}
