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';

/// Service WebSocket pour les notifications en temps réel.
///
/// Ce service gère les connexions WebSocket pour recevoir :
/// - Demandes d'amitié (envoi, réception, acceptation, rejet)
/// - Notifications système (événements, rappels)
/// - Alertes de messages
///
/// **Architecture :**
/// - Connexion WebSocket persistante à `/notifications/ws/{userId}`
/// - Streams séparés par type de notification
/// - Reconnexion automatique en cas de déconnexion
/// - Support multi-sessions (l'utilisateur peut être connecté sur plusieurs appareils)
class RealtimeNotificationService extends ChangeNotifier {
  RealtimeNotificationService(this.userId);

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

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

  bool _isDisposed = false;
  Timer? _reconnectTimer;

  // Streams pour différents types d'événements
  final _friendRequestController = StreamController<FriendRequestNotification>.broadcast();
  final _systemNotificationController = StreamController<SystemNotification>.broadcast();
  final _messageAlertController = StreamController<MessageAlert>.broadcast();
  final _presenceController = StreamController<PresenceUpdate>.broadcast();

  Stream<FriendRequestNotification> get friendRequestStream => _friendRequestController.stream;
  Stream<SystemNotification> get systemNotificationStream => _systemNotificationController.stream;
  Stream<MessageAlert> get messageAlertStream => _messageAlertController.stream;
  Stream<PresenceUpdate> get presenceStream => _presenceController.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/notifications/ws/$userId';
  }

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

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

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

      _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 au service de notifications', tag: 'RealtimeNotificationService');
    } catch (e, stackTrace) {
      AppLogger.e('Erreur de connexion', error: e, stackTrace: stackTrace, tag: 'RealtimeNotificationService');
      _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: 'RealtimeNotificationService');

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

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

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

    AppLogger.i('Déconnecté du service de notifications', tag: 'RealtimeNotificationService');
  }

  /// Envoie un ping pour maintenir la connexion active (keep-alive).
  void sendPing() {
    if (!_isConnected) {
      AppLogger.w('Impossible d\'envoyer un ping: non connecté', tag: 'RealtimeNotificationService');
      return;
    }

    final payload = {
      'type': 'ping',
    };

    _channel?.sink.add(json.encode(payload));
    AppLogger.d('Ping envoyé', tag: 'RealtimeNotificationService');
  }

  /// Envoie un heartbeat pour maintenir le statut online.
  void sendHeartbeat() {
    sendPing(); // Le ping est géré côté serveur pour mettre à jour la présence
  }

  /// Envoie un accusé de réception pour une notification.
  void sendAcknowledgement(String notificationId) {
    if (!_isConnected) return;

    final payload = {
      'type': 'ack',
      'data': {
        'notificationId': notificationId,
      },
    };

    _channel?.sink.add(json.encode(payload));
    AppLogger.d('ACK envoyé pour notification: $notificationId', tag: 'RealtimeNotificationService');
  }

  /// 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 (type == 'connected') {
        AppLogger.i('Confirmation de connexion reçue', tag: 'RealtimeNotificationService');
        return;
      }

      if (type == 'pong') {
        AppLogger.d('Pong reçu', tag: 'RealtimeNotificationService');
        return;
      }

      if (payload == null) {
        AppLogger.w('Payload null pour le type: $type', tag: 'RealtimeNotificationService');
        return;
      }

      switch (type) {
        case 'friend_request_received':
          final notification = FriendRequestNotification.fromJson(payload, type: 'received');
          _friendRequestController.add(notification);
          AppLogger.i('Demande d\'amitié reçue de: ${notification.senderName}', tag: 'RealtimeNotificationService');
          break;

        case 'friend_request_accepted':
          final notification = FriendRequestNotification.fromJson(payload, type: 'accepted');
          _friendRequestController.add(notification);
          AppLogger.i('Demande d\'amitié acceptée par: ${notification.senderName}', tag: 'RealtimeNotificationService');
          break;

        case 'friend_request_rejected':
          final notification = FriendRequestNotification.fromJson(payload, type: 'rejected');
          _friendRequestController.add(notification);
          AppLogger.i('Demande d\'amitié rejetée: ${notification.requestId}', tag: 'RealtimeNotificationService');
          break;

        case 'message_received':
          final alert = MessageAlert.fromJson(payload);
          _messageAlertController.add(alert);
          AppLogger.i('Nouveau message de: ${alert.senderName}', tag: 'RealtimeNotificationService');
          break;

        case 'system_notification':
          final notification = SystemNotification.fromJson(payload);
          _systemNotificationController.add(notification);
          AppLogger.i('Notification système: ${notification.title}', tag: 'RealtimeNotificationService');
          break;

        case 'presence':
          final update = PresenceUpdate.fromJson(payload);
          _presenceController.add(update);
          AppLogger.i('Mise à jour présence: ${update.userId} -> ${update.isOnline}', tag: 'RealtimeNotificationService');
          break;

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

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

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

    // Ne pas tenter de reconnexion si le backend ne supporte pas WebSocket
    if (error.toString().contains('not upgraded to websocket')) {
      AppLogger.w('WebSocket non supporté par le backend, reconnexion désactivée', tag: 'RealtimeNotificationService');
      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: 'RealtimeNotificationService');
          _reconnectTimer = null;
          connect();
        }
      });
    }
  }

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

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

    // Tenter une reconnexion
    if (!_isDisposed && _reconnectTimer == null) {
      _reconnectTimer = Timer(const Duration(seconds: 5), () {
        if (!_isDisposed && !_isConnected) {
          AppLogger.i('Reconnexion après déconnexion...', tag: 'RealtimeNotificationService');
          _reconnectTimer = null;
          connect();
        }
      });
    }
  }

  @override
  void dispose() {
    _isDisposed = true;
    _reconnectTimer?.cancel();
    _reconnectTimer = null;
    disconnect();
    _friendRequestController.close();
    _systemNotificationController.close();
    _messageAlertController.close();
    _presenceController.close();
    super.dispose();
  }
}

/// Notification de demande d'amitié.
class FriendRequestNotification {
  const FriendRequestNotification({
    required this.type,
    required this.requestId,
    required this.senderId,
    required this.senderName,
    required this.senderProfileImage,
  });

  /// Type: 'received', 'accepted', 'rejected'
  final String type;

  /// ID de la demande d'amitié (friendshipId)
  final String requestId;

  /// ID de l'utilisateur qui a envoyé/accepté/rejeté
  final String senderId;

  /// Nom complet de l'utilisateur
  final String senderName;

  /// URL de l'image de profil
  final String senderProfileImage;

  factory FriendRequestNotification.fromJson(Map<String, dynamic> json, {required String type}) {
    return FriendRequestNotification(
      type: type,
      requestId: json['requestId']?.toString() ?? json['friendshipId']?.toString() ?? '',
      senderId: json['senderId']?.toString() ?? json['accepterId']?.toString() ?? '',
      senderName: json['senderName']?.toString() ?? json['acceptedBy']?.toString() ?? 'Utilisateur',
      senderProfileImage: json['senderProfileImage']?.toString() ?? json['accepterProfileImage']?.toString() ?? '',
    );
  }

  String toDisplayMessage() {
    switch (type) {
      case 'received':
        return 'Nouvelle demande d\'amitié de $senderName';
      case 'accepted':
        return '$senderName a accepté votre demande';
      case 'rejected':
        return 'Demande d\'amitié refusée';
      default:
        return 'Notification de demande d\'amitié';
    }
  }
}

/// Notification système.
class SystemNotification {
  const SystemNotification({
    required this.notificationId,
    required this.title,
    required this.message,
    required this.type,
    required this.timestamp,
  });

  final String notificationId;
  final String title;
  final String message;
  final String type; // 'event', 'friend', 'reminder', 'other'
  final DateTime timestamp;

  factory SystemNotification.fromJson(Map<String, dynamic> json) {
    return SystemNotification(
      notificationId: json['notificationId']?.toString() ?? '',
      title: json['title']?.toString() ?? 'Notification',
      message: json['message']?.toString() ?? '',
      type: json['type']?.toString() ?? 'other',
      timestamp: json['timestamp'] != null
          ? DateTime.parse(json['timestamp'].toString())
          : DateTime.now(),
    );
  }
}

/// Alerte de message.
class MessageAlert {
  const MessageAlert({
    required this.messageId,
    required this.conversationId,
    required this.senderId,
    required this.senderName,
    required this.content,
    required this.timestamp,
  });

  final String messageId;
  final String conversationId;
  final String senderId;
  final String senderName;
  final String content;
  final DateTime timestamp;

  factory MessageAlert.fromJson(Map<String, dynamic> json) {
    return MessageAlert(
      messageId: json['messageId']?.toString() ?? '',
      conversationId: json['conversationId']?.toString() ?? '',
      senderId: json['senderId']?.toString() ?? '',
      senderName: json['senderName']?.toString() ?? 'Utilisateur',
      content: json['content']?.toString() ?? '',
      timestamp: json['timestamp'] != null
          ? DateTime.parse(json['timestamp'].toString())
          : DateTime.now(),
    );
  }
}

/// Mise à jour de présence utilisateur.
class PresenceUpdate {
  const PresenceUpdate({
    required this.userId,
    required this.isOnline,
    this.lastSeen,
    required this.timestamp,
  });

  final String userId;
  final bool isOnline;
  final DateTime? lastSeen;
  final DateTime timestamp;

  factory PresenceUpdate.fromJson(Map<String, dynamic> json) {
    return PresenceUpdate(
      userId: json['userId']?.toString() ?? '',
      isOnline: json['isOnline'] as bool? ?? false,
      lastSeen: json['lastSeen'] != null && json['lastSeen'].toString().isNotEmpty
          ? DateTime.tryParse(json['lastSeen'].toString())
          : null,
      timestamp: json['timestamp'] != null
          ? DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int)
          : DateTime.now(),
    );
  }
}
