import 'package:core/data/network/config/dynamic_url_interceptors.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/presentation/utils/app_toast.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:core/utils/platform_info.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'package:html_editor_enhanced/html_editor.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:labels/model/label.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:model/email/attachment.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/extensions/session_extension.dart';
import 'package:model/mailbox/expand_mode.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:rich_text_composer/rich_text_composer.dart';
import 'package:tmail_ui_user/features/base/before_reconnect_manager.dart';
import 'package:tmail_ui_user/features/caching/caching_manager.dart';
import 'package:tmail_ui_user/features/composer/domain/repository/composer_repository.dart';
import 'package:tmail_ui_user/features/composer/domain/state/save_email_as_drafts_state.dart';
import 'package:tmail_ui_user/features/composer/domain/state/update_email_drafts_state.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/create_new_and_save_email_to_drafts_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/create_new_and_send_email_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/download_image_as_base64_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/save_composer_cache_on_web_interactor.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_view_web.dart';
import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/setup_selected_identity_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/formatting_options_state.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/saved_composing_email.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/save_template_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/transform_html_email_content_interactor.dart';
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
import 'package:tmail_ui_user/features/labels/presentation/label_controller.dart';
import 'package:tmail_ui_user/features/login/data/network/interceptors/authorization_interceptors.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/delete_authority_oidc_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/remove_composer_cache_by_id_on_web_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/draggable_app_state.dart';
import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart';
import 'package:tmail_ui_user/features/manage_account/domain/model/preferences/ai_scribe_config.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/log_out_oidc_interactor.dart';
import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart';
import 'package:tmail_ui_user/features/server_settings/domain/usecases/get_server_setting_interactor.dart';
import 'package:tmail_ui_user/features/upload/domain/usecases/local_file_picker_interactor.dart';
import 'package:tmail_ui_user/features/upload/domain/usecases/local_image_picker_interactor.dart';
import 'package:tmail_ui_user/features/upload/presentation/controller/upload_controller.dart';
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart';
import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart';
import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart';
import 'package:tmail_ui_user/main/utils/app_config.dart';
import 'package:tmail_ui_user/main/utils/toast_manager.dart';
import 'package:tmail_ui_user/main/utils/twake_app_manager.dart';
import 'package:uuid/uuid.dart';

import '../../../fixtures/account_fixtures.dart';
import '../../../fixtures/session_fixtures.dart';
import '../../../fixtures/widget_fixtures.dart';
import '../../../mocks/mock_web_view_platform.dart';
import 'composer_controller_test.mocks.dart';

mockControllerCallback() => InternalFinalCallback<void>(callback: () {});
const fallbackGenerators = {
  #onStart: mockControllerCallback,
  #onDelete: mockControllerCallback,
};

class MockRichTextWebController extends Mock implements RichTextWebController {
  @override
  Rx<FormattingOptionsState> get formattingOptionsState => 
    FormattingOptionsState.disabled.obs;

  @override
  bool get isFormattingOptionsEnabled => formattingOptionsState.value == FormattingOptionsState.enabled;

  @override
  HtmlEditorController get editorController => MockHtmlEditorController();

  @override
  bool get codeViewEnabled => false;
}

class MockLabelController extends Mock implements LabelController {
  @override
  RxList<Label> get labels => RxList([]);

  @override
  Rx<ExpandMode> get labelListExpandMode => Rx(ExpandMode.EXPAND);
}

class MockMailboxDashBoardController extends Mock implements MailboxDashBoardController {
  @override
  InternalFinalCallback<void> get onStart => mockControllerCallback();
  @override
  InternalFinalCallback<void> get onDelete => mockControllerCallback();

  @override
  Rxn<AccountId> get accountId => Rxn(AccountFixtures.aliceAccountId);
  @override
  Session? get sessionCurrent => SessionFixtures.aliceSession;

  @override
  RxString get ownEmailAddress => SessionFixtures.aliceSession.getOwnEmailAddressOrEmpty().obs;

  @override
  Rxn<DraggableAppState> get attachmentDraggableAppState => Rxn(DraggableAppState.inActive);
  @override
  bool get isAttachmentDraggableAppActive => attachmentDraggableAppState.value == DraggableAppState.active;

  @override
  Rxn<DraggableAppState> get localFileDraggableAppState => Rxn(DraggableAppState.inActive);
  @override
  bool get isLocalFileDraggableAppActive => localFileDraggableAppState.value == DraggableAppState.active;

  @override
  Map<Role, MailboxId> get mapDefaultMailboxIdByRole => {PresentationMailbox.roleDrafts: MailboxId(Id('value'))};

  @override
  String get baseDownloadUrl => '';

  @override
  int get minInputLengthAutocomplete => AppConfig.defaultMinInputLengthAutocomplete;

  @override
  Map<MailboxId, PresentationMailbox> get mapMailboxById => {};

  @override
  RxBool get isAppGridDialogDisplayed => false.obs;

  @override
  RxBool get isDrawerOpened => false.obs;

  @override
  RxBool get isContextMenuOpened => false.obs;

  @override
  RxBool get isPopupMenuOpened => false.obs;

  @override
  Rx<AIScribeConfig> get cachedAIScribeConfig => AIScribeConfig.initial().obs;

  @override
  bool isAIScribeEndpointAvailable({Session? session, AccountId? accountId}) {
    return false;
  }

  @override
  LabelController get labelController => MockLabelController();
}

@GenerateNiceMocks([
  // Base controller mock specs
  MockSpec<CachingManager>(),
  MockSpec<LanguageCacheManager>(),
  MockSpec<AuthorizationInterceptors>(),
  MockSpec<DynamicUrlInterceptors>(),
  MockSpec<DeleteCredentialInteractor>(),
  MockSpec<LogoutOidcInteractor>(),
  MockSpec<DeleteAuthorityOidcInteractor>(),
  MockSpec<AppToast>(),
  MockSpec<ImagePaths>(),
  MockSpec<ResponsiveUtils>(),
  MockSpec<Uuid>(),
  MockSpec<ToastManager>(),
  MockSpec<TwakeAppManager>(),

  // Composer controller mock specs
  MockSpec<LocalFilePickerInteractor>(),
  MockSpec<LocalImagePickerInteractor>(),
  MockSpec<GetEmailContentInteractor>(),
  MockSpec<GetAllIdentitiesInteractor>(),
  MockSpec<UploadController>(fallbackGenerators: fallbackGenerators),
  MockSpec<RemoveComposerCacheByIdOnWebInteractor>(),
  MockSpec<SaveComposerCacheOnWebInteractor>(),
  MockSpec<DownloadImageAsBase64Interactor>(),
  MockSpec<TransformHtmlEmailContentInteractor>(),
  MockSpec<GetServerSettingInteractor>(),
  MockSpec<CreateNewAndSendEmailInteractor>(),
  MockSpec<CreateNewAndSaveEmailToDraftsInteractor>(),
  MockSpec<PrintEmailInteractor>(),
  MockSpec<ComposerRepository>(),
  MockSpec<SaveTemplateEmailInteractor>(),

  // Additional Getx dependencies mock specs
  MockSpec<NetworkConnectionController>(fallbackGenerators: fallbackGenerators),
  MockSpec<BeforeReconnectManager>(),
  MockSpec<RichTextMobileTabletController>(fallbackGenerators: fallbackGenerators),
  MockSpec<CacheExceptionThrower>(),

  // Additional misc dependencies mock specs
  MockSpec<HtmlEditorApi>(),
  MockSpec<HtmlEditorController>(),
])
void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  // Declaration base controller
  late MockCachingManager mockCachingManager;
  late MockLanguageCacheManager mockLanguageCacheManager;
  late MockAuthorizationInterceptors mockAuthorizationInterceptors;
  late MockDynamicUrlInterceptors mockDynamicUrlInterceptors;
  late MockDeleteCredentialInteractor mockDeleteCredentialInteractor;
  late MockLogoutOidcInteractor mockLogoutOidcInteractor;
  late MockDeleteAuthorityOidcInteractor mockDeleteAuthorityOidcInteractor;
  late MockAppToast mockAppToast;
  late MockUuid mockUuid;
  late MockToastManager mockToastManager;
  late MockTwakeAppManager mockTwakeAppManager;

  // Declaration composer controller
  late ComposerController? composerController;
  late MockLocalFilePickerInteractor mockLocalFilePickerInteractor;
  late MockLocalImagePickerInteractor mockLocalImagePickerInteractor;
  late MockGetEmailContentInteractor mockGetEmailContentInteractor;
  late MockGetAllIdentitiesInteractor mockGetAllIdentitiesInteractor;
  late MockUploadController mockUploadController;
  late MockRemoveComposerCacheByIdOnWebInteractor mockRemoveComposerCacheByIdOnWebInteractor;
  late MockSaveComposerCacheOnWebInteractor mockSaveComposerCacheOnWebInteractor;
  late MockDownloadImageAsBase64Interactor mockDownloadImageAsBase64Interactor;
  late MockTransformHtmlEmailContentInteractor mockTransformHtmlEmailContentInteractor;
  late MockGetServerSettingInteractor mockGetServerSettingInteractor;
  late MockCreateNewAndSendEmailInteractor mockCreateNewAndSendEmailInteractor;
  late MockCreateNewAndSaveEmailToDraftsInteractor mockCreateNewAndSaveEmailToDraftsInteractor;
  late MockPrintEmailInteractor mockPrintEmailInteractor;
  late MockComposerRepository mockComposerRepository;
  late MockSaveTemplateEmailInteractor mockSaveTemplateEmailInteractor;

  // Declaration Getx dependencies
  final mockMailboxDashBoardController = MockMailboxDashBoardController();
  final mockNetworkConnectionController = MockNetworkConnectionController();
  final mockBeforeReconnectManager = MockBeforeReconnectManager();
  final mockRichTextMobileTabletController = MockRichTextMobileTabletController();
  final mockRichTextWebController = MockRichTextWebController();
  final mockCacheExceptionThrower = MockCacheExceptionThrower();

  // Declaration misc dependencies
  late MockHtmlEditorApi mockHtmlEditorApi;

  setUp(() {
    Get.testMode = true;

    // Mock base controller
    mockCachingManager = MockCachingManager();
    mockLanguageCacheManager = MockLanguageCacheManager();
    mockAuthorizationInterceptors = MockAuthorizationInterceptors();
    mockDynamicUrlInterceptors = MockDynamicUrlInterceptors();
    mockDeleteCredentialInteractor = MockDeleteCredentialInteractor();
    mockLogoutOidcInteractor = MockLogoutOidcInteractor();
    mockDeleteAuthorityOidcInteractor = MockDeleteAuthorityOidcInteractor();
    mockAppToast = MockAppToast();
    mockUuid = MockUuid();
    mockToastManager = MockToastManager();
    mockTwakeAppManager = MockTwakeAppManager();

    Get.put<CachingManager>(mockCachingManager);
    Get.put<LanguageCacheManager>(mockLanguageCacheManager);
    Get.put<AuthorizationInterceptors>(mockAuthorizationInterceptors);
    Get.put<AuthorizationInterceptors>(
      mockAuthorizationInterceptors,
      tag: BindingTag.isolateTag,
    );
    Get.put<DynamicUrlInterceptors>(mockDynamicUrlInterceptors);
    Get.put<DeleteCredentialInteractor>(mockDeleteCredentialInteractor);
    Get.put<LogoutOidcInteractor>(mockLogoutOidcInteractor);
    Get.put<DeleteAuthorityOidcInteractor>(mockDeleteAuthorityOidcInteractor);
    Get.put<AppToast>(mockAppToast);
    Get.put<ImagePaths>(ImagePaths());
    Get.put<ResponsiveUtils>(ResponsiveUtils());
    Get.put<Uuid>(mockUuid);
    Get.put<ToastManager>(mockToastManager);
    Get.put<TwakeAppManager>(mockTwakeAppManager);

    // Mock Getx controllers
    Get.put<MailboxDashBoardController>(mockMailboxDashBoardController);
    Get.put<NetworkConnectionController>(mockNetworkConnectionController);
    Get.put<BeforeReconnectManager>(mockBeforeReconnectManager);
    Get.put<RichTextMobileTabletController>(mockRichTextMobileTabletController);
    Get.put<CacheExceptionThrower>(mockCacheExceptionThrower);

    // Mock composer controller
    mockLocalFilePickerInteractor = MockLocalFilePickerInteractor();
    mockLocalImagePickerInteractor = MockLocalImagePickerInteractor();
    mockGetEmailContentInteractor = MockGetEmailContentInteractor();
    mockGetAllIdentitiesInteractor = MockGetAllIdentitiesInteractor();
    mockUploadController = MockUploadController();
    mockRemoveComposerCacheByIdOnWebInteractor = MockRemoveComposerCacheByIdOnWebInteractor();
    mockSaveComposerCacheOnWebInteractor = MockSaveComposerCacheOnWebInteractor();
    mockDownloadImageAsBase64Interactor = MockDownloadImageAsBase64Interactor();
    mockTransformHtmlEmailContentInteractor = MockTransformHtmlEmailContentInteractor();
    mockGetServerSettingInteractor = MockGetServerSettingInteractor();
    mockCreateNewAndSendEmailInteractor = MockCreateNewAndSendEmailInteractor();
    mockCreateNewAndSaveEmailToDraftsInteractor = MockCreateNewAndSaveEmailToDraftsInteractor();
    mockPrintEmailInteractor = MockPrintEmailInteractor();
    mockComposerRepository = MockComposerRepository();
    mockSaveTemplateEmailInteractor = MockSaveTemplateEmailInteractor();

    composerController = ComposerController(
      mockLocalFilePickerInteractor,
      mockLocalImagePickerInteractor,
      mockGetEmailContentInteractor,
      mockGetAllIdentitiesInteractor,
      mockUploadController,
      mockRemoveComposerCacheByIdOnWebInteractor,
      mockSaveComposerCacheOnWebInteractor,
      mockDownloadImageAsBase64Interactor,
      mockTransformHtmlEmailContentInteractor,
      mockGetServerSettingInteractor,
      mockCreateNewAndSendEmailInteractor,
      mockCreateNewAndSaveEmailToDraftsInteractor,
      mockPrintEmailInteractor,
      mockComposerRepository,
      mockSaveTemplateEmailInteractor,
    );

    mockHtmlEditorApi = MockHtmlEditorApi();
  });

  tearDown(() {
    Get.reset();
    composerController = null;
  });
  
  group('ComposerController test:', () {
    group('hash draft email test:', () {
      const emailContent = 'some email content';
      const emailSubject = 'some email subject';
      final toRecipient = EmailAddress('to', 'to@linagora.com');
      final ccRecipient = EmailAddress('cc', 'cc@linagora.com');
      final bccRecipient = EmailAddress('bcc', 'bcc@linagora.com');
      final replyToRecipient = EmailAddress('replyTo', 'replyTo@linagora.com');
      final identity = Identity();
      final attachment = Attachment();
      const alwaysReadReceiptEnabled = false;
      const isMarkAsImportant = false;

      group('email action type is EmailActionType.compose:', () {
        test(
          'Should update _savedEmailDraftHash\n'
          'When screenDisplayMode is normal',
        () async {
          // arrange
          final composerArguments = ComposerArguments(
            emailActionType: EmailActionType.compose,
            displayMode: ScreenDisplayMode.normal,
            identities: [identity],
            selectedIdentityId: identity.id,
          );
          composerController?.composerArguments.value = composerArguments;
          composerController?.richTextMobileTabletController = mockRichTextMobileTabletController;
          composerController?.subjectEmail.value = emailSubject;
          composerController?.listToEmailAddress = [toRecipient];
          composerController?.listCcEmailAddress = [ccRecipient];
          composerController?.listBccEmailAddress = [bccRecipient];
          composerController?.listReplyToEmailAddress = [replyToRecipient];
          composerController?.hasRequestReadReceipt.value = alwaysReadReceiptEnabled;
          composerController?.isMarkAsImportant.value = isMarkAsImportant;
          composerController?.screenDisplayMode.value = composerArguments.displayMode;
          composerController?.currentEmailActionType = composerArguments.emailActionType;
          composerController?.listFromIdentities.value = composerArguments.identities!;

          when(mockRichTextMobileTabletController.htmlEditorApi).thenReturn(mockHtmlEditorApi);
          when(mockHtmlEditorApi.getText()).thenAnswer((_) async => emailContent);
          when(mockUploadController.attachmentsUploaded).thenReturn([attachment]);
          when(mockComposerRepository.removeCollapsedExpandedSignatureEffect(
            emailContent: anyNamed('emailContent'),
          )).thenAnswer((_) async => emailContent);

          final savedEmailDraft = SavedComposingEmail(
            content: emailContent,
            subject: emailSubject,
            toRecipients: {toRecipient},
            ccRecipients: {ccRecipient},
            bccRecipients: {bccRecipient},
            replyToRecipients: {replyToRecipient},
            identity: identity,
            attachments: [attachment],
            hasReadReceipt: alwaysReadReceiptEnabled,
            isMarkAsImportant: isMarkAsImportant,
          );
          
          // act
          composerController?.setupSelectedIdentity();

          await untilCalled(mockHtmlEditorApi.onDocumentChanged());
          await untilCalled(mockHtmlEditorApi.getText());
          await untilCalled(
              mockComposerRepository.removeCollapsedExpandedSignatureEffect(
                  emailContent: anyNamed('emailContent')));

          // assert
          expect(
            composerController?.savedEmailDraftHash,
            equals(savedEmailDraft.asString().hashCode),
          );
        });

        test(
          'Should update _savedEmailDraftHash\n'
          'When screenDisplayMode is minimize',
        () async {
          // arrange
          final composerArguments = ComposerArguments(
            emailActionType: EmailActionType.compose,
            displayMode: ScreenDisplayMode.minimize,
            identities: [identity],
            selectedIdentityId: identity.id,
          );
          composerController?.composerArguments.value = composerArguments;
          composerController?.richTextMobileTabletController = mockRichTextMobileTabletController;
          composerController?.subjectEmail.value = emailSubject;
          composerController?.listToEmailAddress = [toRecipient];
          composerController?.listCcEmailAddress = [ccRecipient];
          composerController?.listBccEmailAddress = [bccRecipient];
          composerController?.listReplyToEmailAddress = [replyToRecipient];
          composerController?.hasRequestReadReceipt.value = alwaysReadReceiptEnabled;
          composerController?.isMarkAsImportant.value = isMarkAsImportant;
          composerController?.screenDisplayMode.value = composerArguments.displayMode;
          composerController?.currentEmailActionType = composerArguments.emailActionType;
          composerController?.listFromIdentities.value = composerArguments.identities!;

          when(mockRichTextMobileTabletController.htmlEditorApi).thenReturn(mockHtmlEditorApi);
          when(mockHtmlEditorApi.getText()).thenAnswer((_) async => emailContent);
          when(mockUploadController.attachmentsUploaded).thenReturn([attachment]);
          when(mockComposerRepository.removeCollapsedExpandedSignatureEffect(
            emailContent: anyNamed('emailContent'),
          )).thenAnswer((_) async => emailContent);

          final savedEmailDraft = SavedComposingEmail(
            content: emailContent,
            subject: emailSubject,
            toRecipients: {toRecipient},
            ccRecipients: {ccRecipient},
            bccRecipients: {bccRecipient},
            replyToRecipients: {replyToRecipient},
            identity: identity,
            attachments: [attachment],
            hasReadReceipt: alwaysReadReceiptEnabled,
            isMarkAsImportant: isMarkAsImportant,
          );

          // act
          composerController?.setupSelectedIdentityWithoutApplySignature();

          await untilCalled(mockHtmlEditorApi.getText());
          await untilCalled(
              mockComposerRepository.removeCollapsedExpandedSignatureEffect(
                  emailContent: anyNamed('emailContent')));

          // assert
          expect(
            composerController?.savedEmailDraftHash,
            equals(savedEmailDraft.asString().hashCode),
          );
        });

        testWidgets(
          'Should update _savedEmailDraftHash\n'
          'When user click save draft button\n'
          'And SaveEmailAsDraftsSuccess is returned',
        (tester) async {
          await tester.runAsync(() async {
            // arrange
            PlatformInfo.isTestingForWeb = true;
            InAppWebViewPlatform.instance = MockWebViewPlatform();

            when(mockUploadController.uploadInlineViewState).thenReturn(
              Rx(Right(UIState.idle)));
            when(mockUploadController.listUploadAttachments).thenReturn(
              RxList<UploadFileState>());
            
            Get.put(composerController!);
            composerController?.richTextWebController = mockRichTextWebController;

            composerController?.setTextEditorWeb(emailContent);
            composerController?.subjectEmail.value = emailSubject;
            composerController?.listToEmailAddress = [toRecipient];
            composerController?.listCcEmailAddress = [ccRecipient];
            composerController?.listBccEmailAddress = [bccRecipient];
            composerController?.listReplyToEmailAddress = [replyToRecipient];
            when(mockUploadController.attachmentsUploaded).thenReturn([attachment]);
            when(mockComposerRepository.removeCollapsedExpandedSignatureEffect(
              emailContent: anyNamed('emailContent'),
            )).thenAnswer((_) async => emailContent);

            final selectedIdentity = Identity(id: IdentityId(Id('alice')));
            composerController?.identitySelected.value = selectedIdentity;
            composerController?.composerArguments.value = ComposerArguments();
            when(
              mockCreateNewAndSaveEmailToDraftsInteractor.execute(
                createEmailRequest: anyNamed('createEmailRequest'),
                cancelToken: anyNamed('cancelToken')))
              .thenAnswer((_) => Stream.value(
                Right(SaveEmailAsDraftsSuccess(EmailId(Id('123')), null))));

            final savedEmailDraft = SavedComposingEmail(
              content: emailContent,
              subject: emailSubject,
              toRecipients: {toRecipient},
              ccRecipients: {ccRecipient},
              bccRecipients: {bccRecipient},
              replyToRecipients: {replyToRecipient},
              identity: selectedIdentity,
              attachments: [attachment],
              hasReadReceipt: false,
              isMarkAsImportant: false,
            );

            await tester.pumpWidget(WidgetFixtures.makeTestableWidget(
              child: const Stack(children: [ComposerView()])));
            await tester.pump();
            
            // act
            final saveAsDraftButton = find.ancestor(
              of: find.byType(InkWell),
              matching: find.byWidgetPredicate(
                (widget) => widget is TMailButtonWidget
                  && widget.icon == ImagePaths().icSaveToDraft));
            await tester.tap(saveAsDraftButton);
            await tester.pump();
            await untilCalled(
              mockCreateNewAndSaveEmailToDraftsInteractor.execute(
                createEmailRequest: anyNamed('createEmailRequest'),
                cancelToken: anyNamed('cancelToken')));
            
            // assert
            expect(
              composerController?.savedEmailDraftHash,
              equals(savedEmailDraft.asString().hashCode),
            );

            // tear down
            PlatformInfo.isTestingForWeb = false;
          });
        });

        testWidgets(
          'Should update _savedEmailDraftHash\n'
          'When user click save draft button\n'
          'And UpdateEmailDraftsSuccess is returned',
        (tester) async {
          await tester.runAsync(() async {
            // arrange
            PlatformInfo.isTestingForWeb = true;
            InAppWebViewPlatform.instance = MockWebViewPlatform();

            when(mockUploadController.uploadInlineViewState).thenReturn(
              Rx(Right(UIState.idle)));
            when(mockUploadController.listUploadAttachments).thenReturn(
              RxList<UploadFileState>());
            
            Get.put(composerController!);
            composerController?.richTextWebController = mockRichTextWebController;

            composerController?.onChangeTextEditorWeb(emailContent);
            composerController?.subjectEmail.value = emailSubject;
            composerController?.listToEmailAddress = [toRecipient];
            composerController?.listCcEmailAddress = [ccRecipient];
            composerController?.listBccEmailAddress = [bccRecipient];
            composerController?.listReplyToEmailAddress = [replyToRecipient];
            when(mockUploadController.attachmentsUploaded).thenReturn([attachment]);
            when(mockComposerRepository.removeCollapsedExpandedSignatureEffect(
              emailContent: anyNamed('emailContent'),
            )).thenAnswer((_) async => emailContent);

            final selectedIdentity = Identity(id: IdentityId(Id('alice')));
            composerController?.identitySelected.value = selectedIdentity;
            composerController?.composerArguments.value = ComposerArguments();
            when(
              mockCreateNewAndSaveEmailToDraftsInteractor.execute(
                createEmailRequest: anyNamed('createEmailRequest'),
                cancelToken: anyNamed('cancelToken')))
              .thenAnswer((_) => Stream.value(
                Right(UpdateEmailDraftsSuccess(EmailId(Id('123'))))));

            final savedEmailDraft = SavedComposingEmail(
              content: emailContent,
              subject: emailSubject,
              toRecipients: {toRecipient},
              ccRecipients: {ccRecipient},
              bccRecipients: {bccRecipient},
              replyToRecipients: {replyToRecipient},
              identity: selectedIdentity,
              attachments: [attachment],
              hasReadReceipt: false,
              isMarkAsImportant: false,
            );

            await tester.pumpWidget(WidgetFixtures.makeTestableWidget(
              child: const Stack(children: [ComposerView()])));
            await tester.pump();
            
            // act
            final saveAsDraftButton = find.ancestor(
              of: find.byType(InkWell),
              matching: find.byWidgetPredicate(
                (widget) => widget is TMailButtonWidget
                  && widget.icon == ImagePaths().icSaveToDraft));
            await tester.tap(saveAsDraftButton);
            await tester.pump();
            await untilCalled(
              mockCreateNewAndSaveEmailToDraftsInteractor.execute(
                createEmailRequest: anyNamed('createEmailRequest'),
                cancelToken: anyNamed('cancelToken')));
            
            // assert
            expect(
              composerController?.savedEmailDraftHash,
              equals(savedEmailDraft.asString().hashCode),
            );

            // tear down
            PlatformInfo.isTestingForWeb = false;
          });
        });
      });

      group('email action type is EmailActionType.editDraft:', () {
        test(
          'Should update _savedEmailDraftHash\n'
          'When screenDisplayMode is normal',
        () async {
          // arrange
          final composerArguments = ComposerArguments(
            emailActionType: EmailActionType.editDraft,
            displayMode: ScreenDisplayMode.normal,
            identities: [identity],
            selectedIdentityId: identity.id,
          );
          composerController?.composerArguments.value = composerArguments;
          composerController?.richTextMobileTabletController = mockRichTextMobileTabletController;
          composerController?.subjectEmail.value = emailSubject;
          composerController?.listToEmailAddress = [toRecipient];
          composerController?.listCcEmailAddress = [ccRecipient];
          composerController?.listBccEmailAddress = [bccRecipient];
          composerController?.listReplyToEmailAddress = [replyToRecipient];
          composerController?.hasRequestReadReceipt.value = alwaysReadReceiptEnabled;
          composerController?.isMarkAsImportant.value = isMarkAsImportant;
          composerController?.screenDisplayMode.value = composerArguments.displayMode;
          composerController?.currentEmailActionType = composerArguments.emailActionType;
          composerController?.listFromIdentities.value = composerArguments.identities!;
          composerController?.identitySelected.value = null;

          when(mockRichTextMobileTabletController.htmlEditorApi).thenReturn(mockHtmlEditorApi);
          when(mockHtmlEditorApi.getText()).thenAnswer((_) async => emailContent);
          when(mockUploadController.attachmentsUploaded).thenReturn([attachment]);
          when(mockComposerRepository.removeCollapsedExpandedSignatureEffect(
            emailContent: anyNamed('emailContent'),
          )).thenAnswer((_) async => emailContent);

          final savedEmailDraft = SavedComposingEmail(
            content: emailContent,
            subject: emailSubject,
            toRecipients: {toRecipient},
            ccRecipients: {ccRecipient},
            bccRecipients: {bccRecipient},
            replyToRecipients: {replyToRecipient},
            identity: identity,
            attachments: [attachment],
            hasReadReceipt: alwaysReadReceiptEnabled,
            isMarkAsImportant: isMarkAsImportant,
          );

          // act
          composerController?.setupSelectedIdentity();

          await untilCalled(mockHtmlEditorApi.getText());
          await untilCalled(
              mockComposerRepository.removeCollapsedExpandedSignatureEffect(
                  emailContent: anyNamed('emailContent')));

          // assert
          expect(
            composerController?.savedEmailDraftHash,
            equals(savedEmailDraft.asString().hashCode),
          );
        });

        test(
          'Should update _savedEmailDraftHash\n'
          'When screenDisplayMode is minimize',
        () async {
          // arrange
          final composerArguments = ComposerArguments(
            emailActionType: EmailActionType.editDraft,
            displayMode: ScreenDisplayMode.minimize,
            identities: [identity],
            selectedIdentityId: identity.id,
          );
          composerController?.composerArguments.value = composerArguments;
          composerController?.richTextMobileTabletController = mockRichTextMobileTabletController;
          composerController?.subjectEmail.value = emailSubject;
          composerController?.listToEmailAddress = [toRecipient];
          composerController?.listCcEmailAddress = [ccRecipient];
          composerController?.listBccEmailAddress = [bccRecipient];
          composerController?.listReplyToEmailAddress = [replyToRecipient];
          composerController?.hasRequestReadReceipt.value = alwaysReadReceiptEnabled;
          composerController?.isMarkAsImportant.value = isMarkAsImportant;
          composerController?.screenDisplayMode.value = composerArguments.displayMode;
          composerController?.currentEmailActionType = composerArguments.emailActionType;
          composerController?.listFromIdentities.value = composerArguments.identities!;
          composerController?.identitySelected.value = null;

          when(mockRichTextMobileTabletController.htmlEditorApi).thenReturn(mockHtmlEditorApi);
          when(mockHtmlEditorApi.getText()).thenAnswer((_) async => emailContent);
          when(mockUploadController.attachmentsUploaded).thenReturn([attachment]);
          when(mockComposerRepository.removeCollapsedExpandedSignatureEffect(
            emailContent: anyNamed('emailContent'),
          )).thenAnswer((_) async => emailContent);

          final savedEmailDraft = SavedComposingEmail(
            content: emailContent,
            subject: emailSubject,
            toRecipients: {toRecipient},
            ccRecipients: {ccRecipient},
            bccRecipients: {bccRecipient},
            replyToRecipients: {replyToRecipient},
            identity: identity,
            attachments: [attachment],
            hasReadReceipt: alwaysReadReceiptEnabled,
            isMarkAsImportant: isMarkAsImportant,
          );

          // act
          composerController?.setupSelectedIdentityWithoutApplySignature();

          await untilCalled(mockHtmlEditorApi.getText());
          await untilCalled(
              mockComposerRepository.removeCollapsedExpandedSignatureEffect(
                  emailContent: anyNamed('emailContent')));

          // assert
          expect(
            composerController?.savedEmailDraftHash,
            equals(savedEmailDraft.asString().hashCode),
          );
        });
      });
    });
  });
}