import 'dart:async';
import 'dart:convert';
import 'package:get/get.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import '../config/app_config.dart';
import 'contacts_service.dart';
import 'firebase_service.dart';

enum WsStatus { disconnected, connecting, connected, waReady }

class WhatsAppService extends GetxService {
  // ── State ────────────────────────────────────────────────────────────────
  final status    = WsStatus.disconnected.obs;
  final qrData    = Rx<String?>(null);
  final isWaReady = false.obs;
  final activeChatId = Rx<String?>(null);

  // ── Internal ─────────────────────────────────────────────────────────────
  WebSocketChannel? _channel;
  StreamSubscription? _sub;
  int _reconnectCount = 0;
  Timer? _reconnectTimer;
  int _requestCounter = 0;

  // Pending requests: requestId → Completer
  final Map<String, Completer<Map<String, dynamic>>> _pending = {};

  // Event streams for push events (new messages, ack updates)
  final _eventController = StreamController<Map<String, dynamic>>.broadcast();
  Stream<Map<String, dynamic>> get events => _eventController.stream;

  // ── Lifecycle ────────────────────────────────────────────────────────────
  @override
  void onInit() {
    super.onInit();
    connect();
  }

  @override
  void onClose() {
    _sub?.cancel();
    _reconnectTimer?.cancel();
    _channel?.sink.close();
    _eventController.close();
    super.onClose();
  }

  // ── Connection ───────────────────────────────────────────────────────────
  void connect() {
    if (status.value == WsStatus.connecting ||
        status.value == WsStatus.connected  ||
        status.value == WsStatus.waReady) return;

    status.value = WsStatus.connecting;
    _reconnectTimer?.cancel();
    print('[WS] Attempting to connect to ${AppConfig.wsUrl}...');

    try {
      _channel = WebSocketChannel.connect(Uri.parse(AppConfig.wsUrl));
      _sub?.cancel();
      _sub = _channel!.stream.listen(
        _onData,
        onError: _onError,
        onDone:  _onDone,
      );
      status.value = WsStatus.connected;
      _reconnectCount = 0;
      print('[WS] Connected successfully.');
      
      // Request initial status check
      ping();
    } catch (e) {
      print('[WS] Connection exception: $e');
      _scheduleReconnect();
    }
  }

  void _onData(dynamic raw) {
    print('[WS RECV] $raw');
    Map<String, dynamic> data;
    try { 
      data = jsonDecode(raw as String); 
    } catch (_) { 
      return; 
    }

    final type      = data['type'] as String?;
    final requestId = data['requestId'] as String?;

    // Resolve pending request
    if (requestId != null && _pending.containsKey(requestId)) {
      _pending.remove(requestId)!.complete(data);
      return;
    }

    // Push events
    switch (type) {
      case 'new_message':
        // Trigger a local notification if the app is open (WebSocket connected)
        final chatId = data['chatId'];
        final msgData = data['data'];
        if (msgData != null && msgData['fromMe'] != true) {
          String body = msgData['body'] ?? '';
          if (body.isEmpty && msgData['hasMedia'] == true) {
             body = '📷 Media/Audio message';
          }
          try {
            final String cleanNumber = chatId?.split('@')[0] ?? '';
            final String senderName = Get.find<ContactsService>().getContactName(
              cleanNumber,
              cleanNumber.isNotEmpty ? '+$cleanNumber' : 'WhatsApp',
            );

            Get.find<FirebaseService>().showLocalNotificationFromData({
              'chatId': chatId,
              'name': senderName,
              'body': body,
            });
          } catch (e) {
            print('[LOCAL NOTIF ERROR] $e');
          }
        }
        break;
      case 'qr':

        qrData.value = data['qr'];
        isWaReady.value = false;
        if (status.value == WsStatus.waReady) {
          status.value = WsStatus.connected;
        }
        break;
      case 'authenticated':
        qrData.value = null;
        break;
      case 'ready':
        isWaReady.value = true;
        status.value = WsStatus.waReady;
        qrData.value = null;
        break;
      case 'disconnected':
        isWaReady.value = false;
        status.value = WsStatus.connected;
        break;
      case 'status':
        if (data['ready'] == true) {
          isWaReady.value = true;
          status.value = WsStatus.waReady;
          qrData.value = null;
        } else {
          isWaReady.value = false;
          if (status.value == WsStatus.waReady) {
            status.value = WsStatus.connected;
          }
        }
        break;
    }

    // Broadcast all events to listeners
    _eventController.add(data);
  }

  void _onError(Object err) {
    print('[WS ERROR] $err');
    _handleDisconnect();
  }

  void _onDone() {
    print('[WS DONE] Connection closed by server');
    _handleDisconnect();
  }

  void _handleDisconnect() {
    status.value = WsStatus.disconnected;
    isWaReady.value = false;
    // Reject all pending requests with error
    final pendingKeys = List.from(_pending.keys);
    for (final key in pendingKeys) {
      _pending.remove(key)?.completeError(Exception('Connection lost'));
    }
    _scheduleReconnect();
  }

  void _scheduleReconnect() {
    _reconnectTimer?.cancel();
    if (_reconnectCount >= AppConfig.maxReconnectAttempts) {
      print('[WS] Max reconnect attempts reached');
      return;
    }
    _reconnectCount++;
    _reconnectTimer = Timer(AppConfig.reconnectDelay, connect);
  }

  // ── Request/Response ─────────────────────────────────────────────────────
  Future<Map<String, dynamic>> _request(Map<String, dynamic> payload) {
    final id = (_requestCounter++).toString();
    payload['requestId'] = id;
    final completer = Completer<Map<String, dynamic>>();
    
    if (status.value == WsStatus.disconnected) {
      completer.completeError(Exception('WebSocket is disconnected'));
      return completer.future;
    }

    _pending[id] = completer;
    
    try {
      final jsonMsg = jsonEncode(payload);
      print('[WS SEND] $jsonMsg');
      _channel?.sink.add(jsonMsg);
    } catch (e) {
      _pending.remove(id);
      completer.completeError(e);
      return completer.future;
    }

    // Timeout after 60s
    return completer.future.timeout(
      const Duration(seconds: 60),
      onTimeout: () {
        _pending.remove(id);
        throw TimeoutException('Request timed out: ${payload['type']}');
      },
    );
  }

  // ── Public API ───────────────────────────────────────────────────────────
  Future<Map<String, dynamic>> getConversations({
    int limit = 50,
    int offset = 0,
  }) => _request({ 'type': 'get_conversations', 'limit': limit, 'offset': offset });

  Future<Map<String, dynamic>> getMessages(String chatId, {int limit = 50}) =>
      _request({ 'type': 'get_messages', 'chatId': chatId, 'limit': limit });

  Future<Map<String, dynamic>> sendMessage(String chatId, String text) =>
      _request({ 'type': 'send_message', 'chatId': chatId, 'text': text });

  Future<Map<String, dynamic>> markRead(String chatId) =>
      _request({ 'type': 'mark_read', 'chatId': chatId });

  Future<Map<String, dynamic>> searchConversations(String query) =>
      _request({ 'type': 'search_conversations', 'query': query });

  Future<Map<String, dynamic>> sendFcmToken(String token) =>
      _request({ 'type': 'register_fcm', 'token': token });

  Future<Map<String, dynamic>> getMedia(String messageId) =>
      _request({ 'type': 'get_media', 'messageId': messageId });

  Future<Map<String, dynamic>> sendMedia(String chatId, String base64, String mimetype, String filename, {String? caption}) =>
      _request({
        'type': 'send_media',
        'chatId': chatId,
        'base64': base64,
        'mimetype': mimetype,
        'filename': filename,
        'caption': caption ?? ''
      });

  // Cache downloaded media: messageId -> base64
  final RxMap<String, String> mediaCache = <String, String>{}.obs;

  Future<String?> downloadAndCacheMedia(String messageId) async {
    if (mediaCache.containsKey(messageId)) return mediaCache[messageId];

    try {
      final res = await getMedia(messageId);
      if (res['type'] == 'media' && res['data'] != null) {
        final String base64Data = res['data'];
        mediaCache[messageId] = base64Data;
        return base64Data;
      }
    } catch (e) {
      print('[MEDIA DOWNLOAD ERROR] Failed to download message media: $e');
    }
    return null;
  }

  Future<Map<String, dynamic>> ping() =>
      _request({ 'type': 'ping' });
}
