import 'package:flutter/foundation.dart';

/// Represents a message awaiting acknowledgment
class PendingMessage {
  /// Unique message identifier
  final String messageId;

  /// Target topic
  final String topic;

  /// The JSON payload to retry sending
  final String jsonPayload;

  /// When the message was originally sent
  final DateTime sentAt;

  /// Number of retry attempts so far
  int retryCount;

  PendingMessage({
    required this.messageId,
    required this.topic,
    required this.jsonPayload,
    required this.sentAt,
    this.retryCount = 0,
  });
}

/// Tracks pending messages awaiting acknowledgment and handles retries.
///
/// This class is extracted from WebSocketService for testability.
/// It manages:
/// - Tracking sent messages that require ACK
/// - Removing messages when ACK is received
/// - Retrying unacknowledged messages
/// - Timing out messages after max retries
class AckTracker {
  final Map<String, PendingMessage> _pendingMessages = {};

  /// Maximum number of retry attempts before timeout
  final int maxRetries;

  /// Callback to retry sending a message.
  /// Returns true if send was successful.
  final Future<bool> Function(String topic, String payload)? onRetry;

  /// Callback when a message times out (max retries exceeded)
  final void Function(String messageId, String topic)? onTimeout;

  AckTracker({
    this.maxRetries = 4,
    this.onRetry,
    this.onTimeout,
  });

  /// Track a sent message that requires acknowledgment
  void track(String messageId, String topic, String payload) {
    _pendingMessages[messageId] = PendingMessage(
      messageId: messageId,
      topic: topic,
      jsonPayload: payload,
      sentAt: DateTime.now(),
    );
  }

  /// Remove a message from tracking (ACK received)
  void acknowledge(String messageId) {
    _pendingMessages.remove(messageId);
  }

  /// Check if a message is pending acknowledgment
  bool isPending(String messageId) =>
      _pendingMessages.containsKey(messageId);

  /// Get the number of pending messages
  int get pendingCount => _pendingMessages.length;

  /// Get all pending message IDs
  List<String> get pendingMessageIds =>
      List.unmodifiable(_pendingMessages.keys);

  /// Get a pending message by ID (for testing)
  @visibleForTesting
  PendingMessage? getPendingMessage(String messageId) =>
      _pendingMessages[messageId];

  /// Process all pending messages and retry if needed.
  ///
  /// This should be called periodically (e.g., every 5 seconds).
  /// Messages that exceed maxRetries will be timed out and removed.
  ///
  /// Set [isConnected] to false to skip retry attempts while disconnected.
  Future<void> processRetries({bool isConnected = true}) async {
    if (_pendingMessages.isEmpty) {
      return;
    }

    final messagesToRemove = <String>[];

    for (final entry in _pendingMessages.entries) {
      final messageId = entry.key;
      final pending = entry.value;

      if (pending.retryCount >= maxRetries) {
        // Max retries exceeded - timeout
        onTimeout?.call(messageId, pending.topic);
        messagesToRemove.add(messageId);
      } else if (isConnected) {
        // Increment retry count and attempt resend
        pending.retryCount++;

        if (onRetry != null) {
          await onRetry!(pending.topic, pending.jsonPayload);
        }
      }
    }

    // Remove timed out messages
    for (final messageId in messagesToRemove) {
      _pendingMessages.remove(messageId);
    }
  }

  /// Clear all pending messages.
  ///
  /// Primarily for testing purposes.
  @visibleForTesting
  void clearAll() => _pendingMessages.clear();

  /// Clear pending messages for a specific topic pattern.
  ///
  /// Useful for clearing login messages when auth response is received.
  void clearForTopic(String topicPattern) {
    final toRemove = _pendingMessages.entries
        .where((e) => e.value.topic == topicPattern)
        .map((e) => e.key)
        .toList();

    for (final messageId in toRemove) {
      _pendingMessages.remove(messageId);
    }
  }
}
