import 'package:flutter_test/flutter_test.dart';
import 'package:whitenoise/services/message_service.dart';
import 'package:whitenoise/src/rust/api/markdown.dart';
import 'package:whitenoise/src/rust/api/media_files.dart';
import 'package:whitenoise/src/rust/api/messages.dart';
import 'package:whitenoise/src/rust/frb_generated.dart';

import '../mocks/mock_wn_api.dart';
import '../test_helpers.dart';

const _testPubkey = testPubkeyA;

class _MockTag implements Tag {
  final List<String> vec;
  _MockTag(this.vec);

  @override
  void dispose() {}

  @override
  bool get isDisposed => false;
}

const _mockNeventPrefix = 'nostr:nevent1mock';

class _MockApi extends MockWnApi {
  final List<({String pubkey, String groupId, String message, int kind, List<Tag>? tags})>
  sentMessages = [];
  final List<({String pubkey, String groupId, String eventId})> retryAttempts = [];
  bool shouldFailSendMessage = false;
  bool shouldFailRetry = false;

  @override
  Future<Tag> crateApiUtilsTagFromVec({required List<String> vec}) async {
    return _MockTag(vec);
  }

  @override
  String crateApiUtilsEventIdToNeventUri({
    required String eventIdHex,
    required String pubkeyHex,
  }) {
    return '$_mockNeventPrefix$eventIdHex';
  }

  @override
  Future<MessageWithTokens> crateApiMessagesSendMessageToGroup({
    required String pubkey,
    required String groupId,
    required String message,
    required int kind,
    List<Tag>? tags,
  }) async {
    sentMessages.add((pubkey: pubkey, groupId: groupId, message: message, kind: kind, tags: tags));
    if (shouldFailSendMessage) throw Exception('send message failed');
    return MessageWithTokens(
      id: 'sent_${sentMessages.length}',
      pubkey: pubkey,
      kind: kind,
      createdAt: DateTime.now(),
      content: message,
      tokens: const MarkdownDocument(blocks: []),
    );
  }

  @override
  Future<void> crateApiMessagesRetryMessagePublish({
    required String pubkey,
    required String groupId,
    required String eventId,
  }) async {
    retryAttempts.add((pubkey: pubkey, groupId: groupId, eventId: eventId));
    if (shouldFailRetry) throw Exception('retry failed');
  }
}

void main() {
  late _MockApi mockApi;
  late MessageService service;

  setUpAll(() {
    mockApi = _MockApi();
    RustLib.initMock(api: mockApi);
  });

  setUp(() {
    mockApi.sentMessages.clear();
    mockApi.retryAttempts.clear();
    mockApi.shouldFailSendMessage = false;
    mockApi.shouldFailRetry = false;
    service = const MessageService(pubkey: _testPubkey, groupId: 'group1');
  });

  group('retryMessage', () {
    test('calls retry API once', () async {
      await service.retryMessage(eventId: 'event123');

      expect(mockApi.retryAttempts.length, 1);
    });

    test('passes correct pubkey from constructor', () async {
      await service.retryMessage(eventId: 'event123');

      expect(mockApi.retryAttempts.first.pubkey, _testPubkey);
    });

    test('passes correct groupId from constructor', () async {
      await service.retryMessage(eventId: 'event123');

      expect(mockApi.retryAttempts.first.groupId, 'group1');
    });

    test('passes correct eventId argument', () async {
      await service.retryMessage(eventId: 'event123');

      expect(mockApi.retryAttempts.first.eventId, 'event123');
    });

    test('rethrows when API call fails', () async {
      mockApi.shouldFailRetry = true;

      expect(
        () => service.retryMessage(eventId: 'event123'),
        throwsA(
          isA<Exception>().having((e) => e.toString(), 'message', contains('retry failed')),
        ),
      );
    });
  });

  group('sendTextMessage', () {
    test('sends message once', () async {
      await service.sendTextMessage(content: 'Hello');

      expect(mockApi.sentMessages.length, 1);
    });

    test('calls API with pubkey from constructor', () async {
      await service.sendTextMessage(content: 'Hello');

      expect(mockApi.sentMessages.first.pubkey, _testPubkey);
    });

    test('calls API with groupId from constructor', () async {
      await service.sendTextMessage(content: 'Hello');

      expect(mockApi.sentMessages.first.groupId, 'group1');
    });

    test('calls API with message content', () async {
      await service.sendTextMessage(content: 'Hello World');

      expect(mockApi.sentMessages.first.message, 'Hello World');
    });

    test('calls API with text message kind (9)', () async {
      await service.sendTextMessage(content: 'Hello');

      expect(mockApi.sentMessages.first.kind, 9);
    });

    test('sends message without tags when no reply params', () async {
      await service.sendTextMessage(content: 'Hello');

      expect(mockApi.sentMessages.first.tags, isNull);
    });

    test('rethrows error when API call fails', () async {
      mockApi.shouldFailSendMessage = true;

      expect(
        () => service.sendTextMessage(content: 'Hello'),
        throwsA(
          isA<Exception>().having((e) => e.toString(), 'message', contains('send message failed')),
        ),
      );
    });
  });

  group('sendTextMessage (reply)', () {
    const testReplyId = 'reply_msg_id';
    const testReplyPubkey = testPubkeyB;
    const testReplyKind = 9;

    test('sends message once when reply params provided', () async {
      await service.sendTextMessage(
        content: 'Reply content',
        replyToMessageId: testReplyId,
        replyToMessagePubkey: testReplyPubkey,
        replyToMessageKind: testReplyKind,
      );

      expect(mockApi.sentMessages.length, 1);
    });

    test('prepends nevent URI to content when replying', () async {
      await service.sendTextMessage(
        content: 'Reply content',
        replyToMessageId: testReplyId,
        replyToMessagePubkey: testReplyPubkey,
        replyToMessageKind: testReplyKind,
      );

      expect(
        mockApi.sentMessages.first.message,
        '$_mockNeventPrefix$testReplyId\nReply content',
      );
    });

    test('calls API with text message kind (9) when replying', () async {
      await service.sendTextMessage(
        content: 'Reply content',
        replyToMessageId: testReplyId,
        replyToMessagePubkey: testReplyPubkey,
        replyToMessageKind: testReplyKind,
      );

      expect(mockApi.sentMessages.first.kind, 9);
    });

    test('sends single q tag with event id and pubkey', () async {
      await service.sendTextMessage(
        content: 'Reply content',
        replyToMessageId: testReplyId,
        replyToMessagePubkey: testReplyPubkey,
        replyToMessageKind: testReplyKind,
      );

      final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
      expect(tags.length, 1);
      expect(tags[0].vec, ['q', testReplyId, '', testReplyPubkey]);
    });

    test('sends no tags when only replyToMessageId provided', () async {
      await service.sendTextMessage(
        content: 'Reply content',
        replyToMessageId: testReplyId,
      );

      expect(mockApi.sentMessages.first.tags, isNull);
    });

    test('sends no tags when only replyToMessagePubkey provided', () async {
      await service.sendTextMessage(
        content: 'Reply content',
        replyToMessagePubkey: testReplyPubkey,
      );

      expect(mockApi.sentMessages.first.tags, isNull);
    });

    test('sends no tags when only replyToMessageKind provided', () async {
      await service.sendTextMessage(
        content: 'Reply content',
        replyToMessageKind: testReplyKind,
      );

      expect(mockApi.sentMessages.first.tags, isNull);
    });

    test('does not prepend nevent URI when reply params incomplete', () async {
      await service.sendTextMessage(
        content: 'Reply content',
        replyToMessageId: testReplyId,
      );

      expect(mockApi.sentMessages.first.message, 'Reply content');
    });
  });

  group('deleteTextMessage', () {
    test('sends deletion message once', () async {
      await service.deleteTextMessage(
        messageId: 'msg123',
        messagePubkey: testPubkeyB,
      );

      expect(mockApi.sentMessages.length, 1);
    });

    test('calls API with pubkey from constructor', () async {
      await service.deleteTextMessage(
        messageId: 'msg123',
        messagePubkey: testPubkeyB,
      );

      expect(mockApi.sentMessages.first.pubkey, _testPubkey);
    });

    test('calls API with groupId from constructor', () async {
      await service.deleteTextMessage(
        messageId: 'msg123',
        messagePubkey: testPubkeyB,
      );

      expect(mockApi.sentMessages.first.groupId, 'group1');
    });

    test('calls API with empty message', () async {
      await service.deleteTextMessage(
        messageId: 'msg123',
        messagePubkey: testPubkeyB,
      );

      expect(mockApi.sentMessages.first.message, '');
    });

    test('calls API with deletion kind (5)', () async {
      await service.deleteTextMessage(
        messageId: 'msg123',
        messagePubkey: testPubkeyB,
      );

      expect(mockApi.sentMessages.first.kind, 5);
    });

    test('sends e tag with messageId', () async {
      await service.deleteTextMessage(
        messageId: 'msg123',
        messagePubkey: testPubkeyB,
      );

      final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
      expect(tags[0].vec, ['e', 'msg123']);
    });

    test('sends p tag with messagePubkey', () async {
      await service.deleteTextMessage(
        messageId: 'msg123',
        messagePubkey: testPubkeyB,
      );

      final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
      expect(tags[1].vec, ['p', testPubkeyB, '']);
    });

    test('sends k tag with text message kind (9)', () async {
      await service.deleteTextMessage(
        messageId: 'msg123',
        messagePubkey: testPubkeyB,
      );

      final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
      expect(tags[2].vec, ['k', '9']);
    });
  });

  group('deleteReaction', () {
    test('sends deletion message once', () async {
      await service.deleteReaction(
        reactionId: 'reaction123',
        reactionPubkey: testPubkeyB,
      );

      expect(mockApi.sentMessages.length, 1);
    });

    test('calls API with pubkey from constructor', () async {
      await service.deleteReaction(
        reactionId: 'reaction123',
        reactionPubkey: testPubkeyB,
      );

      expect(mockApi.sentMessages.first.pubkey, _testPubkey);
    });

    test('calls API with groupId from constructor', () async {
      await service.deleteReaction(
        reactionId: 'reaction123',
        reactionPubkey: testPubkeyB,
      );

      expect(mockApi.sentMessages.first.groupId, 'group1');
    });

    test('calls API with empty message', () async {
      await service.deleteReaction(
        reactionId: 'reaction123',
        reactionPubkey: testPubkeyB,
      );

      expect(mockApi.sentMessages.first.message, '');
    });

    test('calls API with deletion kind (5)', () async {
      await service.deleteReaction(
        reactionId: 'reaction123',
        reactionPubkey: testPubkeyB,
      );

      expect(mockApi.sentMessages.first.kind, 5);
    });

    test('sends e tag with reactionId', () async {
      await service.deleteReaction(
        reactionId: 'reaction123',
        reactionPubkey: testPubkeyB,
      );

      final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
      expect(tags[0].vec, ['e', 'reaction123']);
    });

    test('sends p tag with reactionPubkey', () async {
      await service.deleteReaction(
        reactionId: 'reaction123',
        reactionPubkey: testPubkeyB,
      );

      final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
      expect(tags[1].vec, ['p', testPubkeyB, '']);
    });

    test('sends k tag with reaction kind (7)', () async {
      await service.deleteReaction(
        reactionId: 'reaction123',
        reactionPubkey: testPubkeyB,
      );

      final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
      expect(tags[2].vec, ['k', '7']);
    });
  });

  group('toggleReaction', () {
    ChatMessage createMessage({
      String id = 'msg123',
      String pubkey = testPubkeyB,
      ReactionSummary? reactions,
    }) => ChatMessage(
      id: id,
      pubkey: pubkey,
      content: 'Test message',
      createdAt: DateTime.now(),
      tags: const [],
      isReply: false,
      isDeleted: false,
      contentTokens: const MarkdownDocument(blocks: []),
      reactions: reactions ?? const ReactionSummary(byEmoji: [], userReactions: []),
      mediaAttachments: const [],
      kind: 9,
    );

    test('sends reaction when user has not reacted', () async {
      final message = createMessage();

      await service.toggleReaction(message: message, emoji: '👍');

      expect(mockApi.sentMessages.length, 1);
      expect(mockApi.sentMessages.first.kind, 7);
      expect(mockApi.sentMessages.first.message, '👍');
    });

    test('deletes reaction when user has already reacted with same emoji', () async {
      final message = createMessage(
        reactions: ReactionSummary(
          byEmoji: [
            EmojiReaction(
              emoji: '👍',
              count: BigInt.one,
              users: const [_testPubkey],
            ),
          ],
          userReactions: [
            UserReaction(
              reactionId: 'reaction_to_delete',
              user: _testPubkey,
              emoji: '👍',
              createdAt: DateTime.now(),
            ),
          ],
        ),
      );

      await service.toggleReaction(message: message, emoji: '👍');

      expect(mockApi.sentMessages.length, 1);
      expect(mockApi.sentMessages.first.kind, 5);
      final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
      expect(tags[0].vec, ['e', 'reaction_to_delete']);
    });

    test('sends new reaction when user has reacted with different emoji', () async {
      final message = createMessage(
        reactions: ReactionSummary(
          byEmoji: [
            EmojiReaction(
              emoji: '👍',
              count: BigInt.one,
              users: const [_testPubkey],
            ),
          ],
          userReactions: [
            UserReaction(
              reactionId: 'existing_reaction',
              user: _testPubkey,
              emoji: '👍',
              createdAt: DateTime.now(),
            ),
          ],
        ),
      );

      await service.toggleReaction(message: message, emoji: '❤');

      expect(mockApi.sentMessages.length, 1);
      expect(mockApi.sentMessages.first.kind, 7);
      expect(mockApi.sentMessages.first.message, '❤');
    });

    test('sends reaction when other users have reacted but not current user', () async {
      final message = createMessage(
        reactions: ReactionSummary(
          byEmoji: [
            EmojiReaction(emoji: '👍', count: BigInt.one, users: const [testPubkeyC]),
          ],
          userReactions: [
            UserReaction(
              reactionId: 'other_reaction',
              user: testPubkeyC,
              emoji: '👍',
              createdAt: DateTime.now(),
            ),
          ],
        ),
      );

      await service.toggleReaction(message: message, emoji: '👍');

      expect(mockApi.sentMessages.length, 1);
      expect(mockApi.sentMessages.first.kind, 7);
      expect(mockApi.sentMessages.first.message, '👍');
    });
  });

  group('sendReaction', () {
    test('sends reaction message once', () async {
      await service.sendReaction(
        messageId: 'msg123',
        messagePubkey: testPubkeyB,
        messageKind: 9,
        emoji: '👍',
      );

      expect(mockApi.sentMessages.length, 1);
    });

    test('calls API with pubkey from constructor', () async {
      await service.sendReaction(
        messageId: 'msg123',
        messagePubkey: testPubkeyB,
        messageKind: 9,
        emoji: '👍',
      );

      expect(mockApi.sentMessages.first.pubkey, _testPubkey);
    });

    test('calls API with groupId from constructor', () async {
      await service.sendReaction(
        messageId: 'msg123',
        messagePubkey: testPubkeyB,
        messageKind: 9,
        emoji: '👍',
      );

      expect(mockApi.sentMessages.first.groupId, 'group1');
    });

    test('calls API with emoji as message', () async {
      await service.sendReaction(
        messageId: 'msg123',
        messagePubkey: testPubkeyB,
        messageKind: 9,
        emoji: '🔥',
      );

      expect(mockApi.sentMessages.first.message, '🔥');
    });

    test('calls API with reaction kind (7)', () async {
      await service.sendReaction(
        messageId: 'msg123',
        messagePubkey: testPubkeyB,
        messageKind: 9,
        emoji: '👍',
      );

      expect(mockApi.sentMessages.first.kind, 7);
    });

    test('sends e tag with messageId', () async {
      await service.sendReaction(
        messageId: 'msg123',
        messagePubkey: testPubkeyB,
        messageKind: 9,
        emoji: '👍',
      );

      final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
      expect(tags[0].vec, ['e', 'msg123']);
    });

    test('sends p tag with messagePubkey', () async {
      await service.sendReaction(
        messageId: 'msg123',
        messagePubkey: testPubkeyB,
        messageKind: 9,
        emoji: '👍',
      );

      final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
      expect(tags[1].vec, ['p', testPubkeyB, '']);
    });

    test('sends k tag with messageKind', () async {
      await service.sendReaction(
        messageId: 'msg123',
        messagePubkey: testPubkeyB,
        messageKind: 42,
        emoji: '👍',
      );

      final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
      expect(tags[2].vec, ['k', '42']);
    });
  });

  group('sendMessage', () {
    MediaFile createMediaFile({
      String id = 'media1',
      String blossomUrl = 'https://blossom.example.com/file.jpg',
      String mimeType = 'image/jpeg',
      String encryptedFileHash = 'abc123',
      String? originalFileHash = 'original123',
      FileMetadata? fileMetadata,
      String? nonce,
      String? schemeVersion,
    }) => MediaFile(
      id: id,
      mlsGroupId: testGroupId,
      accountPubkey: _testPubkey,
      filePath: '/path/to/file.jpg',
      originalFileHash: originalFileHash,
      encryptedFileHash: encryptedFileHash,
      mimeType: mimeType,
      mediaType: 'image',
      blossomUrl: blossomUrl,
      nostrKey: 'nostr123',
      fileMetadata: fileMetadata,
      createdAt: DateTime(2024),
      nonce: nonce,
      schemeVersion: schemeVersion,
    );

    test('sends message once', () async {
      await service.sendMessage(content: 'Hello');

      expect(mockApi.sentMessages.length, 1);
    });

    test('calls API with pubkey from constructor', () async {
      await service.sendMessage(content: 'Hello');

      expect(mockApi.sentMessages.first.pubkey, _testPubkey);
    });

    test('calls API with groupId from constructor', () async {
      await service.sendMessage(content: 'Hello');

      expect(mockApi.sentMessages.first.groupId, 'group1');
    });

    test('calls API with message content', () async {
      await service.sendMessage(content: 'Hello World');

      expect(mockApi.sentMessages.first.message, 'Hello World');
    });

    test('calls API with text message kind (9)', () async {
      await service.sendMessage(content: 'Hello');

      expect(mockApi.sentMessages.first.kind, 9);
    });

    test('sends message without tags when no reply or media', () async {
      await service.sendMessage(content: 'Hello');

      expect(mockApi.sentMessages.first.tags, isNull);
    });

    group('with reply params', () {
      const testReplyId = 'reply_msg_id';
      const testReplyPubkey = testPubkeyB;
      const testReplyKind = 9;

      test('sends single q tag with event id and pubkey', () async {
        await service.sendMessage(
          content: 'Reply content',
          replyToMessageId: testReplyId,
          replyToMessagePubkey: testReplyPubkey,
          replyToMessageKind: testReplyKind,
        );

        final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
        expect(tags.length, 1);
        expect(tags[0].vec, ['q', testReplyId, '', testReplyPubkey]);
      });

      test('prepends nevent URI to content', () async {
        await service.sendMessage(
          content: 'Reply content',
          replyToMessageId: testReplyId,
          replyToMessagePubkey: testReplyPubkey,
          replyToMessageKind: testReplyKind,
        );

        expect(
          mockApi.sentMessages.first.message,
          '$_mockNeventPrefix$testReplyId\nReply content',
        );
      });
    });

    group('with media files', () {
      test('sends imeta tag for single media file', () async {
        final media = createMediaFile();

        await service.sendMessage(content: 'With media', mediaFiles: [media]);

        final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
        expect(tags.length, 1);
        expect(tags[0].vec[0], 'imeta');
      });

      test('includes url in imeta tag', () async {
        final media = createMediaFile(blossomUrl: 'https://blossom.test/image.jpg');

        await service.sendMessage(content: 'With media', mediaFiles: [media]);

        final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
        expect(tags[0].vec, contains('url https://blossom.test/image.jpg'));
      });

      test('includes mime type in imeta tag', () async {
        final media = createMediaFile(mimeType: 'image/png');

        await service.sendMessage(content: 'With media', mediaFiles: [media]);

        final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
        expect(tags[0].vec, contains('m image/png'));
      });

      test('includes original file hash in imeta tag', () async {
        final media = createMediaFile();

        await service.sendMessage(content: 'With media', mediaFiles: [media]);

        final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
        expect(tags[0].vec, contains('x original123'));
      });

      test('includes version tag in imeta tag when available', () async {
        final media = createMediaFile(schemeVersion: 'mip04-v2');

        await service.sendMessage(content: 'With media', mediaFiles: [media]);

        final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
        expect(tags[0].vec, contains('v mip04-v2'));
      });

      test('always includes filename in imeta tag', () async {
        final media = createMediaFile();

        await service.sendMessage(content: 'With media', mediaFiles: [media]);

        final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
        expect(tags[0].vec, contains('filename '));
      });

      test('includes filename when available', () async {
        final media = createMediaFile(
          fileMetadata: const FileMetadata(originalFilename: 'photo.jpg'),
        );

        await service.sendMessage(content: 'With media', mediaFiles: [media]);

        final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
        expect(tags[0].vec, contains('filename photo.jpg'));
      });

      test('includes blurhash when available', () async {
        final media = createMediaFile(
          fileMetadata: const FileMetadata(blurhash: 'LEHV6nWB2yk8pyo0adR*.7kCMdnj'),
        );

        await service.sendMessage(content: 'With media', mediaFiles: [media]);

        final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
        expect(tags[0].vec, contains('blurhash LEHV6nWB2yk8pyo0adR*.7kCMdnj'));
      });

      test('includes dimensions when available', () async {
        final media = createMediaFile(
          fileMetadata: const FileMetadata(dimensions: '1920x1080'),
        );

        await service.sendMessage(content: 'With media', mediaFiles: [media]);

        final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
        expect(tags[0].vec, contains('dim 1920x1080'));
      });

      test('includes nonce when available', () async {
        final media = createMediaFile(nonce: 'nonce123');

        await service.sendMessage(content: 'With media', mediaFiles: [media]);

        final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
        expect(tags[0].vec, contains('n nonce123'));
      });

      test('omits nonce when not available', () async {
        final media = createMediaFile();

        await service.sendMessage(content: 'With media', mediaFiles: [media]);

        final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
        expect(tags[0].vec.any((s) => s.startsWith('n ')), isFalse);
      });

      test('sends multiple imeta tags for multiple media files', () async {
        final media1 = createMediaFile(blossomUrl: 'https://a.com/1.jpg');
        final media2 = createMediaFile(id: 'media2', blossomUrl: 'https://a.com/2.jpg');

        await service.sendMessage(content: 'With media', mediaFiles: [media1, media2]);

        final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
        expect(tags.length, 2);
        expect(tags[0].vec[0], 'imeta');
        expect(tags[1].vec[0], 'imeta');
      });

      test('omits x tag when originalFileHash is null', () async {
        final media = createMediaFile(originalFileHash: null);

        await service.sendMessage(content: 'With media', mediaFiles: [media]);

        final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
        expect(tags[0].vec.any((s) => s.startsWith('x ')), isFalse);
      });

      test('omits blurhash and dim when not available', () async {
        final media = createMediaFile();

        await service.sendMessage(content: 'With media', mediaFiles: [media]);

        final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
        expect(tags[0].vec.any((s) => s.startsWith('blurhash')), isFalse);
        expect(tags[0].vec.any((s) => s.startsWith('dim')), isFalse);
      });
    });

    group('with reply and media', () {
      test('sends q tag and imeta tag', () async {
        final media = createMediaFile();

        await service.sendMessage(
          content: 'Reply with media',
          replyToMessageId: 'reply_id',
          replyToMessagePubkey: testPubkeyB,
          replyToMessageKind: 9,
          mediaFiles: [media],
        );

        final tags = mockApi.sentMessages.first.tags!.cast<_MockTag>();
        expect(tags.length, 2);
        expect(tags[0].vec[0], 'q');
        expect(tags[1].vec[0], 'imeta');
      });

      test('prepends nevent URI to content with media', () async {
        final media = createMediaFile();

        await service.sendMessage(
          content: 'Reply with media',
          replyToMessageId: 'reply_id',
          replyToMessagePubkey: testPubkeyB,
          replyToMessageKind: 9,
          mediaFiles: [media],
        );

        expect(
          mockApi.sentMessages.first.message,
          startsWith(_mockNeventPrefix),
        );
        expect(
          mockApi.sentMessages.first.message,
          endsWith('\nReply with media'),
        );
      });
    });
  });
}
