import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:votianlt_app/services/developer.dart' as developer;

import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/status.dart' as ws_status;
import 'dart:math';
import 'database_service.dart';
import 'chat_service.dart';
import 'notification_service.dart';
import 'location_service.dart';
import '../app_state.dart';
import '../models/chat_message.dart';
import '../models/job.dart';
import '../models/queued_message.dart';
import 'dart_mq.dart';

class WebSocketService {
  static final WebSocketService _instance = WebSocketService._internal();

  factory WebSocketService() => _instance;

  WebSocketService._internal();

  WebSocketChannel? _wsChannel;
  StreamSubscription? _wsSubscription;
  bool _isConnected = false;
  bool _isConnecting = false;

  // Authentication state
  bool _isAuthenticated = false;
  String? _authToken;

  // Keep last known values for UI initialization (Behavior-like)
  Map<String, dynamic>? _lastAuthResponse;

  // Completer to await a graceful WebSocket disconnect
  Completer<void>? _disconnectCompleter;

  // Unique persistent App ID for client identification
  String? _appId;

  // Automatic reconnection timer
  Timer? _reconnectTimer;

  // In-memory message buffer for messages sent while disconnected
  final List<_BufferedMessage> _messageBuffer = [];

  // Database service
  final DatabaseService _databaseService = DatabaseService();

  // Validator for optional jobId field on chat messages
  final RegExp _jobIdRegExp = RegExp(r'^[0-9a-fA-F]{24}$');

  /// Ensure a unique persistent App ID exists.
  /// Generates a UUID v4 if not already stored.
  Future<void> _ensureAppId() async {
    if (_appId != null) return;
    try {
      final existing = await _databaseService.loadKeyValue('appId');
      if (existing != null && existing.isNotEmpty) {
        _appId = existing;
        developer.log(
          'Loaded existing appId: $_appId',
          name: 'WebSocketService',
        );
        return;
      }
    } catch (_) {}
    // Generate a UUID v4 and persist
    _appId = _generateUuid();
    developer.log('Generated new appId: $_appId', name: 'WebSocketService');
    try {
      await _databaseService.saveKeyValue('appId', _appId!);
    } catch (_) {}
  }

  /// Get the unique persistent App ID (for external access if needed)
  String? get appId => _appId;

  /// Generate a UUID v4
  String _generateUuid() {
    final rand = Random();
    final buf = StringBuffer();

    for (int i = 0; i < 36; i++) {
      if (i == 8 || i == 13 || i == 18 || i == 23) {
        buf.write('-');
      } else if (i == 14) {
        buf.write('4');
      } else if (i == 19) {
        buf.write(['8', '9', 'a', 'b'][rand.nextInt(4)]);
      } else {
        buf.write(rand.nextInt(16).toRadixString(16));
      }
    }
    return buf.toString();
  }

  // ---------------------------------------------------------------------------
  // WebSocket Connection
  // ---------------------------------------------------------------------------

  /// Build the WebSocket URL
  /// Im Release-Modus: votianlt.de (Produktionsserver)
  /// Im Debug-Modus: localhost (Android Emulator: 10.0.2.2)
  String _buildWebSocketUrl() {
    // Release-Modus: Verbindung zum Produktionsserver
    if (kReleaseMode) {
      return 'wss://votianlt.de/ws/messaging';
    }

    return 'ws://192.168.180.10:8082/ws/messaging';
  }

  /// Handle a connected WebSocket (common setup for connect and reconnect)
  void _onWebSocketConnected() {
    developer.log('WebSocket connected', name: 'WebSocketService');

    // Update internal connection state
    _isConnected = true;
    _isConnecting = false;
    // Note: Don't publish connectionStatus=true yet - wait for successful auth

    // Re-run the same setup as initial connection (auto-login)
    _setupAfterConnect();
  }

  /// Setup auto-login after connection (initial or reconnect)
  Future<void> _setupAfterConnect() async {
    try {
      // Ensure we have an appId
      await _ensureAppId();

      // Auto-login with saved credentials if user was previously logged in
      final credentials = await _databaseService.loadCredentials();
      if (credentials != null) {
        developer.log(
          'Auto-login with saved credentials for ${credentials.email}',
          name: 'WebSocketService',
        );
        await login(credentials.email, credentials.password);
      }
    } catch (e, st) {
      developer.log(
        'Error in _setupAfterConnect: $e',
        name: 'WebSocketService',
      );
      developer.log('Stack: $st', name: 'WebSocketService');
    }
  }

  /// Handle WebSocket disconnection
  void _handleWebSocketDisconnect() {
    developer.log('WebSocket disconnected', name: 'WebSocketService');
    _isConnected = false;
    _isAuthenticated = false;
    Future.microtask(() {
      DartMQ().publish<bool>(MQTopics.connectionStatus, false);
    });

    // Clean up WebSocket resources
    _wsSubscription?.cancel();
    _wsSubscription = null;
    _wsChannel = null;

    try {
      _disconnectCompleter?.complete();
    } catch (_) {}
    _disconnectCompleter = null;

    // Start automatic reconnection attempts
    _startReconnectTimer();
  }

  void _startReconnectTimer() {
    _stopReconnectTimer();
    _reconnectTimer = Timer.periodic(const Duration(seconds: 15), (timer) {
      if (_isConnected || _isConnecting) {
        _stopReconnectTimer();
        return;
      }
      developer.log(
        'Attempting automatic reconnection...',
        name: 'WebSocketService',
      );
      connect();
    });
  }

  void _stopReconnectTimer() {
    _reconnectTimer?.cancel();
    _reconnectTimer = null;
  }

  /// Force a clean reconnect after the app resumes from standby.
  /// Keeps buffered outbound messages intact and relies on saved credentials
  /// for the subsequent auto-login inside [connect].
  Future<void> reconnectForAppResume() async {
    final credentials = await _databaseService.loadCredentials();
    if (credentials == null) {
      developer.log(
        'Skipping reconnect after resume - no saved credentials',
        name: 'WebSocketService',
      );
      return;
    }

    if (_isConnecting) {
      developer.log(
        'Skipping reconnect after resume - connection attempt already running',
        name: 'WebSocketService',
      );
      return;
    }

    developer.log(
      'Restarting WebSocket connection after app resume',
      name: 'WebSocketService',
    );

    _stopReconnectTimer();

    final existingSubscription = _wsSubscription;
    final existingChannel = _wsChannel;

    _wsSubscription = null;
    _wsChannel = null;
    _disconnectCompleter = null;
    _isConnected = false;
    _isConnecting = false;
    _isAuthenticated = false;
    _authToken = null;
    _lastAuthResponse = null;

    Future.microtask(() {
      DartMQ().publish<bool>(MQTopics.connectionStatus, false);
    });

    try {
      await existingSubscription?.cancel();
    } catch (e, st) {
      developer.log(
        'Error cancelling old WebSocket subscription on resume: $e',
        name: 'WebSocketService',
      );
      developer.log('Stack: $st', name: 'WebSocketService');
    }

    try {
      await existingChannel?.sink.close(ws_status.goingAway);
    } catch (e, st) {
      developer.log(
        'Error closing old WebSocket channel on resume: $e',
        name: 'WebSocketService',
      );
      developer.log('Stack: $st', name: 'WebSocketService');
    }

    await connect();
  }

  // ---------------------------------------------------------------------------
  // WebSocket Send / Receive
  // ---------------------------------------------------------------------------

  /// Send a message over WebSocket in wire format: {"topic": ..., "payload": ...}
  bool _sendWebSocket(String topic, String jsonPayload) {
    if (!_isConnected || _wsChannel == null) {
      developer.log(
        'Cannot send, not connected. Topic=$topic',
        name: 'WebSocketService',
      );
      return false;
    }
    try {
      final parsed = jsonDecode(jsonPayload);
      final wireMessage = jsonEncode({'topic': topic, 'payload': parsed});
      developer.log('>> SEND: $wireMessage', name: 'WebSocketService');
      _wsChannel!.sink.add(wireMessage);
      return true;
    } catch (e, st) {
      developer.log(
        'Error sending WebSocket message: $e',
        name: 'WebSocketService',
      );
      developer.log('Stack: $st', name: 'WebSocketService');
      return false;
    }
  }

  /// Handle incoming WebSocket message
  void _onWebSocketMessage(dynamic rawData) async {
    if (rawData is! String) {
      developer.log(
        'Received non-text WebSocket message, ignoring',
        name: 'WebSocketService',
      );
      return;
    }

    try {
      final wireMessage = jsonDecode(rawData) as Map<String, dynamic>;
      final topic = wireMessage['topic'] as String?;
      final payload = wireMessage['payload'];

      if (topic == null) {
        developer.log(
          'WebSocket message missing topic field',
          name: 'WebSocketService',
        );
        return;
      }

      developer.log('<< RECEIVED: $rawData', name: 'WebSocketService');

      await _handleMessage(topic, payload);
    } catch (e, st) {
      developer.log(
        'Error parsing WebSocket message: $e',
        name: 'WebSocketService',
      );
      developer.log('Stack: $st', name: 'WebSocketService');
    }
  }

  // ---------------------------------------------------------------------------
  // Message Handlers
  // ---------------------------------------------------------------------------

  Future<void> _handleMessage(String topic, dynamic data) async {
    developer.log(
      '_handleMessage called with topic: $topic',
      name: 'WebSocketService',
    );
    if (topic.startsWith('/client/')) {
      await _handleClientMessage(topic, data);
    } else {
      developer.log(
        'Topic does not start with /client/, ignoring',
        name: 'WebSocketService',
      );
    }
  }

  Future<void> _handleClientMessage(String topic, dynamic data) async {
    developer.log(
      'Handling client message: topic=$topic, dataType=${data.runtimeType}',
      name: 'WebSocketService',
    );

    if (topic.endsWith('/auth')) {
      await _handleAuthMessage(topic, data);
    } else if (topic.endsWith('/jobs')) {
      _handleJobsMessage(data);
    } else if (topic.endsWith('/job_deleted')) {
      _handleJobDeletedMessage(data);
    } else if (topic.endsWith('/job_created')) {
      _handleJobCreatedMessage(data);
    } else if (topic.endsWith('/message_ack')) {
      await _handleChatMessageAck(data);
    } else if (topic.endsWith('/message')) {
      await _handleChatMessage(topic, data);
    } else {
      _handleOtherClientMessage(topic, data);
    }
  }

  Future<void> _handleAuthMessage(
    String topic,
    Map<String, dynamic> data,
  ) async {
    _lastAuthResponse = data;
    DartMQ().publish<Map<String, dynamic>>(MQTopics.authResponse, data);

    if (data['success'] == true) {
      await _handleSuccessfulAuth();
    } else {
      _handleFailedAuth();
    }
  }

  Future<void> _handleSuccessfulAuth() async {
    _isAuthenticated = true;

    developer.log('Auth successful', name: 'WebSocketService');

    // Publish connection status to UI - fully connected and authenticated (async to avoid build-phase issues)
    Future.microtask(() {
      DartMQ().publish<bool>(MQTopics.connectionStatus, true);
    });

    // Flush any messages that were buffered while disconnected.
    // This also clears local jobs and notifies the server.
    await _flushMessageBuffer();

    // Start location tracking only if enabled in auth response
    final locationTrackingEnabled =
        _lastAuthResponse?['locationTrackingEnabled'] == true;
    developer.log(
      'Location tracking enabled: $locationTrackingEnabled',
      name: 'WebSocketService',
    );
    if (locationTrackingEnabled) {
      LocationService().startTracking();
      developer.log('Location tracking started', name: 'WebSocketService');
    } else {
      developer.log(
        'Location tracking disabled by server',
        name: 'WebSocketService',
      );
    }
  }

  void _handleFailedAuth() {
    _isAuthenticated = false;
    _authToken = null;
  }

  /// Übersetzung deaktiviert - Texte werden im Original angezeigt
  Future<Map<String, dynamic>> _translateJobData(
    Map<String, dynamic> jobData,
  ) async {
    // Keine Übersetzung - Daten werden so wie vom Server empfangen verwendet
    return jobData;
  }

  void _handleJobsMessage(List<dynamic> data) async {
    final jobs = data;

    // Log empfangene Jobs JSON
    developer.log(
      '<< JOBS RECEIVED: ${jsonEncode(data)}',
      name: 'WebSocketService',
    );

    if (jobs.isNotEmpty) {
      final currentJobCount = AppState().assignedJobs.length;
      if (currentJobCount > 0 && jobs.length > currentJobCount) {
        final newCount = jobs.length - currentJobCount;
        NotificationService().showJobNotification(
          title: 'Neue Jobs',
          body:
              newCount == 1
                  ? 'Sie haben einen neuen Job erhalten.'
                  : 'Sie haben $newCount neue Jobs erhalten.',
        );
      }

      // Parse and persist jobs to database immediately
      try {
        final List<Job> parsedJobs = [];
        for (final jobData in jobs) {
          try {
            Map<String, dynamic> actualJobData;
            if (jobData is Map<String, dynamic> && jobData.containsKey('job')) {
              actualJobData = Map<String, dynamic>.from(
                jobData['job'] as Map<String, dynamic>,
              );
              if (jobData.containsKey('tasks') && jobData['tasks'] is List) {
                actualJobData['tasks'] = jobData['tasks'];
              }
              if (jobData.containsKey('cargoItems') &&
                  jobData['cargoItems'] is List) {
                actualJobData['cargoItems'] = jobData['cargoItems'];
              }
            } else {
              actualJobData = jobData as Map<String, dynamic>;
            }

            // Übersetze Textfelder vor dem Speichern
            actualJobData = await _translateJobData(actualJobData);

            final job = Job.fromJson(actualJobData);
            parsedJobs.add(job);
          } catch (e, stackTrace) {
            developer.log('Error parsing job: $e', name: 'WebSocketService');
            developer.log('Stack trace: $stackTrace', name: 'WebSocketService');
          }
        }

        // Save all parsed jobs to database
        if (parsedJobs.isNotEmpty) {
          await _databaseService.saveJobs(parsedJobs);
          developer.log(
            'Saved ${parsedJobs.length} jobs to database',
            name: 'WebSocketService',
          );
        }
      } catch (e, stackTrace) {
        developer.log(
          'Error saving jobs to database: $e',
          name: 'WebSocketService',
        );
        developer.log('Stack trace: $stackTrace', name: 'WebSocketService');
      }

      DartMQ().publish<List<dynamic>>(MQTopics.jobsResponse, jobs);
    } else {
      // Clear all jobs from database when empty list received
      await _databaseService.clearAllJobsAndRelatedData();
      developer.log(
        'Cleared all jobs from database (empty list received)',
        name: 'WebSocketService',
      );

      // Still publish empty list to complete any waiting operations
      DartMQ().publish<List<dynamic>>(MQTopics.jobsResponse, []);
    }
  }

  void _handleJobDeletedMessage(Map<String, dynamic> data) async {
    final jobId = data['jobId']?.toString();
    final jobNumber = data['jobNumber']?.toString();

    if (jobId == null || jobId.isEmpty) {
      developer.log(
        'Received job_deleted message without jobId',
        name: 'WebSocketService',
      );
      return;
    }

    developer.log(
      '<< JOB DELETED: jobId=$jobId, jobNumber=$jobNumber',
      name: 'WebSocketService',
    );

    // Delete job from database immediately
    try {
      await _databaseService.deleteJob(jobId);
      developer.log(
        'Deleted job $jobId from database',
        name: 'WebSocketService',
      );
    } catch (e, stackTrace) {
      developer.log(
        'Error deleting job $jobId from database: $e',
        name: 'WebSocketService',
      );
      developer.log('Stack trace: $stackTrace', name: 'WebSocketService');
    }

    // Publish event via DartMQ for UI to handle
    DartMQ().publish<Map<String, dynamic>>(MQTopics.jobDeleted, data);
  }

  void _handleJobCreatedMessage(Map<String, dynamic> data) async {
    final jobId = data['job']?['id']?.toString() ?? data['id']?.toString();
    final jobNumber =
        data['job']?['jobNumber']?.toString() ?? data['jobNumber']?.toString();

    // Log empfangenes Job JSON
    developer.log(
      '<< JOB CREATED JSON: ${jsonEncode(data)}',
      name: 'WebSocketService',
    );
    developer.log(
      '<< JOB CREATED: jobId=$jobId, jobNumber=$jobNumber',
      name: 'WebSocketService',
    );

    // Parse and persist job to database immediately
    try {
      Map<String, dynamic> actualJobData;
      if (data.containsKey('job')) {
        actualJobData = Map<String, dynamic>.from(
          data['job'] as Map<String, dynamic>,
        );
        if (data.containsKey('tasks') && data['tasks'] is List) {
          actualJobData['tasks'] = data['tasks'];
        }
        if (data.containsKey('cargoItems') && data['cargoItems'] is List) {
          actualJobData['cargoItems'] = data['cargoItems'];
        }
      } else {
        actualJobData = data;
      }

      // Übersetze Textfelder vor dem Speichern
      actualJobData = await _translateJobData(actualJobData);

      final job = Job.fromJson(actualJobData);
      await _databaseService.saveOrUpdateJob(job);
      developer.log(
        'Saved new job ${job.id} to database',
        name: 'WebSocketService',
      );
    } catch (e, stackTrace) {
      developer.log(
        'Error saving new job to database: $e',
        name: 'WebSocketService',
      );
      developer.log('Stack trace: $stackTrace', name: 'WebSocketService');
    }

    // Show notification with sound for new job
    NotificationService().showJobNotification(
      title: 'Neuer Job',
      body:
          jobNumber != null && jobNumber.isNotEmpty
              ? 'Job $jobNumber wurde Ihnen zugewiesen.'
              : 'Sie haben einen neuen Job erhalten.',
    );

    // Publish event via DartMQ for UI to handle
    DartMQ().publish<Map<String, dynamic>>(MQTopics.jobCreated, data);
  }

  Future<void> _handleChatMessage(
    String topic,
    Map<String, dynamic> data,
  ) async {
    const requiredFields = [
      'messageId',
      'content',
      'origin',
      'messageType',
      'createdAt',
    ];
    final missing = <String>[];
    for (final field in requiredFields) {
      final value = data[field];
      if (value == null || (value is String && value.trim().isEmpty)) {
        missing.add(field);
      }
    }

    if (missing.isNotEmpty) {
      return;
    }

    try {
      final message = ChatMessage.fromJson(data);
      await ChatService().saveIncomingMessage(message);

      final conversationKey = ChatService().conversationKeyForMessage(message);

      String notificationTitle;
      if (message.messageType == ChatMessageType.jobRelated) {
        final jobNumber = message.jobNumber ?? '';
        notificationTitle =
            jobNumber.isNotEmpty
                ? 'Nachricht zu Job $jobNumber'
                : 'Neue Job-Nachricht';
      } else {
        notificationTitle = 'Neue Nachricht';
      }

      String notificationBody;
      if (message.contentType == ChatContentType.image) {
        notificationBody = '[Bild]';
      } else {
        final content = message.content;
        notificationBody =
            content.length > 100 ? '${content.substring(0, 100)}...' : content;
      }

      NotificationService().showChatNotification(
        title: notificationTitle,
        body: notificationBody,
        conversationKey: conversationKey,
      );
    } catch (e, st) {
      developer.log('Error parsing chat message: $e', name: 'WebSocketService');
      developer.log('Stack: $st', name: 'WebSocketService');
    }
  }

  Future<void> _handleChatMessageAck(Map<String, dynamic> data) async {
    final clientMessageId = data['clientMessageId']?.toString().trim() ?? '';
    if (clientMessageId.isEmpty) {
      developer.log(
        'Received message ACK without clientMessageId',
        name: 'WebSocketService',
      );
      return;
    }

    await _databaseService.removeQueuedMessage(clientMessageId);
    await ChatService().markOutgoingMessageSynced(clientMessageId);
  }

  void _handleOtherClientMessage(String topic, Map<String, dynamic> data) {
    final type = data['type'];
    if (topic.contains('/tasks/') || type == 'task') {
      DartMQ().publish<Map<String, dynamic>>(MQTopics.taskEvents, data);
    } else {
      developer.log(
        'Unhandled client message type: $type on topic: $topic',
        name: 'WebSocketService',
      );
    }
  }

  // ---------------------------------------------------------------------------
  // Public API
  // ---------------------------------------------------------------------------

  bool get isConnected => _isConnected;

  bool get isConnecting => _isConnecting;

  bool get isAuthenticated => _isAuthenticated;

  String? get authToken => _authToken;

  /// Connect to the WebSocket server
  Future<void> connect() async {
    // Prevent overlapping connection attempts
    if (_isConnected) {
      developer.log(
        'Already connected to WebSocket server',
        name: 'WebSocketService',
      );
      return;
    }
    if (_isConnecting) {
      developer.log(
        'Connection attempt already in progress - skipping',
        name: 'WebSocketService',
      );
      return;
    }

    final wsUrl = _buildWebSocketUrl();

    developer.log(
      'Connecting to WebSocket server: $wsUrl',
      name: 'WebSocketService',
    );

    _isConnecting = true;

    // Publish "not connected" status to UI while connecting (async to avoid build-phase issues)
    Future.microtask(() {
      DartMQ().publish<bool>(MQTopics.connectionStatus, false);
    });

    try {
      // Ensure stable appId
      await _ensureAppId();

      final channel = WebSocketChannel.connect(Uri.parse(wsUrl));

      // Wait for the connection to be established
      await channel.ready;

      _wsChannel = channel;

      // Listen for incoming messages
      _wsSubscription = channel.stream.listen(
        _onWebSocketMessage,
        onError: (error) {
          developer.log('WebSocket error: $error', name: 'WebSocketService');
          _handleWebSocketDisconnect();
        },
        onDone: () {
          developer.log(
            'WebSocket connection closed',
            name: 'WebSocketService',
          );
          _handleWebSocketDisconnect();
        },
        cancelOnError: false,
      );

      // Stop any reconnection attempts since we're now connected
      _stopReconnectTimer();

      developer.log(
        'Connected to WebSocket server at $wsUrl',
        name: 'WebSocketService',
      );

      // Run common post-connect setup (sets _isConnected, starts timers, auto-login)
      _onWebSocketConnected();
    } catch (e) {
      _isConnecting = false;
      developer.log(
        'Error connecting to WebSocket server: $e',
        name: 'WebSocketService',
      );

      // Start reconnection attempts on connection failure
      _startReconnectTimer();
    }
  }

  /// Send a message to the server. Buffers if not connected/authenticated
  /// or if sending fails due to an exception.
  void sendMessage(String topic, String message) {
    if (_isConnected && _isAuthenticated && _wsChannel != null) {
      final success = _sendWebSocket(topic, message);
      if (!success) {
        // Message could not be sent due to exception - buffer for retry
        developer.log(
          '>> BUFFERED (send failed): topic=$topic',
          name: 'WebSocketService',
        );
        _messageBuffer.add(_BufferedMessage(topic, message));
      }
    } else {
      developer.log(
        '>> BUFFERED (not ready): topic=$topic',
        name: 'WebSocketService',
      );
      _messageBuffer.add(_BufferedMessage(topic, message));
    }
  }

  /// Flush all buffered messages after successful authentication.
  /// Messages that fail to send are re-buffered for the next flush cycle.
  /// Clears all local jobs and related data, then notifies the server.
  Future<void> _flushMessageBuffer() async {
    final initialBufferSize = _messageBuffer.length;
    final sentQueuedChatCount = await _flushQueuedChatMessages();

    if (initialBufferSize > 0) {
      developer.log(
        'Flushing ${_messageBuffer.length} buffered messages',
        name: 'WebSocketService',
      );
      final messages = List<_BufferedMessage>.from(_messageBuffer);
      _messageBuffer.clear();
      final failedMessages = <_BufferedMessage>[];
      for (final msg in messages) {
        final success = _sendWebSocket(msg.topic, msg.jsonPayload);
        if (!success) {
          // Re-buffer failed messages for retry on next connection
          failedMessages.add(msg);
        }
      }
      // Add failed messages back to buffer for retry
      if (failedMessages.isNotEmpty) {
        developer.log(
          '${failedMessages.length} messages failed to send, re-buffering for retry',
          name: 'WebSocketService',
        );
        _messageBuffer.addAll(failedMessages);
      }
    }

    // Clear all local jobs and related data before notifying server.
    // The server will send current jobs automatically.
    developer.log(
      'Clearing local jobs and related data before buffer_flushed notification',
      name: 'WebSocketService',
    );
    await _databaseService.clearAllJobsAndRelatedData();

    // Notify server that buffer flush is complete
    final sentCount =
        (initialBufferSize - _messageBuffer.length) + sentQueuedChatCount;
    final bufferFlushedPayload = jsonEncode({
      'timestamp': DateTime.now().toIso8601String(),
      'messageCount': sentCount,
    });
    _sendWebSocket('/server/buffer_flushed', bufferFlushedPayload);
  }

  Future<int> _flushQueuedChatMessages() async {
    final queuedMessages = await _databaseService.getQueuedMessages();
    if (queuedMessages.isEmpty) {
      return 0;
    }

    developer.log(
      'Flushing ${queuedMessages.length} queued chat messages',
      name: 'WebSocketService',
    );

    var sentCount = 0;
    for (final message in queuedMessages) {
      final success = await _trySendQueuedChatMessage(
        message,
        incrementRetryOnFailure: true,
      );
      if (success) {
        sentCount++;
      }
    }

    return sentCount;
  }

  Future<bool> _trySendQueuedChatMessage(
    QueuedMessage message, {
    bool incrementRetryOnFailure = false,
  }) async {
    if (!_isConnected || !_isAuthenticated || _wsChannel == null) {
      return false;
    }

    final success = _sendWebSocket(message.topic, jsonEncode(message.payload));
    if (!success && incrementRetryOnFailure) {
      await _databaseService.updateMessageRetryCount(
        message.id,
        message.retryCount + 1,
      );
    }
    return success;
  }

  /// Publish a chat message according to the backend contract.
  /// The message is stored locally and remains queued until the server confirms it.
  Future<ChatMessage?> sendChatMessage({
    required String sender,
    required String receiver,
    required String content,
    ChatContentType contentType = ChatContentType.text,
    String? jobId,
    String? jobNumber,
  }) async {
    final trimmedSender = sender.trim();
    final trimmedReceiver = receiver.trim();
    final trimmedContent = content.trim();
    final normalizedJobId = jobId?.trim();
    final normalizedJobNumber = jobNumber?.trim();
    final hasJobContext =
        (normalizedJobId?.isNotEmpty ?? false) ||
        (normalizedJobNumber?.isNotEmpty ?? false);

    if (trimmedSender.isEmpty ||
        trimmedReceiver.isEmpty ||
        trimmedContent.isEmpty) {
      developer.log(
        'Cannot send chat message - missing required fields',
        name: 'WebSocketService',
      );
      return null;
    }

    if (normalizedJobId != null &&
        normalizedJobId.isNotEmpty &&
        !_jobIdRegExp.hasMatch(normalizedJobId)) {
      developer.log(
        'Cannot send chat message - jobId has invalid format: $normalizedJobId',
        name: 'WebSocketService',
      );
      return null;
    }

    final payload = <String, dynamic>{
      'sender': trimmedSender,
      'receiver': trimmedReceiver,
      'content': trimmedContent,
    };
    final now = DateTime.now();
    final clientMessageId = 'local-${now.microsecondsSinceEpoch}';
    payload['messageId'] = clientMessageId;

    if (normalizedJobId != null && normalizedJobId.isNotEmpty) {
      payload['jobId'] = normalizedJobId;
    }
    if (normalizedJobNumber != null && normalizedJobNumber.isNotEmpty) {
      payload['jobNumber'] = normalizedJobNumber;
    }
    payload['contentType'] = chatContentTypeToString(contentType);

    const topic = '/server/message';

    try {
      final message = ChatMessage(
        id: clientMessageId,
        content: trimmedContent,
        createdAt: now,
        direction: ChatDirection.outgoing,
        messageType:
            hasJobContext
                ? ChatMessageType.jobRelated
                : ChatMessageType.general,
        contentType: contentType,
        jobId: normalizedJobId?.isEmpty ?? true ? null : normalizedJobId,
        jobNumber:
            normalizedJobNumber?.isEmpty ?? true ? null : normalizedJobNumber,
        read: false,
        pendingSync: true,
      );
      final queuedMessage = QueuedMessage(
        id: clientMessageId,
        topic: topic,
        payload: payload,
        createdAt: now,
      );

      await _databaseService.queueMessage(queuedMessage);
      await ChatService().saveOutgoingMessage(message);
      final sentImmediately = await _trySendQueuedChatMessage(queuedMessage);
      if (!sentImmediately) {
        developer.log(
          'Chat message $clientMessageId queued for retry after reconnect',
          name: 'WebSocketService',
        );
      }

      return message;
    } catch (e, st) {
      developer.log('Error sending chat message: $e', name: 'WebSocketService');
      developer.log('Stack: $st', name: 'WebSocketService');
      return null;
    }
  }

  /// Subscribe to a topic (no-op for WebSocket - all messages arrive on one connection)
  void subscribe(String topic, [Function? callback]) {
    // WebSocket does not use topic subscriptions.
    // All messages are routed by the server to this connection after authentication.
  }

  /// Send login request
  Future<void> login(String email, String password) async {
    final loginStartTime = DateTime.now();
    final sessionId = loginStartTime.millisecondsSinceEpoch.toString();

    developer.log('=== LOGIN METHOD CALLED ===', name: 'WebSocketService');
    developer.log('Email: $email', name: 'WebSocketService');
    developer.log('isConnected: $_isConnected', name: 'WebSocketService');
    developer.log(
      'wsChannel: ${_wsChannel != null ? "exists" : "null"}',
      name: 'WebSocketService',
    );

    await _ensureAppId();
    developer.log('AppId: $_appId', name: 'WebSocketService');

    if (!_isConnected || _wsChannel == null) {
      developer.log(
        'LOGIN ABORTED: Not connected to server',
        name: 'WebSocketService',
      );
      _lastAuthResponse = {
        'success': false,
        'message': 'Nicht mit Server verbunden',
        'sessionId': sessionId,
        'timestamp': loginStartTime.toIso8601String(),
      };
      DartMQ().publish<Map<String, dynamic>>(
        MQTopics.authResponse,
        _lastAuthResponse!,
      );
      return;
    }

    final loginData = {'email': email, 'password': password};

    try {
      const topic = '/server/login';
      final jsonPayload = jsonEncode(loginData);

      developer.log(
        'Sending login message to $topic',
        name: 'WebSocketService',
      );

      // Send login directly (not via sendMessage which buffers until authenticated)
      _sendWebSocket(topic, jsonPayload);

      developer.log(
        'Login message sent successfully',
        name: 'WebSocketService',
      );
    } catch (e, st) {
      developer.log('Error sending login: $e', name: 'WebSocketService');
      developer.log('Stack: $st', name: 'WebSocketService');

      _lastAuthResponse = {
        'success': false,
        'message': 'Fehler beim Senden der Anmeldedaten',
        'error': e.toString(),
        'sessionId': sessionId,
        'timestamp': DateTime.now().toIso8601String(),
      };
      DartMQ().publish<Map<String, dynamic>>(
        MQTopics.authResponse,
        _lastAuthResponse!,
      );
    }
  }

  /// Retry login with saved credentials (called from UI after auth timeout)
  Future<void> retryLoginWithSavedCredentials() async {
    if (!_isConnected || _wsChannel == null) {
      developer.log(
        'Cannot retry login: not connected to server',
        name: 'WebSocketService',
      );
      return;
    }

    final credentials = await _databaseService.loadCredentials();
    if (credentials != null) {
      developer.log(
        'Retrying login with saved credentials for ${credentials.email}',
        name: 'WebSocketService',
      );
      await login(credentials.email, credentials.password);
    } else {
      developer.log(
        'Cannot retry login: no saved credentials found',
        name: 'WebSocketService',
      );
    }
  }

  /// Logout user
  Future<void> logout() async {
    _isAuthenticated = false;
    _authToken = null;
    // Delete saved credentials to prevent auto-login
    await _databaseService.deleteCredentials();
    // Stop location tracking
    LocationService().stopTracking();
  }

  /// Disconnect from WebSocket server
  Future<void> disconnect() async {
    // Stop location tracking
    LocationService().stopTracking();

    if (_wsChannel != null) {
      final completer = Completer<void>();
      _disconnectCompleter = completer;

      try {
        await _wsChannel!.sink.close(ws_status.normalClosure);
      } catch (e, st) {
        developer.log('Error during disconnect: $e', name: 'WebSocketService');
        developer.log('Stack: $st', name: 'WebSocketService');
        if (!completer.isCompleted) {
          completer.complete();
        }
      }

      try {
        await completer.future.timeout(const Duration(seconds: 3));
      } catch (_) {}

      _wsSubscription?.cancel();
      _wsSubscription = null;
      _wsChannel = null;
      _disconnectCompleter = null;
    }
    _isConnected = false;
    _isAuthenticated = false;
    _authToken = null;
    _stopReconnectTimer();
    _lastAuthResponse = null;
    _messageBuffer.clear();
    Future.microtask(() {
      DartMQ().publish<bool>(MQTopics.connectionStatus, false);
    });
  }

  /// Reset service state without disposing (useful for logout)
  Future<void> reset() async {
    await disconnect();
  }

  /// Send task completion event to server.
  /// Messages are buffered if offline and sent automatically when reconnected.
  Future<void> sendTaskCompleted({
    required String taskId,
    String? taskType,
    Map<String, dynamic>? extraData,
  }) async {
    final String normalizedType = (taskType ?? 'UNKNOWN').toUpperCase();
    const String destination = '/server/task_completed';

    final payload = <String, dynamic>{
      'taskId': taskId,
      'taskType': normalizedType,
    };
    if (extraData != null && extraData.isNotEmpty) {
      payload['extraData'] = extraData;
    }

    try {
      final jsonPayload = jsonEncode(payload);
      // sendMessage buffers automatically if not connected/authenticated
      sendMessage(destination, jsonPayload);
    } catch (e, st) {
      developer.log(
        'Error sending task completion: $e',
        name: 'WebSocketService',
      );
      developer.log('Stack: $st', name: 'WebSocketService');
    }
  }

  /// Send station completion event to server.
  /// Messages are buffered if offline and sent automatically when reconnected.
  Future<void> sendStationCompleted({
    required String jobId,
    required String jobNumber,
    required int stationOrder,
    bool hasIncompleteOptionalTasks = false,
  }) async {
    const String destination = '/server/station_completed';

    final payload = <String, dynamic>{
      'jobId': jobId,
      'jobNumber': jobNumber,
      'stationOrder': stationOrder,
      'completedAt': DateTime.now().toUtc().toIso8601String(),
      'hasIncompleteOptionalTasks': hasIncompleteOptionalTasks,
    };

    try {
      final jsonPayload = jsonEncode(payload);
      sendMessage(destination, jsonPayload);
    } catch (e, st) {
      developer.log(
        'Error sending station completion: $e',
        name: 'WebSocketService',
      );
      developer.log('Stack: $st', name: 'WebSocketService');
    }
  }

  /// Dispose resources
  void dispose() {
    _stopReconnectTimer();
    disconnect();
  }
}

class _BufferedMessage {
  final String topic;
  final String jsonPayload;
  _BufferedMessage(this.topic, this.jsonPayload);
}

// Backward compatibility typedef
typedef StompService = WebSocketService;
