import 'dart:async';
import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

import '../../core/constants/env_config.dart';
import '../../core/utils/app_logger.dart';
import '../../domain/entities/chat_message.dart';
import '../models/chat_message_model.dart';

/// Service WebSocket pour la communication temps réel du chat.
///
/// Ce service gère :
/// - La connexion WebSocket au serveur
/// - La réception de nouveaux messages en temps réel
/// - Les indicateurs de frappe (typing indicators)
/// - Les confirmations de lecture (read receipts)
class ChatWebSocketService extends ChangeNotifier {
  ChatWebSocketService(this.userId);

  final String userId;
  WebSocketChannel? _channel;
  StreamSubscription? _subscription;

  bool _isConnected = false;
  bool get isConnected => _isConnected;

  bool _isDisposed = false;
  Timer? _reconnectTimer;

  // Streams pour les événements temps réel
  final _messageController = StreamController<ChatMessage>.broadcast();
  final _typingController = StreamController<TypingIndicator>.broadcast();
  final _readReceiptController = StreamController<ReadReceipt>.broadcast();
  final _deliveryController = StreamController<DeliveryConfirmation>.broadcast();

  Stream<ChatMessage> get messageStream => _messageController.stream;
  Stream<TypingIndicator> get typingStream => _typingController.stream;
  Stream<ReadReceipt> get readReceiptStream => _readReceiptController.stream;
  Stream<DeliveryConfirmation> get deliveryStream => _deliveryController.stream;

  /// Récupère l'URL WebSocket à partir de l'URL HTTP de base.
  String get _wsUrl {
    final baseUrl = EnvConfig.apiBaseUrl;
    // Remplacer http:// par ws:// ou https:// par wss://
    final wsUrl = baseUrl.replaceFirst('http://', 'ws://').replaceFirst('https://', 'wss://');
    return '$wsUrl/chat/ws/$userId';
  }

  /// Se connecte au serveur WebSocket.
  Future<void> connect() async {
    if (_isDisposed) {
      AppLogger.w('Tentative de connexion après dispose, ignorée', tag: 'ChatWebSocketService');
      return;
    }

    if (_isConnected) {
      AppLogger.w('Déjà connecté', tag: 'ChatWebSocketService');
      return;
    }

    try {
      AppLogger.i('Connexion à: $_wsUrl', tag: 'ChatWebSocketService');

      _channel = WebSocketChannel.connect(Uri.parse(_wsUrl));

      // Écouter les messages entrants
      _subscription = _channel!.stream.listen(
        _handleMessage,
        onError: _handleError,
        onDone: _handleDisconnection,
        cancelOnError: false,
      );

      _isConnected = true;
      if (!_isDisposed) {
        notifyListeners();
      }

      AppLogger.i('Connecté avec succès', tag: 'ChatWebSocketService');
    } catch (e, stackTrace) {
      AppLogger.e('Erreur de connexion', error: e, stackTrace: stackTrace, tag: 'ChatWebSocketService');
      _isConnected = false;
      if (!_isDisposed) {
        notifyListeners();
      }
    }
  }

  /// Déconnecte du serveur WebSocket.
  Future<void> disconnect() async {
    _reconnectTimer?.cancel();
    _reconnectTimer = null;

    if (!_isConnected) return;

    AppLogger.i('Déconnexion...', tag: 'ChatWebSocketService');

    await _subscription?.cancel();
    _subscription = null;

    try {
      await _channel?.sink.close();
    } catch (e) {
      AppLogger.w('Erreur lors de la fermeture du canal: $e', tag: 'ChatWebSocketService');
    }
    _channel = null;

    _isConnected = false;
    if (!_isDisposed) {
      notifyListeners();
    }

    AppLogger.i('Déconnecté', tag: 'ChatWebSocketService');
  }

  /// Envoie un message via WebSocket.
  void sendMessage(ChatMessage message) {
    if (!_isConnected) {
      AppLogger.w('Erreur: Non connecté', tag: 'ChatWebSocketService');
      return;
    }

    final model = ChatMessageModel.fromEntity(message);
    final payload = {
      'type': 'message',
      'data': model.toJson(),
    };

    _channel?.sink.add(json.encode(payload));

    AppLogger.d('Message envoyé: ${message.id}', tag: 'ChatWebSocketService');
  }

  /// Envoie un indicateur de frappe.
  void sendTypingIndicator(String conversationId, bool isTyping) {
    if (!_isConnected) return;

    final payload = {
      'type': 'typing',
      'data': {
        'conversationId': conversationId,
        'userId': userId,
        'isTyping': isTyping,
      },
    };

    _channel?.sink.add(json.encode(payload));

    AppLogger.d('Indicateur de frappe envoyé: $isTyping', tag: 'ChatWebSocketService');
  }

  /// Envoie une confirmation de lecture.
  void sendReadReceipt(String messageId) {
    if (!_isConnected) return;

    final payload = {
      'type': 'read',
      'data': {
        'messageId': messageId,
        'userId': userId,
      },
    };

    _channel?.sink.add(json.encode(payload));

    AppLogger.d('Confirmation de lecture envoyée: $messageId', tag: 'ChatWebSocketService');
  }

  /// Gère les messages entrants du WebSocket.
  void _handleMessage(dynamic data) {
    try {
      final jsonData = json.decode(data as String) as Map<String, dynamic>;
      final type = jsonData['type'] as String?;
      final payload = jsonData['data'] as Map<String, dynamic>?;

      if (payload == null) return;

      switch (type) {
        case 'message':
          final message = ChatMessageModel.fromJson(payload).toEntity();
          _messageController.add(message);
          AppLogger.d('Nouveau message reçu: ${message.id}', tag: 'ChatWebSocketService');
          break;

        case 'typing':
          final indicator = TypingIndicator(
            conversationId: payload['conversationId'] as String,
            userId: payload['userId'] as String,
            isTyping: payload['isTyping'] as bool,
          );
          _typingController.add(indicator);
          AppLogger.d('Indicateur de frappe: ${indicator.isTyping}', tag: 'ChatWebSocketService');
          break;

        case 'read':
          AppLogger.d('RECEIVED READ: $payload', tag: 'ChatWebSocketService');
          final receipt = ReadReceipt(
            messageId: payload['messageId'] as String,
            userId: payload['userId'] as String,
            timestamp: DateTime.parse(payload['timestamp'] as String),
          );
          _readReceiptController.add(receipt);
          AppLogger.d('Confirmation lecture ajoutée au stream: ${receipt.messageId}', tag: 'ChatWebSocketService');
          break;

        case 'delivered':
          AppLogger.d('RECEIVED DELIVERED: $payload', tag: 'ChatWebSocketService');
          final confirmation = DeliveryConfirmation(
            messageId: payload['messageId'] as String,
            isDelivered: payload['isDelivered'] as bool,
            timestamp: DateTime.fromMillisecondsSinceEpoch(
              payload['timestamp'] as int,
            ),
          );
          _deliveryController.add(confirmation);
          AppLogger.d('Confirmation délivrance ajoutée au stream: ${confirmation.messageId}', tag: 'ChatWebSocketService');
          break;

        default:
          AppLogger.w('Type de message inconnu: $type', tag: 'ChatWebSocketService');
      }
    } catch (e, stackTrace) {
      AppLogger.e('Erreur parsing message', error: e, stackTrace: stackTrace, tag: 'ChatWebSocketService');
    }
  }

  /// Gère les erreurs WebSocket.
  void _handleError(Object error) {
    AppLogger.w('Erreur WebSocket: $error', tag: 'ChatWebSocketService');

    _isConnected = false;
    if (!_isDisposed) {
      notifyListeners();
    }

    // Ne pas tenter de reconnexion si le backend ne supporte pas WebSocket
    // Le backend retourne "Connection was not upgraded to websocket"
    if (error.toString().contains('not upgraded to websocket')) {
      AppLogger.w('WebSocket non supporté par le backend, reconnexion désactivée', tag: 'ChatWebSocketService');
      return;
    }

    // Tentative de reconnexion après 5 secondes seulement si pas disposé
    if (!_isDisposed && _reconnectTimer == null) {
      _reconnectTimer = Timer(const Duration(seconds: 5), () {
        if (!_isDisposed && !_isConnected) {
          AppLogger.i('Tentative de reconnexion...', tag: 'ChatWebSocketService');
          _reconnectTimer = null;
          connect();
        }
      });
    }
  }

  /// Gère la déconnexion WebSocket.
  void _handleDisconnection() {
    AppLogger.i('Déconnexion détectée', tag: 'ChatWebSocketService');

    _isConnected = false;
    if (!_isDisposed) {
      notifyListeners();
    }
  }

  @override
  void dispose() {
    _isDisposed = true;
    _reconnectTimer?.cancel();
    _reconnectTimer = null;
    disconnect();
    _messageController.close();
    _typingController.close();
    _readReceiptController.close();
    _deliveryController.close();
    super.dispose();
  }
}

/// Indicateur de frappe.
class TypingIndicator {
  const TypingIndicator({
    required this.conversationId,
    required this.userId,
    required this.isTyping,
  });

  final String conversationId;
  final String userId;
  final bool isTyping;
}

/// Confirmation de lecture.
class ReadReceipt {
  const ReadReceipt({
    required this.messageId,
    required this.userId,
    required this.timestamp,
  });

  final String messageId;
  final String userId;
  final DateTime timestamp;
}

/// Confirmation de délivrance.
class DeliveryConfirmation {
  const DeliveryConfirmation({
    required this.messageId,
    required this.isDelivered,
    required this.timestamp,
  });

  final String messageId;
  final bool isDelivered;
  final DateTime timestamp;
}
