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

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

import '../constants/app_constants.dart';
import '../storage/secure_storage.dart';

/// WebSocket Service pakai STOMP (stomp_dart_client).
///
/// Spring Boot backend pakai @EnableWebSocketMessageBroker dengan SockJS.
/// Flutter connect pakai STOMP agar bisa subscribe ke topic spesifik per user.
///
/// Subscriptions yang dipakai:
///   Guardian  → /topic/location/{userId}    live GPS update
///   Guardian  → /queue/sos/{guardianId}     SOS alert real-time
///   User      → /queue/notif/{userId}       notifikasi dari Guardian
class WebSocketService {
  final SecureStorage _storage;

  StompClient? _client;
  bool _connected = false;

  // Subscription callbacks
  void Function(double lat, double lng)? _onLocation;
  void Function(Map<String, dynamic> sosData)? _onSos;
  void Function(Map<String, dynamic> notifData)? _onNotif;

  // Subscription frames (untuk unsubscribe)
  StompUnsubscribe? _locationUnsub;
  StompUnsubscribe? _sosUnsub;
  StompUnsubscribe? _notifUnsub;

  WebSocketService(this._storage);

  bool get isConnected => _connected;

  /// Connect ke WebSocket server.
  /// Dipanggil setelah login berhasil (dari screens.dart _startPostLoginServices).
  Future<void> connect(String serverUrl) async {
    await disconnect();

    final token = await _storage.getAccessToken();
    final wsUrl = AppConstants.buildWsUrl(serverUrl);

    final completer = Completer<void>();

    _client = StompClient(
      config: StompConfig(
        url: wsUrl,
        onConnect: (frame) {
          _connected = true;
          debugPrint('[WS] STOMP connected to $wsUrl');
          if (!completer.isCompleted) completer.complete();
        },
        onDisconnect: (frame) {
          _connected = false;
          debugPrint('[WS] STOMP disconnected');
        },
        onStompError: (frame) {
          debugPrint('[WS] STOMP error: ${frame.body}');
          if (!completer.isCompleted) {
            completer.completeError(frame.body ?? 'STOMP error');
          }
        },
        onWebSocketError: (dynamic error) {
          debugPrint('[WS] WebSocket error: $error');
          _connected = false;
          if (!completer.isCompleted) completer.completeError(error);
        },
        onUnhandledMessage: (frame) {
          debugPrint('[WS] Unhandled: ${frame.body}');
        },
        // Inject JWT token di header STOMP CONNECT
        stompConnectHeaders:
            token != null ? {'Authorization': 'Bearer $token'} : {},
        webSocketConnectHeaders:
            token != null ? {'Authorization': 'Bearer $token'} : {},
        reconnectDelay: const Duration(seconds: 5),
      ),
    );

    _client!.activate();

    // Tunggu connected atau timeout
    try {
      await completer.future.timeout(const Duration(seconds: 5));
    } catch (e) {
      debugPrint('[WS] Connect timeout/error: $e');
      // Don't throw — let dashboard work without WS
    }
  }

  /// Subscribe ke live GPS updates dari User.
  /// Guardian panggil ini setelah connect.
  /// [userId] = ID dari ROLE_USER yang dipair.
  void subscribeLocation(String userId,
      void Function(double lat, double lng) callback) {
    _onLocation = callback;
    if (_client == null || !_connected) {
      debugPrint('[WS] subscribeLocation skipped — not connected');
      return;
    }
    _locationUnsub?.call(); // unsubscribe sebelumnya jika ada
    _locationUnsub = _client!.subscribe(
      destination: '/topic/location/$userId',
      callback: (frame) {
        try {
          final data =
              jsonDecode(frame.body ?? '{}') as Map<String, dynamic>;
          final lat = (data['lat'] as num?)?.toDouble();
          final lng = (data['lng'] as num?)?.toDouble();
          if (lat != null && lng != null) {
            _onLocation?.call(lat, lng);
          }
        } catch (e) {
          debugPrint('[WS] Location parse error: $e');
        }
      },
    );
    debugPrint('[WS] Subscribed to /topic/location/$userId');
  }

  /// Subscribe ke SOS alert untuk Guardian.
  /// [guardianId] = ID dari ROLE_GUARDIAN yang login.
  void subscribeSos(void Function(Map<String, dynamic> sosData) callback) {
    _onSos = callback;
    if (_client == null || !_connected) return;

    _storage.getUserId().then((guardianId) {
      if (guardianId == null) return;
      _sosUnsub?.call();
      _sosUnsub = _client!.subscribe(
        destination: '/queue/sos/$guardianId',
        callback: (frame) {
          try {
            final data =
                jsonDecode(frame.body ?? '{}') as Map<String, dynamic>;
            _onSos?.call(data);
          } catch (e) {
            debugPrint('[WS] SOS parse error: $e');
          }
        },
      );
      debugPrint('[WS] Subscribed to /queue/sos/$guardianId');
    });
  }

  /// Subscribe ke notifikasi Guardian → User.
  /// [userId] = ID dari ROLE_USER yang login.
  void subscribeNotification(
      void Function(Map<String, dynamic> notifData) callback) {
    _onNotif = callback;
    if (_client == null || !_connected) return;

    _storage.getUserId().then((userId) {
      if (userId == null) return;
      _notifUnsub?.call();
      _notifUnsub = _client!.subscribe(
        destination: '/queue/notif/$userId',
        callback: (frame) {
          try {
            final data =
                jsonDecode(frame.body ?? '{}') as Map<String, dynamic>;
            _onNotif?.call(data);
          } catch (e) {
            debugPrint('[WS] Notif parse error: $e');
          }
        },
      );
      debugPrint('[WS] Subscribed to /queue/notif/$userId');
    });
  }

  /// Disconnect dan cleanup semua subscriptions.
  Future<void> disconnect() async {
    _locationUnsub?.call();
    _sosUnsub?.call();
    _notifUnsub?.call();
    _locationUnsub = null;
    _sosUnsub = null;
    _notifUnsub = null;
    _client?.deactivate();
    _client = null;
    _connected = false;
  }

  // Legacy compat — lama pakai onMessage raw
  void send(Object message) {
    debugPrint('[WS] send() not used in STOMP mode. Use subscribe callbacks.');
  }
}
