import {
  ChatMessageAttachment,
  GetMessagesResult,
  GetUserParams,
  ListUserResponse,
  QBBlobCreate,
  QBBlobCreateUploadParams,
  QBChatDialog,
  QBChatMessage,
  QBChatXMPPMessage,
  QBGetDialogResult,
  QBLoginParams,
  QBSession,
  QBSystemMessage,
  QBUser,
  AIChatHistory,
  AIAnswerResponse,
} from 'quickblox/quickblox';
import { RemoteDialogDTO } from '../../dto/dialog/RemoteDialogDTO';
import {
  INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
  INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
  NOT_FOUND_REMOTE_DATASOURCE_EXCEPTION_CODE,
  NOT_FOUND_REMOTE_DATASOURCE_EXCEPTION_MESSAGE,
  RemoteDataSourceException,
} from '../exception/RemoteDataSourceException';
import { RemoteDialogsDTO } from '../../dto/dialog/RemoteDialogsDTO';
import { RemoteMessageDTO } from '../../dto/message/RemoteMessageDTO';
import { RemoteUserDTO } from '../../dto/user/RemoteUserDTO';
import { RemoteUsersDTO } from '../../dto/user/RemoteUsersDTO';
import { RemoteMessagesDTO } from '../../dto/message/RemoteMessagesDTO';
import { RemoteFileDTO } from '../../dto/file/RemoteFileDTO';
import { Pagination } from '../../../Domain/repository/Pagination';
import { DialogDTOMapper } from './Mapper/DialogDTOMapper';
import { IDTOMapper } from './Mapper/IDTOMapper';
import { Stubs } from '../../Stubs';
import {
  QBAnswerAssist,
  QBChatConnect,
  QBChatDisconnect,
  qbChatGetMessagesExtended,
  QBChatMarkMessageDelivered,
  QBChatMarkMessageRead,
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  // QBChatMarkMessageRead,
  QBChatSendMessage,
  QBChatSendSystemMessage,
  QBCreateAndUploadContent,
  QBCreateGroupDialog,
  QBCreatePrivateDialog,
  QBCreateSession,
  QBDeleteDialog,
  QBGetDialogById,
  QBGetDialogs,
  QBInit,
  QBInitParams,
  QBJoinGroupDialog,
  QBLogout,
  QBSendIsStopTypingStatus,
  QBSendIsTypingStatus,
  QBTranslate,
  QBUpdateDialog,
  QBUsersGet,
  QBUsersGetById,
  setQB,
  getQB,
  QBCreateGroupDialogWithAutojoinFalse,
  QBLeaveDialog,
} from '../../../qb-api-calls';
import { UserDTOMapper } from './Mapper/UserDTOMapper';
import { MessageDTOMapper } from './Mapper/MessageDTOMapper';
import { IMapper } from '../../mapper/IMapper';
import { DialogRemoteDTOMapper } from '../../mapper/DialogRemoteDTOMapper';
import { DialogEntity } from '../../../Domain/entity/DialogEntity';
import RepositoryException from '../exception/RepositoryException';
import { MapperDTOException } from '../exception/MapperDTOException';
import { stringifyError } from '../../../utils/parse';
import { DialogType } from '../../../Domain/entity/DialogTypes';
import { NotificationTypes } from '../../../Domain/entity/NotificationTypes';
import EventMessageType from '../../../Domain/entity/EventMessageType';
import { SubscriptionPerformer } from '../../../Domain/use_cases/base/Subscribable/SubscriptionPerformer';
import { CallBackFunction } from '../../../Domain/use_cases/base/IUseCase';
import { FileDTOMapper } from './Mapper/FileDTOMapper';
import { DialogEventInfo } from '../../../Domain/entity/DialogEventInfo';
import { IRemoteDataSource } from './IRemoteDataSource';
import { QBConfig } from '../../../QBconfig';
import {
  QBUIKitChatDialog,
  QBUIKitChatNewMessage,
  QBUIKitConfig,
  QBUIKitSystemMessage,
} from '../../../CommonTypes/CommonTypes';

export type PaginatedDTOResult = {
  PaginationResult: Pagination;
  ResultData: RemoteUserDTO[];
};

export type LoginData = {
  login: string;
  password: string;
};
export type AuthorizationData = {
  sessionToken: string;
  userId: number;
  password: string;
  userName: string;
};

export class RemoteDataSource implements IRemoteDataSource {
  private joinStatusMap: Map<string, boolean> = new Map();
  private joinCheckInterval: NodeJS.Timeout | null = null;
  private processedDialogs = new Set<string>();
  private userCache: Map<string, RemoteUserDTO> = new Map();
  private userCachePreloadInterval: NodeJS.Timeout | null = null;

  private userDTOMapper: IDTOMapper;

  private messageDTOMapper: IDTOMapper;

  private fileDTOMapper: IDTOMapper;

  private _needInit: boolean;

  private _authProcessed: boolean;

  get authProcessed(): boolean {
    const auth = this._authProcessed;
    const QB = getQB();
    const chatConnection = QB && QB.chat && QB.chat.isConnected;

    if (chatConnection) return true;

    return auth || chatConnection;
  }

  set authProcessed(value: boolean) {
    this._authProcessed = value;
  }

  get needInit(): boolean {
    const QB = getQB();
    const needed = this._needInit;
    const chatConnection = QB && QB.chat && QB.chat.isConnected;

    if (chatConnection) return false;

    return needed;
  }

  public setAuthProcessedSuccessed() {
    this.authProcessed = true;
    this.setInitSDKSuccessed();
  }

  public setInitSDKSuccessed() {
    this._needInit = false;
  }

  private _authInformation: AuthorizationData | undefined;

  get authInformation(): AuthorizationData | undefined {
    return this._authInformation;
  }

  set authInformation(value: AuthorizationData | undefined) {
    this._authInformation = value;
  }

  private currentDialog: RemoteDialogDTO | undefined;

  //
  getCurrentDialogDTOMapper(): IDTOMapper {
    const currentUserId: number = this._authInformation?.userId || -1;
    const dialogDTOMapper: IDTOMapper = new DialogDTOMapper(currentUserId);

    return dialogDTOMapper;
  }
  //
  private startUserCachePreload(): void {
    if (this.userCachePreloadInterval) return; // Избегаем повторного запуска

    this.userCachePreloadInterval = setInterval(async () => {
      try {
        const pagination = new Pagination(1, 100); // Пагинация по 100 пользователей
        let hasMoreUsers = true;

        while (hasMoreUsers) {
          const { ResultData, PaginationResult } = await this.getAllUsers(
            pagination,
          );

          for (const user of ResultData) {
            if (!this.userCache.has(user.id)) {
              this.userCache.set(user.id, user);
            }
          }

          if (
            PaginationResult.getCurrentPage() >= PaginationResult.totalPages
          ) {
            hasMoreUsers = false;
          } else {
            pagination.setCurrentPage(pagination.getCurrentPage() + 1);
          }
        }

        console.log(
          `User cache preload complete. Loaded ${this.userCache.size} users.`,
        );

        clearInterval(this.userCachePreloadInterval!);
        this.userCachePreloadInterval = null;
      } catch (error) {
        console.error('Error during user cache preload:', error);
      }
    }, 4500); // Интервал 10 секунд
  }

  //
  constructor() {
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    console.log('QB inside RemoteDataSource:', window.QB);
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    if (typeof window !== 'undefined' && window.QB) {
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
      setQB(window.QB); // Устанавливаем ссылку на QuickBlox SDK
    }
    this.userDTOMapper = new UserDTOMapper();
    this.messageDTOMapper = new MessageDTOMapper();
    this.fileDTOMapper = new FileDTOMapper();
    this._needInit = true;
    this._authProcessed = false;
    this.subscriptionOnSystemMessages[NotificationTypes.REMOVE_USER] =
      new SubscriptionPerformer<RemoteMessageDTO>();
    this.subscriptionOnSystemMessages[NotificationTypes.UPDATE_DIALOG] =
      new SubscriptionPerformer<RemoteMessageDTO>();
    this.subscriptionOnSystemMessages[NotificationTypes.DELETE_LEAVE_DIALOG] =
      new SubscriptionPerformer<RemoteMessageDTO>();
    this.subscriptionOnSystemMessages[NotificationTypes.NEW_DIALOG] =
      new SubscriptionPerformer<RemoteMessageDTO>();

    // this.startUserCachePreload();  //TODO: uncomment this line after test [CSAMPLES-4065] is done
  }

  // eslint-disable-next-line class-methods-use-this
  createAnswer(
    text: string,
    messages: AIChatHistory,
    smartChatAssistantId: string,
  ): Promise<AIAnswerResponse> {
    return QBAnswerAssist(smartChatAssistantId, text, messages);
  }

  // eslint-disable-next-line class-methods-use-this
  translate(
    text: string,
    languageCode: string,
    smartChatAssistantId: string,
  ): Promise<AIAnswerResponse> {
    return QBTranslate(smartChatAssistantId, text, languageCode);
  }

  async updateCurrentDialog(
    dto: RemoteDialogDTO,
    qbConfig: QBUIKitConfig,
  ): Promise<void> {
    this.currentDialog = dto;

    const dialogsDTOtoEntityMapper: IMapper = new DialogRemoteDTOMapper();
    const dialogEntity: DialogEntity = await dialogsDTOtoEntityMapper.toEntity(
      this.currentDialog,
    );

    const userId = this._authInformation?.userId || -1;
    const dialogId = this.currentDialog.id;
    const messageId = this.currentDialog.lastMessageId;

    const resultMessage: DialogEventInfo = {
      eventMessageType: EventMessageType.LocalMessage,
      dialogInfo: dialogEntity,
      messageInfo: undefined,
      messageStatus: {
        isTyping: false,
        userId,
        dialogId,
        messageId,
        deliveryStatus: 'delivered',
      },
      notificationTypes: undefined,
    };

    this.subscriptionOnMessageStatus.informSubscribers(
      resultMessage,
      EventMessageType.LocalMessage,
    );

    // Mark all messages in the dialog as read
    await this.markAllMessagesAsRead(dialogId, qbConfig);
  }

  /**
   * Marks all messages in the specified dialog as read.
   * Uses the QuickBlox API endpoint provided in qbConfig, or defaults to api.quickblox.com.
   */
  // eslint-disable-next-line class-methods-use-this
  private async markAllMessagesAsRead(
    dialogId: string,
    qbConfig: QBUIKitConfig,
  ): Promise<void> {
    try {
      const apiEndpoint =
        qbConfig.appConfig.endpoints.api || 'api.quickblox.com';
      const qbToken = qbConfig.credentials.sessionToken;

      if (!qbToken) {
        console.warn(
          'QuickBlox session token is missing. Cannot mark messages as read.',
        );

        return;
      }

      const response = await fetch(`https://${apiEndpoint}/chat/Message.json`, {
        method: 'PUT',
        headers: {
          'QB-Token': qbToken,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          chat_dialog_id: dialogId,
          read: '1',
        }),
      });

      if (!response.ok) {
        throw new Error(
          `Failed to mark messages as read. HTTP status: ${response.status}`,
        );
      }

      console.log(`All messages in dialog ${dialogId} marked as read.`);
    } catch (error) {
      console.error(
        `Error marking messages as read in dialog ${dialogId}:`,
        error,
      );
    }
  }

  public async setUpMockStorage(): Promise<void> {
    const dialogsDTOtoEntityMapper: IMapper = new DialogRemoteDTOMapper();
    const dialogsEntities = Stubs.createDialogsForTest();

    // eslint-disable-next-line no-restricted-syntax
    for (const item of dialogsEntities) {
      const dialogDTO: RemoteDialogDTO =
        // eslint-disable-next-line no-await-in-loop
        await dialogsDTOtoEntityMapper.fromEntity<
          DialogEntity,
          RemoteDialogDTO
        >(item);

      // eslint-disable-next-line @typescript-eslint/no-unused-vars,promise/catch-or-return,promise/always-return
      this.createDialog(dialogDTO)
        // eslint-disable-next-line @typescript-eslint/no-unused-vars,promise/always-return
        .then((_) => {
          console.log(`added mock item ${JSON.stringify(dialogDTO)}`);
        })
        .catch((e) => {
          console.log('EXCEPTION:', (e as RepositoryException).message);
        });
    }
  }

  // eslint-disable-next-line class-methods-use-this
  private async createUserSession(loginData: QBLoginParams) {
    const session: QBSession = await QBCreateSession(loginData);

    return session;
  }

  // eslint-disable-next-line class-methods-use-this
  public static initSDK(sdkParams: QBInitParams): boolean {
    console.log('2. call initSDK in RemoteDataSourceMock');
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    if (!window.QB) {
      throw new Error('HAVE NO SDK');
    } else {
      console.log('2. start init actions');
      QBInit({
        appIdOrToken: sdkParams.appIdOrToken,
        authKeyOrAppId: sdkParams.authKeyOrAppId,
        authSecret: sdkParams.authSecret,
        accountKey: sdkParams.accountKey,
        config: sdkParams.config,
      });
      const QB = getQB();
      const QuickBloxVersion = `CALL initData: Init SDK was success: version ${QB.version} build ${QB.buildNumber}`;

      console.log(QuickBloxVersion);

      return true;
    }
  }

  public async initSDKWithUser(
    sdkParams: QBInitParams,
    authData: LoginData,
  ): Promise<void> {
    console.log('2. call initSDK in RemoteDataSourceMock');
    if (this.needInit) {
      console.log('2. start init actions');
      QBInit({
        appIdOrToken: sdkParams.appIdOrToken,
        authKeyOrAppId: sdkParams.authKeyOrAppId,
        authSecret: sdkParams.authSecret,
        accountKey: sdkParams.accountKey,
        config: sdkParams.config,
      });
      const QB = getQB();
      const QuickBloxVersion = `CALL initData: Init SDK was success: version ${QB.version} build ${QB.buildNumber}`;

      console.log(QuickBloxVersion);
      //
      this._needInit = false;
      await this.loginWithUser(authData);
    }
  }

  public resetProcessedDialogs(): void {
    this.processedDialogs.clear();
  }

  public async disconnectAndLogoutUser() {
    if (this.authProcessed) {
      QBChatDisconnect();
      await QBLogout();
      this.authProcessed = false;
      this.releaseSubscriptions();
      this.releaseEventsHandlers();
      this.resetProcessedDialogs();
    }
  }

  releaseSubscriptions(): void {
    Object.entries(this.subscriptionOnSystemMessages).map((x) => {
      const eventName = x[0];

      return this.subscriptionOnSystemMessages[eventName].release();
    });
  }

  subscribeOnSystemMessaging(
    notificationType: NotificationTypes,
    callback: CallBackFunction<RemoteMessageDTO>,
  ): void {
    let SystemMessageType: string = NotificationTypes.NEW_DIALOG;

    switch (notificationType) {
      case NotificationTypes.REMOVE_USER:
        SystemMessageType = NotificationTypes.REMOVE_USER;
        break;
      case NotificationTypes.DELETE_LEAVE_DIALOG:
        SystemMessageType = NotificationTypes.DELETE_LEAVE_DIALOG;
        break;
      case NotificationTypes.UPDATE_DIALOG:
        SystemMessageType = NotificationTypes.UPDATE_DIALOG;
        break;
      default:
        SystemMessageType = NotificationTypes.NEW_DIALOG;
        break;
    }

    this.subscriptionOnSystemMessages[SystemMessageType].subscribe(
      callback,
      EventMessageType.SystemMessage,
    );
  }

  protected subscriptionOnSystemMessages: Dictionary<
    SubscriptionPerformer<RemoteMessageDTO>
  > = {};

  subscribeOnMessaging(callback: CallBackFunction<RemoteMessageDTO>): void {
    this.subscriptionOnChatMessages.subscribe(
      callback,
      EventMessageType.RegularMessage,
    );
  }

  protected subscriptionOnChatMessages: SubscriptionPerformer<RemoteMessageDTO> =
    new SubscriptionPerformer<RemoteMessageDTO>();

  protected subscriptionOnMessageStatus: SubscriptionPerformer<DialogEventInfo> =
    new SubscriptionPerformer<DialogEventInfo>();

  protected subscriptionOnSessionExpiredListener: SubscriptionPerformer<boolean> =
    new SubscriptionPerformer<boolean>();

  subscribeOnSessionExpiredListener(callback: CallBackFunction<boolean>): void {
    console.log('subscribe On Session Expired Event');
    this.subscriptionOnSessionExpiredListener.subscribe(
      callback,
      EventMessageType.LocalMessage,
    );
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const sessionTimeOut = QBConfig.appConfig.sessionTimeOut || 122;

    setTimeout(() => {
      console.log('NEED LOGOUT');
      if (this.authProcessed) {
        this.subscriptionOnSessionExpiredListener.informSubscribers(
          true,
          EventMessageType.LocalMessage,
        );
      }
    }, sessionTimeOut * 60 * 1000);
  }

  subscribeOnUpdateMessageStatus(
    callback: CallBackFunction<DialogEventInfo>,
  ): void {
    this.subscriptionOnMessageStatus.subscribe(
      callback,
      EventMessageType.LocalMessage,
    );
  }

  public initEventsHandlers() {
    console.log('CALL--initEventsHandlers--CALL');
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    console.log('QB inside library:', window.QB);
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    if (typeof window !== 'undefined' && window.QB) {
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
      setQB(window.QB); // Устанавливаем ссылку на QuickBlox SDK
    }
    const QB = getQB();

    QB.chat.onSystemMessageListener = (message: QBUIKitSystemMessage) => {
      console.log(`EVENT: receive system message: ${JSON.stringify(message)}`);
      const resultMessage = new RemoteMessageDTO();

      resultMessage.sender_id = message.userId;
      resultMessage.message = message.body || 'system message';
      resultMessage.notification_type =
        message.extension?.notification_type || NotificationTypes.UPDATE_DIALOG;
      resultMessage.dialogId = message?.extension?.dialog_id || '';

      this.subscriptionOnSystemMessages[
        resultMessage.notification_type!
      ].informSubscribers(resultMessage, EventMessageType.SystemMessage);
    };

    QB.chat.onMessageListener = (
      userId: number,
      message: QBChatXMPPMessage,
    ) => {
      console.log(
        `EVENT: receive  message for userid: ${userId}, message: ${stringifyError(
          message,
        )}`,
      );
      const dialogId: QBChatDialog['_id'] =
        message.dialog_id || message.extension.dialog_id || '-1';

      //
      qbChatGetMessagesExtended(dialogId, {
        _id: message.id,
        mark_as_read: 0,
      })
        // eslint-disable-next-line promise/always-return
        .then((qbMessages: GetMessagesResult) => {
          ///
          qbMessages.items.map(async (currentItem) => {
            const dtoMessage: RemoteMessageDTO =
              await this.messageDTOMapper.toTDO<
                QBChatMessage,
                RemoteMessageDTO
              >(currentItem);

            dtoMessage.dialogId = dialogId;
            this.subscriptionOnChatMessages.informSubscribers(
              dtoMessage,
              EventMessageType.RegularMessage,
            );
            //
            if (this.currentDialog?.id === dialogId) {
              QBChatMarkMessageRead({
                messageId: message.id,
                dialogId,
                userId: dtoMessage.sender_id,
              });
            }

            ///
            return dtoMessage;
          });
        })
        .catch();
      //
      QBGetDialogById(dialogId)
        .then(async (result) => {
          //
          const dialogs: QBChatDialog[] | undefined = result?.items.filter(
            (v) => v._id === dialogId,
          );
          const current =
            dialogs && dialogs.length > 0 ? dialogs[0] : undefined;
          //
          const dialogDTO: RemoteDialogDTO =
            await this.getCurrentDialogDTOMapper().toTDO(current);
          const dialogsDTOtoEntityMapper: IMapper = new DialogRemoteDTOMapper();

          const dialogEntity: DialogEntity =
            await dialogsDTOtoEntityMapper.toEntity(dialogDTO);
          //
          //
          const resultEvent: DialogEventInfo = {
            eventMessageType: EventMessageType.LocalMessage,
            dialogInfo: dialogEntity,
            messageInfo: undefined,
            messageStatus: {
              isTyping: false,
              userId,
              dialogId,
              messageId: message.id,
              deliveryStatus: 'read',
            },
            notificationTypes: undefined,
          };

          this.subscriptionOnMessageStatus.informSubscribers(
            resultEvent,
            EventMessageType.LocalMessage,
          );

          return resultEvent;
        })
        .catch();
      //
    };
    QB.chat.onDeliveredStatusListener = (messageId, dialogId, userId) => {
      console.log(
        `EVENT: receive delivered message id: ${messageId}, dialogid: ${dialogId} userid: ${userId}`,
      );

      QBGetDialogById(dialogId)
        .then(async (result) => {
          //
          // eslint-disable-next-line @typescript-eslint/no-unused-vars

          const dialogs: QBChatDialog[] | undefined = result?.items.filter(
            (v) => v._id === dialogId,
          );
          const current =
            dialogs && dialogs.length > 0 ? dialogs[0] : undefined;
          //
          const dialogDTO: RemoteDialogDTO =
            await this.getCurrentDialogDTOMapper().toTDO(current);
          const dialogsDTOtoEntityMapper: IMapper = new DialogRemoteDTOMapper();

          const dialogEntity: DialogEntity =
            await dialogsDTOtoEntityMapper.toEntity(dialogDTO);
          //
          //
          const resultMessage: DialogEventInfo = {
            eventMessageType: EventMessageType.LocalMessage,
            dialogInfo: dialogEntity,
            messageInfo: undefined,
            messageStatus: {
              isTyping: false,
              userId,
              dialogId,
              messageId,
              deliveryStatus: 'delivered',
            },
            notificationTypes: undefined,
          };

          this.subscriptionOnMessageStatus.informSubscribers(
            resultMessage,
            EventMessageType.LocalMessage,
          );
          //
          //
          // eslint-disable-next-line promise/always-return
          if (current && current.type === DialogType.private) {
            qbChatGetMessagesExtended(dialogId, {
              _id: messageId,
              mark_as_read: 0,
            })
              // eslint-disable-next-line promise/always-return
              .then((qbMessages: GetMessagesResult) => {
                ///
                qbMessages.items.map(async (currentItem) => {
                  const dtoMessage: RemoteMessageDTO =
                    await this.messageDTOMapper.toTDO<
                      QBChatMessage,
                      RemoteMessageDTO
                    >(currentItem);

                  dtoMessage.dialogId = dialogId;
                  this.subscriptionOnChatMessages.informSubscribers(
                    dtoMessage,
                    EventMessageType.RegularMessage,
                  );
                  //
                  if (this.currentDialog?.id === dialogId) {
                    QBChatMarkMessageRead({
                      messageId,
                      dialogId,
                      userId: dtoMessage.sender_id,
                    });
                  }

                  return dtoMessage;
                });

                ///
              })
              .catch();
            //
          }
        })
        .catch();
    };
    QB.chat.onMessageTypingListener = (isTyping, userId, dialogId) => {
      console.log(
        `EVENT: receive typing: ${
          isTyping ? 'true' : 'false'
        }, dialog id: ${dialogId} userid: ${userId}`,
      );
      if (this.authProcessed && this.authInformation?.userId === userId) return;

      const resultMessage: DialogEventInfo = {
        eventMessageType: EventMessageType.LocalMessage,
        messageInfo: undefined,
        dialogInfo: undefined,
        messageStatus: {
          isTyping,
          userId,
          dialogId,
          messageId: '',
          deliveryStatus: 'sending',
        },
        notificationTypes: undefined,
      };

      this.subscriptionOnMessageStatus.informSubscribers(
        resultMessage,
        EventMessageType.LocalMessage,
      );
    };
    QB.chat.onSessionExpiredListener = (error) => {
      console.log('call  onSessionExpiredListener');
      console.timeLog('onSessionExpiredListener');
      let result = true;

      if (error) {
        console.log(
          'EVENT:onSessionExpiredListener - error: ',
          stringifyError(error),
        );
        result = false;
      } else {
        console.log(
          'EVENT: Hello from SessionExpiredListener. session is expired.',
        );
        result = true;
      }
      this.subscriptionOnSessionExpiredListener.informSubscribers(
        result,
        EventMessageType.LocalMessage,
      );
    };
    QB.chat.onDisconnectedListener = () => {
      console.log('EVENT: disconnect');
    };
    QB.chat.onReconnectListener = () => {
      console.log('EVENT: reconnect');
    };
    QB.chat.onMessageErrorListener = (messageId: string, error: unknown) => {
      console.log(
        `EVENT: receive error message id: ${messageId}, error: ${stringifyError(
          error,
        )}, current auth status is: ${this.authProcessed ? 'true' : 'false'}`,
      );
    };

    QB.chat.onReadStatusListener = (messageId, dialogId, userId) => {
      console.log(
        `EVENT: receive read message id: ${messageId}, dialogid: ${dialogId} userid: ${userId}`,
      );
      const resultMessage: DialogEventInfo = {
        eventMessageType: EventMessageType.LocalMessage,
        messageInfo: undefined,
        dialogInfo: undefined,
        messageStatus: {
          isTyping: false,
          userId,
          dialogId,
          messageId,
          deliveryStatus: 'read',
        },
        notificationTypes: undefined,
      };

      this.subscriptionOnMessageStatus.informSubscribers(
        resultMessage,
        EventMessageType.LocalMessage,
      );
    };

    //
  }

  // eslint-disable-next-line class-methods-use-this
  public releaseEventsHandlers() {
    const QB = getQB();

    QB.chat.onSessionExpiredListener = undefined;
    QB.chat.onReconnectListener = undefined;
    QB.chat.onDisconnectedListener = undefined;
    QB.chat.onSystemMessageListener = undefined;
    QB.chat.onMessageListener = undefined;
    QB.chat.onMessageErrorListener = undefined;
    QB.chat.onDeliveredStatusListener = undefined;
    QB.chat.onReadStatusListener = undefined;
    QB.chat.onMessageTypingListener = undefined;
  }

  public async loginWithUser(authParams: LoginData) {
    if (this.needInit) return;
    if (this.authProcessed) return;
    //
    console.log('CALL createUserSession....');
    const userRequiredParams = {
      login: authParams.login,
      password: authParams.password,
    };
    const sessionResult = await this.createUserSession(
      userRequiredParams,
    ).catch((e) => {
      this._needInit = true;
      this.authProcessed = false;
      console.log(
        `EXCEPTION IN CREATE USER SESSION (${
          userRequiredParams.login
        }) : ${stringifyError(e)}`,
      );
    });

    if (!sessionResult) return;
    //
    //
    this.initEventsHandlers();

    const session = sessionResult;
    const sessionDataTmp = JSON.stringify(session);

    const sessionData = JSON.parse(sessionDataTmp);

    const authInformation = {
      userName: authParams.login,
      userId: sessionData.user_id,
      sessionToken: sessionData.token,
      password: sessionData.token,
    };

    const paramsConnect = {
      userId: authInformation.userId,
      password: authInformation.password,
    };

    const connectResult = await QBChatConnect(paramsConnect);

    if (connectResult) {
      //
      this._authInformation = authInformation;
      this.setAuthProcessedSuccessed();
    } else {
      console.log('could not connect to chat');
    }

    //
  }
  //get dialogs flow

  private async joinGroupDialog(dialogId: string): Promise<void> {
    // try {
    //   await QBJoinGroupDialog(dialogId).catch(() => {
    //     this.joinStatusMap.set(dialogId, false);
    //     throw new RemoteDataSourceException(
    //       INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
    //       INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
    //     );
    //   });
    //   this.joinStatusMap.set(dialogId, true);
    // } catch (reason) {
    //   console.log(`QBJoinGroupDialog error for dialog ${dialogId}:`, reason);
    //   this.joinStatusMap.set(dialogId, false);
    // }
  }

  public async joinDialog(dialogDto: RemoteDialogDTO): Promise<void> {
    const dialogId = dialogDto.id;
    await QBJoinGroupDialog(dialogId).catch(() => {
      this.joinStatusMap.set(dialogId, false);
      throw new RemoteDataSourceException(
        INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
        INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
      );
    });
  }

  //version 0.5.0-beta.11,12,14
  private startJoinRetryProcess(): void {
    if (this.joinCheckInterval) return;

    this.joinCheckInterval = setInterval(async () => {
      const dialogsToRetry = Array.from(this.joinStatusMap.entries())
        .filter(([_, isJoined]) => !isJoined)
        .map(([id]) => id);

      if (dialogsToRetry.length === 0) {
        clearInterval(this.joinCheckInterval!);
        this.joinCheckInterval = null;
        console.log('All dialogs successfully joined. Stopping retry process.');
        return;
      }

      console.log(`Retrying join for ${dialogsToRetry.length} dialogs...`);
      await this.processDialogs(dialogsToRetry);
    }, 7000);
  }

  private async processDialogsInBackground(dialogIds: string[]): Promise<void> {
    const BATCH_SIZE = 12;

    console.log(
      `Starting background processing for ${dialogIds.length} dialogs`,
    );

    for (let i = 0; i < dialogIds.length; i += BATCH_SIZE) {
      const batch = dialogIds.slice(i, i + BATCH_SIZE);

      console.log(`Processing batch ${i / BATCH_SIZE + 1}:`, batch);

      await Promise.all(batch.map((id) => this.joinGroupDialog(id)));
      console.log(`Batch ${i / BATCH_SIZE + 1} completed.`);

      await new Promise((resolve) => setTimeout(resolve, 1500));
    }

    console.log('Background processing complete.');
  }

  private async processDialogs(dialogIds: string[]): Promise<void> {
    await Promise.all(dialogIds.map((id) => this.joinGroupDialog(id)));
  }

  async getDialogs(pagination?: Pagination): Promise<RemoteDialogsDTO> {
    let currentPagination: Pagination = pagination || new Pagination();

    const pageNumber = currentPagination.getCurrentPage() || 1;
    const perPage = currentPagination.perPage || 100;

    const params: Dictionary<any> = {
      created_at: { lt: Date.now() / 1000 },
      sort_desc: 'updated_at',
      page: pageNumber,
      limit: perPage,
      // type: { in: [2, 3] },
    };

    // Conditionally filter dialog types unless public dialogs should be shown
    if (!QBConfig.appConfig?.showPublicDialogsInList) {
      params.type = {
        in: [2, 3], // Only group and private dialogs
      };
    }

    const dialogsDTO: Array<RemoteDialogDTO> = [];
    const joinDialogIds: string[] = [];
    const qbDialogs: QBGetDialogResult | undefined = await QBGetDialogs(params);

    if (qbDialogs) {
      //LOGS
      console.log('call getDialogs:');
      console.log('Total dialogs:', qbDialogs.total_entries);
      console.log('Dialogs:');
      // @ts-ignore
      qbDialogs.items.forEach((dialog) => {
        console.log(`- ID: ${dialog._id}`);
        console.log(`  Name: ${dialog.name}`);
        console.log(`  Type: ${dialog.type}`);
        console.log(`  Occupants: ${dialog.occupants_ids.join(', ')}`);
        console.log(`  Last message: ${dialog.last_message || 'No message'}`);
        console.log(`  Unread messages: ${dialog.unread_messages_count ?? 0}`);
        console.log(`  is_join_required: ${dialog.is_join_required ?? 'N/A'}`);
        console.log('--------------------');
      });
      console.log('Dialogs resume table:');
      console.table(
        // @ts-ignore
        qbDialogs.items.map((d) => ({
          ID: d._id,
          Name: d.name,
          Type: d.type,
          Occupants: d.occupants_ids.length,
          LastMessage: d.last_message,
          Unread: d.unread_messages_count ?? 0,
          is_join_required: d.is_join_required ?? 'N/A',
        }))
      );

      //
      currentPagination = new Pagination(0, perPage);
      currentPagination.totalPages = qbDialogs.total_entries / perPage;
      currentPagination.setCurrentPage(pageNumber);

      for (const item of qbDialogs.items) {
        const qbEntity: QBChatDialog = item;
        if (!qbEntity) continue;

        const newDTO: RemoteDialogDTO =
          await this.getCurrentDialogDTOMapper().toTDO(qbEntity);

        if (
          newDTO.type === DialogType.group &&
          !this.processedDialogs.has(newDTO.id)
        ) {
          this.processedDialogs.add(newDTO.id);

          if (!this.joinStatusMap.has(newDTO.id)) {
            this.joinStatusMap.set(newDTO.id, false);
            joinDialogIds.push(newDTO.id);
          }
        }

        dialogsDTO.push(newDTO);
      }

      const firstBatch = joinDialogIds.slice(0, 24);
      await this.processDialogs(firstBatch);

      const remainingDialogs = joinDialogIds.slice(24);
      if (remainingDialogs.length) {
        await this.processDialogsInBackground(remainingDialogs);
        this.startJoinRetryProcess();
      }
    } else {
      console.log('No dialogs retrieved');
    }

    return Promise.resolve(new RemoteDialogsDTO(dialogsDTO, currentPagination));
  }

  //tested in 0.5.0-beta.10
  // private async processDialogs(dialogIds: string[]): Promise<void> {
  //   await Promise.all(dialogIds.map((id) => this.joinGroupDialog(id)));
  // }
  //
  // private async processDialogsInBackground(dialogIds: string[]): Promise<void> {
  //   const BATCH_SIZE = 12;
  //
  //   console.log(`Starting background processing for ${dialogIds.length} dialogs`);
  //
  //   for (let i = 0; i < dialogIds.length; i += BATCH_SIZE) {
  //     const batch = dialogIds.slice(i, i + BATCH_SIZE);
  //
  //     console.log(`Processing batch ${i / BATCH_SIZE + 1}:`, batch);
  //
  //     await Promise.all(batch.map((id) => this.joinGroupDialog(id)));
  //     console.log(`Batch ${i / BATCH_SIZE + 1} completed.`);
  //
  //     await new Promise((resolve) => setTimeout(resolve, 1500));
  //   }
  //
  //   console.log('Background processing complete.');
  // }
  //
  // private async joinGroupDialog(dialogId: string): Promise<void> {
  //   try {
  //     await QBJoinGroupDialog(dialogId);
  //   } catch (reason) {
  //     console.log(`QBJoinGroupDialog error for dialog ${dialogId}:`, reason);
  //   }
  // }
  //
  // async getDialogs(pagination?: Pagination): Promise<RemoteDialogsDTO> {
  //   let currentPagination: Pagination = pagination || new Pagination();
  //
  //   console.log(
  //     'call getDialogs in RemoteDataSourceMock param pagination: ',
  //     JSON.stringify(pagination)
  //   );
  //   console.log(
  //     'call getDialogs in RemoteDataSourceMock with result pagination: ',
  //     JSON.stringify(currentPagination)
  //   );
  //
  //   const pageNumber = currentPagination.getCurrentPage() || 1;
  //   console.log('pageNumber: ', pageNumber);
  //   const perPage = currentPagination.perPage || 100;
  //   console.log('perPage: ', perPage);
  //
  //   const params = {
  //     created_at: { lt: Date.now() / 1000 },
  //     sort_desc: 'updated_at',
  //     page: pageNumber,
  //     limit: perPage,
  //     type: { in: [2, 3] },
  //   };
  //
  //   console.log(
  //     'call getDialogs in RemoteDataSourceMock with params: ',
  //     JSON.stringify(params)
  //   );
  //
  //   const dialogsDTO: Array<RemoteDialogDTO> = [];
  //   const joinDialogIds: string[] = [];
  //   const qbDialogs: QBGetDialogResult | undefined = await QBGetDialogs(params);
  //
  //   if (qbDialogs) {
  //     console.log(
  //       `request completed. have: total_entries ${
  //         qbDialogs.total_entries
  //       }, limit: ${qbDialogs.limit}, skip: ${
  //         qbDialogs.skip
  //       } items: ${JSON.stringify(qbDialogs.items)}`
  //     );
  //
  //     currentPagination = new Pagination(0, perPage);
  //     currentPagination.totalPages = qbDialogs.total_entries / perPage;
  //     currentPagination.setCurrentPage(pageNumber);
  //
  //     for (const item of qbDialogs.items) {
  //       const qbEntity: QBChatDialog = item;
  //       if (!qbEntity) continue;
  //
  //       const newDTO: RemoteDialogDTO = await this.getCurrentDialogDTOMapper().toTDO(qbEntity);
  //
  //       if (newDTO.type === DialogType.group && !this.processedDialogs.has(newDTO.id)) {
  //         this.processedDialogs.add(newDTO.id);
  //         joinDialogIds.push(newDTO.id);
  //       }
  //
  //       dialogsDTO.push(newDTO);
  //     }
  //
  //     const firstBatch = joinDialogIds.slice(0, 12);
  //     await this.processDialogs(firstBatch);
  //
  //     const remainingDialogs = joinDialogIds.slice(12);
  //     if (remainingDialogs.length) {
  //       this.processDialogsInBackground(remainingDialogs).then();
  //     }
  //   }
  //
  //   return Promise.resolve(new RemoteDialogsDTO(dialogsDTO, currentPagination));
  // }

  // original version 0.4.5
  // async getDialogs(pagination?: Pagination): Promise<RemoteDialogsDTO> {
  //   let currentPagination: Pagination = pagination || new Pagination();
  //
  //   console.log(
  //     'call getDialogs in RemoteDataSourceMock param pagination: ',
  //     JSON.stringify(pagination),
  //   );
  //   console.log(
  //     'call getDialogs in RemoteDataSourceMock with result pagination: ',
  //     JSON.stringify(currentPagination),
  //   );
  //   const pageNumber = currentPagination.getCurrentPage() || 1;
  //
  //   console.log('pageNumber: ', pageNumber);
  //   const perPage = currentPagination.perPage || 100;
  //
  //   console.log('perPage: ', perPage);
  //   const params = {
  //     created_at: {
  //       lt: Date.now() / 1000,
  //     },
  //     // sort_desc: 'created_at',// artan 19.0.2024
  //     sort_desc: 'updated_at',
  //     page: pageNumber,
  //     // per_page: perPage,
  //     limit: perPage,
  //     type: {
  //       in: [2, 3],
  //     },
  //   };
  //   // filter['type[in]'] = [3, 2].join(',');
  //
  //   console.log(
  //     'call getDialogs in RemoteDataSourceMock with params: ',
  //     JSON.stringify(params),
  //   );
  //   const dialogsDTO: Array<RemoteDialogDTO> = [];
  //   const qbDialogs: QBGetDialogResult | undefined = await QBGetDialogs(params);
  //
  //   if (qbDialogs) {
  //     console.log(
  //       `request completed. have: total_entries ${
  //         qbDialogs.total_entries
  //       }, limit: ${qbDialogs.limit}, skip: ${
  //         qbDialogs.skip
  //       } items: ${JSON.stringify(qbDialogs.items)}`,
  //     );
  //     currentPagination = new Pagination(0, perPage);
  //     currentPagination.totalPages = qbDialogs.total_entries / perPage;
  //     currentPagination.setCurrentPage(pageNumber);
  //
  //     // eslint-disable-next-line no-restricted-syntax
  //     for (const item of qbDialogs.items) {
  //       const qbEntity: QBChatDialog = item;
  //       if (!qbEntity) {
  //         continue;
  //       }
  //       const newDTO: RemoteDialogDTO =
  //         // eslint-disable-next-line no-await-in-loop
  //         await this.getCurrentDialogDTOMapper().toTDO(qbEntity);
  //
  //       // artan 27/06/23
  //       if (newDTO.type === DialogType.group) {
  //         // eslint-disable-next-line no-await-in-loop
  //         await QBJoinGroupDialog(newDTO.id).catch((reason) => {
  //           console.log('getDialogs. QBJoinGroupDialog error', reason);
  //           throw new RemoteDataSourceException(
  //             INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
  //             INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
  //           );
  //         });
  //       }
  //
  //       //
  //       // const currentUserId = this._authInformation?.userId || 0;
  //       // const statusMessageParams: QBMessageStatusParams = {
  //       //   userId: newDTO.lastMessageUserId || currentUserId,
  //       //   dialogId: newDTO.id,
  //       //   messageId: newDTO.lastMessageId,
  //       // };
  //       //
  //       // // eslint-disable-next-line @typescript-eslint/no-unsafe-call
  //       // QBChatMarkMessageDelivered(statusMessageParams);
  //
  //       dialogsDTO.push(newDTO);
  //     }
  //   }
  //
  //   return Promise.resolve(new RemoteDialogsDTO(dialogsDTO, currentPagination));
  // }

  // eslint-disable-next-line class-methods-use-this
  async createDialog(dto: RemoteDialogDTO): Promise<RemoteDialogDTO> {
    console.log(
      'call createDialog in remote with params: ',
      JSON.stringify(dto),
    );
    const qbEntity: QBChatDialog =
      await this.getCurrentDialogDTOMapper().fromDTO(dto);

    let qbDialog: QBChatDialog | undefined | null;

    if (qbEntity.type === DialogType.private) {
      const participantId = qbEntity.occupants_ids[0];

      qbDialog = await QBCreatePrivateDialog(participantId, qbEntity.name);
    } else if (qbEntity.type === DialogType.group) {
      const participants = qbEntity.occupants_ids;

      const data = { photo: qbEntity.photo };

      qbDialog = await QBCreateGroupDialog(participants, qbEntity.name, data);
    }

    if (qbDialog === null || qbDialog === undefined) {
      return Promise.reject(
        new RemoteDataSourceException(
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
        ),
      );
    }

    const newDTO: RemoteDialogDTO =
      await this.getCurrentDialogDTOMapper().toTDO(qbDialog);

    console.log(
      'have result in createDialog in remote ds: ',
      JSON.stringify(newDTO),
    );

    return Promise.resolve(newDTO);
  }

  async createDialogWithAutojoin(
    dto: RemoteDialogDTO,
  ): Promise<RemoteDialogDTO> {
    console.log(
      'call createDialogWithAutoJoin in remote with params: ',
      JSON.stringify(dto),
    );
    const qbEntity: QBChatDialog =
      await this.getCurrentDialogDTOMapper().fromDTO(dto);

    let qbDialog: QBChatDialog | undefined | null;

    if (qbEntity.type === DialogType.private) {
      const participantId = qbEntity.occupants_ids[0];

      qbDialog = await QBCreatePrivateDialog(participantId, qbEntity.name);
    } else if (qbEntity.type === DialogType.group) {
      const participants = qbEntity.occupants_ids;

      const data = { photo: qbEntity.photo };

      qbDialog = await QBCreateGroupDialogWithAutojoinFalse(
        participants,
        qbEntity.name,
        data,
      );
    }

    if (qbDialog === null || qbDialog === undefined) {
      return Promise.reject(
        new RemoteDataSourceException(
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
        ),
      );
    }

    const newDTO: RemoteDialogDTO =
      await this.getCurrentDialogDTOMapper().toTDO(qbDialog);

    console.log(
      'have result in createDialog in remote ds: ',
      JSON.stringify(newDTO),
    );

    return Promise.resolve(newDTO);
  }

  async updateDialog(dto: RemoteDialogDTO): Promise<RemoteDialogDTO> {
    const qbEntity: QBUIKitChatDialog =
      await this.getCurrentDialogDTOMapper().fromDTO(dto);

    let data = {};

    if (qbEntity.name && qbEntity.name.length > 0) {
      data = {
        name: qbEntity.name,
        // photo: qbEntity.photo,
        // photo: 'null',
        ...data,
      };
    }
    if (qbEntity.photo) {
      data = {
        ...data,
        photo: qbEntity.photo,
      };
    }
    if (qbEntity.new_occupants_ids && qbEntity.new_occupants_ids.length > 0) {
      data = {
        ...data,
        push_all: { occupants_ids: qbEntity.new_occupants_ids },
      };
    }

    const qbDialogs: QBUIKitChatDialog = await QBUpdateDialog(
      qbEntity._id,
      data,
    );

    if (qbDialogs === null || qbDialogs === undefined) {
      return Promise.reject(
        new RemoteDataSourceException(
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
        ),
      );
    }

    const newDTO: RemoteDialogDTO =
      await this.getCurrentDialogDTOMapper().toTDO(qbDialogs);

    return Promise.resolve(newDTO);
  }

  // eslint-disable-next-line class-methods-use-this
  async getDialog(dto: RemoteDialogDTO): Promise<RemoteDialogDTO> {
    const qbEntity: QBChatDialog =
      await this.getCurrentDialogDTOMapper().fromDTO(dto);

    const qbDialog: QBGetDialogResult | undefined = await QBGetDialogById(
      qbEntity._id,
    );

    if (qbDialog === null || qbDialog === undefined) {
      return Promise.reject(
        new RemoteDataSourceException(
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
        ),
      );
    }
    const qbEntityFromDB: QBChatDialog = qbDialog.items[0];

    const resultDTO: RemoteDialogDTO =
      await this.getCurrentDialogDTOMapper().toTDO(qbEntityFromDB);

    return Promise.resolve(resultDTO);
  }

  // eslint-disable-next-line class-methods-use-this
  async deleteDialog(dto: RemoteDialogDTO): Promise<void> {
    const qbEntity: QBChatDialog =
      await this.getCurrentDialogDTOMapper().fromDTO(dto);

    await QBDeleteDialog([qbEntity._id]).catch(() => {
      throw new RemoteDataSourceException(
        INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
        INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
      );
    });

    return Promise.resolve();
  }

  async deleteUsersFromDialog(
    dialogDTO: RemoteDialogDTO,
    usersDTO: Array<RemoteUserDTO>,
  ): Promise<void> {
    const qbEntity: QBChatDialog =
      await this.getCurrentDialogDTOMapper().fromDTO(dialogDTO);
    const usersIds: string[] = [];

    usersDTO.forEach((item) => {
      usersIds.push(item.id);
    });

    let data = {};

    if (usersIds.length > 0) {
      data = {
        pull_all: { occupants_ids: usersIds },
      };
    }
    const qbDialogs: QBChatDialog = await QBUpdateDialog(qbEntity._id, data);

    if (qbDialogs === null || qbDialogs === undefined) {
      return Promise.reject(
        new RemoteDataSourceException(
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
        ),
      );
    }

    return Promise.resolve();
  }

  async unjoinUsersFromDialog(dialogDTO: RemoteDialogDTO): Promise<void> {
    const qbEntity: QBChatDialog =
      await this.getCurrentDialogDTOMapper().fromDTO(dialogDTO);

    const qbResult = await QBLeaveDialog(qbEntity._id);

    if (qbResult === null || qbResult === undefined) {
      return Promise.reject(
        new RemoteDataSourceException(
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
        ),
      );
    }

    return Promise.resolve();
  }

  // async getUser(dto: RemoteUserDTO): Promise<RemoteUserDTO> {
  //   const currentItem: QBUser | undefined = await QBUsersGetById(
  //     parseInt(dto.id, 10),
  //   );
  //
  //   if (currentItem === null || currentItem === undefined) {
  //     return Promise.reject(
  //       new RemoteDataSourceException(
  //         INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
  //         INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
  //       ),
  //     );
  //   }
  //   let dtoUser: RemoteUserDTO | undefined;
  //
  //   try {
  //     dtoUser = await this.userDTOMapper.toTDO<QBUser, RemoteUserDTO>(
  //       currentItem,
  //     );
  //   } catch (e) {
  //     console.log('problem with:');
  //     console.log(JSON.stringify(currentItem));
  //     console.log((e as MapperDTOException).message);
  //     console.log((e as MapperDTOException)._description);
  //   }
  //
  //   if (dtoUser !== null || dtoUser !== undefined) {
  //     return Promise.resolve(dtoUser as RemoteUserDTO);
  //   }
  //
  //   return Promise.reject(
  //     new RemoteDataSourceException(
  //       NOT_FOUND_REMOTE_DATASOURCE_EXCEPTION_MESSAGE,
  //       NOT_FOUND_REMOTE_DATASOURCE_EXCEPTION_CODE,
  //       `not found user with id: ${dto.id} (${dto.full_name})`,
  //     ),
  //   );
  // }

  // eslint-disable-next-line @typescript-eslint/require-await,class-methods-use-this,@typescript-eslint/no-unused-vars
  async getUser(dto: RemoteUserDTO): Promise<RemoteUserDTO> {
    // Проверяем кэш перед запросом на сервер
    if (this.userCache.has(dto.id)) {
      console.log(`User ${dto.id} found in cache.`);
      return Promise.resolve(this.userCache.get(dto.id)!);
    }

    const currentItem: QBUser | undefined = await QBUsersGetById(
      parseInt(dto.id, 10),
    );

    if (currentItem === null || currentItem === undefined) {
      return Promise.reject(
        new RemoteDataSourceException(
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
        ),
      );
    }

    let dtoUser: RemoteUserDTO | undefined;

    try {
      dtoUser = await this.userDTOMapper.toTDO<QBUser, RemoteUserDTO>(
        currentItem,
      );
    } catch (e) {
      console.log('problem with:');
      console.log(JSON.stringify(currentItem));
      console.log((e as MapperDTOException).message);
      console.log((e as MapperDTOException)._description);
    }

    if (dtoUser !== null && dtoUser !== undefined) {
      // Сохраняем пользователя в кэше
      this.userCache.set(dto.id, dtoUser);
      console.log(`User ${dto.id} added to cache.`);
      return Promise.resolve(dtoUser as RemoteUserDTO);
    }

    return Promise.reject(
      new RemoteDataSourceException(
        NOT_FOUND_REMOTE_DATASOURCE_EXCEPTION_MESSAGE,
        NOT_FOUND_REMOTE_DATASOURCE_EXCEPTION_CODE,
        `not found user with id: ${dto.id} (${dto.full_name})`,
      ),
    );
  }

  // eslint-disable-next-line @typescript-eslint/require-await,class-methods-use-this,@typescript-eslint/no-unused-vars
  async getUsers(remoteUsersDTO: RemoteUsersDTO): Promise<RemoteUsersDTO> {
    const usersDTO: Array<RemoteUserDTO> = new Array<RemoteUserDTO>();

    // eslint-disable-next-line no-restricted-syntax
    for (const item of remoteUsersDTO.users) {
      // eslint-disable-next-line no-await-in-loop
      const userDTO = await this.getUser(item);

      usersDTO.push(userDTO);
    }

    return Promise.resolve(new RemoteUsersDTO(usersDTO, new Pagination()));
  }

  // eslint-disable-next-line class-methods-use-this
  public async getAllUsers(
    pagination: Pagination,
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    filter?: string,
  ): Promise<PaginatedDTOResult> {
    console.log('call getAllUsers');

    console.log('pagination: ', JSON.stringify(pagination));

    const users: RemoteUserDTO[] = [];
    const pageTemplate = {
      pageSize: pagination.perPage,
      startPageNumber: pagination.getCurrentPage(),
    };
    const pageNumber = pageTemplate.startPageNumber;
    const resultPagination = new Pagination(
      pageNumber,
      pageTemplate.startPageNumber,
    );

    let params: GetUserParams = {
      per_page: pageTemplate.pageSize,
      page: pageNumber,
    };

    if (filter && filter.length > 2) {
      params = {
        full_name: filter,
        per_page: pageTemplate.pageSize,
        page: pageNumber > 0 ? pageNumber : 1,
      };
    }

    console.log('params: ', JSON.stringify(params));

    // eslint-disable-next-line no-await-in-loop
    const response: ListUserResponse | void = await QBUsersGet(params).catch(
      (err) => {
        console.log('catch call QBUsersGet : ', stringifyError(err));
      },
    );

    if (response === undefined || response === null) {
      throw new RemoteDataSourceException(
        NOT_FOUND_REMOTE_DATASOURCE_EXCEPTION_MESSAGE,
        NOT_FOUND_REMOTE_DATASOURCE_EXCEPTION_CODE,
        `params: ${JSON.stringify(params)}`,
      );
    }
    let currentItem: QBUser;

    console.log('response: total_entries: ', response.total_entries);
    console.log('response: per_page: ', response.per_page);
    console.log('response: current_page: ', response.current_page);

    resultPagination.totalPages =
      response.total_entries / pageTemplate.pageSize;
    resultPagination.setCurrentPage(response.current_page);
    console.log(
      `response result: count of items ${
        response.items.length
      } items: ${JSON.stringify(response.items)}`,
    );
    try {
      // eslint-disable-next-line no-restricted-syntax
      for (const item of response.items) {
        currentItem = item.user;
        // eslint-disable-next-line no-await-in-loop
        const dtoUser: RemoteUserDTO = await this.userDTOMapper.toTDO<
          QBUser,
          RemoteUserDTO
        >(currentItem);

        users.push(dtoUser);
      }
    } catch (e) {
      console.log('problem with:');
      console.log(JSON.stringify(currentItem!));
      console.log((e as MapperDTOException).message);
      console.log((e as MapperDTOException)._description);
    }

    return { ResultData: users, PaginationResult: resultPagination };
  }

  // eslint-disable-next-line @typescript-eslint/require-await,class-methods-use-this,@typescript-eslint/no-unused-vars,@typescript-eslint/ban-ts-comment
  // @ts-ignore
  // eslint-disable-next-line @typescript-eslint/require-await,class-methods-use-this
  async getMessages(
    dialogId: string,
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    pagination: Pagination,
  ): Promise<RemoteMessagesDTO> {
    const messagesDTO: Array<RemoteMessageDTO> = new Array<RemoteMessageDTO>();

    const qbMessages: GetMessagesResult = await qbChatGetMessagesExtended(
      dialogId,
    );

    if (qbMessages === null || qbMessages === undefined) {
      return Promise.reject(
        new RemoteDataSourceException(
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
        ),
      );
    }
    console.log(`GET MESSAGES ${qbMessages.items.length} FROM SERVER.`);
    const currentUserId = this._authInformation?.userId;

    await Promise.all(
      qbMessages.items.map(async (currentItem) => {
        const dtoMessage: RemoteMessageDTO = await this.messageDTOMapper.toTDO<
          QBChatMessage,
          RemoteMessageDTO
        >(currentItem);

        if (
          this.authProcessed &&
          currentUserId &&
          dtoMessage.read_ids &&
          dtoMessage.read_ids.length > 0 &&
          dtoMessage.read_ids.includes(currentUserId) === false
        ) {
          QBChatMarkMessageRead({
            messageId: dtoMessage.id,
            dialogId: dtoMessage.dialogId,
            userId: dtoMessage.sender_id,
          });
        }

        messagesDTO.push(dtoMessage);

        return dtoMessage;
      }),
    );

    return Promise.resolve(
      new RemoteMessagesDTO(messagesDTO, new Pagination()),
    );
  }

  // eslint-disable-next-line class-methods-use-this
  async typingMessageStart(
    dialogDTO: RemoteDialogDTO,
    senderId: number,
  ): Promise<void> {
    const qbEntity: QBChatDialog =
      await this.getCurrentDialogDTOMapper().fromDTO(dialogDTO);

    if (dialogDTO.type === DialogType.group) {
      if (!this.joinStatusMap.has(dialogDTO.id)) {
        await this.joinGroupDialog(dialogDTO.id);
      }
      // await QBJoinGroupDialog(dialogDTO.id).catch(() => {
      //   throw new RemoteDataSourceException(
      //     INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
      //     INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
      //   );
      // });
    }

    QBSendIsTypingStatus(qbEntity, senderId);

    return Promise.resolve();
  }

  // eslint-disable-next-line class-methods-use-this
  async typingMessageStop(
    dialogDTO: RemoteDialogDTO,
    senderId: number,
  ): Promise<void> {
    const qbEntity: QBChatDialog =
      await this.getCurrentDialogDTOMapper().fromDTO(dialogDTO);

    QBSendIsStopTypingStatus(qbEntity, senderId);

    return Promise.resolve();
  }

  // eslint-disable-next-line @typescript-eslint/require-await,class-methods-use-this,@typescript-eslint/no-unused-vars
  async sendMessage(dto: RemoteMessageDTO): Promise<RemoteMessageDTO> {
    console.log('call sendMessage');
    //
    const QB = getQB();
    //
    const messageText = dto.message;

    const extension: Partial<QBUIKitChatNewMessage['extension']> = {
      save_to_history: 1,
      dialog_id: dto.dialogId,
    };

    if (dto.notification_type) {
      extension.notification_type = dto.notification_type;
    }

    if (dto.sender_id || dto.recipient_id) {
      extension.sender_id = dto.sender_id || dto.recipient_id;
    }

    if (dto.qb_message_action) {
      extension.qb_message_action = dto.qb_message_action;
    }

    if (dto.origin_sender_name) {
      extension.origin_sender_name = dto.origin_sender_name;
    }

    const originalMessages = MessageDTOMapper.translateOriginalDataToJSON(
      MessageDTOMapper.convertToQBChatNewMessage(
        dto.qb_original_messages || [],
      ) || [],
    );

    if (originalMessages && originalMessages.length > 0) {
      extension.qb_original_messages = originalMessages;
    }

    const qbEntity: QBUIKitChatNewMessage = {
      type: dto.dialog_type === DialogType.private ? 'chat' : 'groupchat',
      body: messageText || '',
      dialog_id: dto.dialogId,
      extension: extension as QBUIKitChatNewMessage['extension'],
      markable: 1,
    };

    // const qbEntity: QBUIKitChatNewMessage = {
    //   type: dto.dialog_type === DialogType.private ? 'chat' : 'groupchat',
    //   body: messageText || '',
    //   notification_type: dto.notification_type,
    //   dialog_id: dto.dialogId,
    //   extension: {
    //     save_to_history: 1,
    //     dialog_id: dto.dialogId,
    //     notification_type: dto.notification_type,
    //     sender_id: dto.sender_id || dto.recipient_id,
    //     qb_message_action: dto.qb_message_action, // 'forward' 'reply'
    //     origin_sender_name: dto.origin_sender_name,
    //     qb_original_messages: MessageDTOMapper.translateOriginalDataToJSON(
    //       MessageDTOMapper.convertToQBChatNewMessage(
    //         dto.qb_original_messages || [],
    //       ) || [],
    //     ),
    //   },
    //   markable: 1,
    //   // markable: 0, // mark_as_read ??
    // };

    if (dto.attachments?.length > 0) {
      qbEntity.extension.attachments = [];
      dto.attachments.forEach((att) => {
        // const chatMessageAttachment: ChatMessageAttachment = {
        //   id: att.id,
        //   name: att.name,
        //   size: att.size,
        //   type: att.type.toString(),
        //   uid: att.uid,
        // };
        const chatMessageAttachment: ChatMessageAttachment = {
          id: att.id?.toString(),
          name: att.name?.toString(),
          size: att.size?.toString(),
          type: att.type?.toString(),
          uid: att.uid?.toString(),
        };

        qbEntity.extension.attachments?.push(chatMessageAttachment);
      });
    }
    let qbMessageId: QBChatMessage['_id'] | undefined | null;

    if (dto.dialog_type === DialogType.private) {
      qbMessageId = await QBChatSendMessage(dto.recipient_id, qbEntity);
    } else {
      //original version
      // await QBJoinGroupDialog(dto.dialogId).catch(() => {
      //   throw new RemoteDataSourceException(
      //     INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
      //     INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
      //   );
      // });
      //beta version: 13-15
      // if (!this.joinStatusMap.has(dto.dialogId)) {
      //   await this.joinGroupDialog(dto.dialogId);
      // }
      await this.joinGroupDialog(dto.dialogId);
      const dialogJid = QB.chat.helpers.getRoomJidFromDialogId(dto.dialogId);
      try {
        qbMessageId = await QBChatSendMessage(dialogJid, qbEntity);
        console.log(
          `Dialog joined status is ${this.joinStatusMap.has(
            dto.dialogId,
          )} and message was sent with Id: ${qbMessageId}`,
        );
      } catch (e) {
        qbMessageId = undefined;
        const errorMessage = `Error in DataSource: send message throw: ${stringifyError(
          e,
        )}`;
        console.log('Error in DataSource: send message throw: ', errorMessage);
        console.warn('Error in DataSource: send message throw: ', errorMessage);
        console.error(
          'Error in DataSource: send message throw: ',
          errorMessage,
        );
      }
    }

    if (qbMessageId === null || qbMessageId === undefined) {
      return Promise.reject(
        new RemoteDataSourceException(
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
        ),
      );
    }
    console.log(`DataSource: regular message was sent, id: ${qbMessageId}`);
    // eslint-disable-next-line no-param-reassign
    dto.id = qbMessageId;

    return Promise.resolve(dto);
  }

  // eslint-disable-next-line class-methods-use-this

  // eslint-disable-next-line class-methods-use-this
  async sendSystemMessage(dto: RemoteMessageDTO): Promise<boolean> {
    let qbMessages: QBSystemMessage | QBChatMessage['_id'] | undefined;

    if (dto.notification_type && dto.notification_type.length > 0) {
      const systemMessage: { extension: QBSystemMessage['extension'] } = {
        extension: {
          notification_type: dto.notification_type,
          dialog_id: dto.dialogId,
        },
      };

      qbMessages = await QBChatSendSystemMessage(
        dto.recipient_id,
        systemMessage,
      );
    }

    if (qbMessages === null || qbMessages === undefined) {
      return Promise.reject(
        new RemoteDataSourceException(
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
        ),
      );
    }
    console.log('system message was sent');

    return Promise.resolve(true);
  }

  // eslint-disable-next-line @typescript-eslint/require-await,class-methods-use-this,@typescript-eslint/no-unused-vars
  async updateMessage(
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    dto: RemoteMessageDTO,
  ): Promise<RemoteMessageDTO> {
    const qbEntity: QBChatMessage = await this.messageDTOMapper.fromDTO(dto);

    const storageValue = localStorage.getItem('qbMessages');
    const result: string = storageValue ?? '';

    const qbMessages: Record<string, QBChatMessage> =
      result === '' ? {} : JSON.parse(result);

    if (qbMessages === null || qbMessages === undefined) {
      return Promise.reject(
        new RemoteDataSourceException(
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
        ),
      );
    }
    qbMessages[qbEntity._id] = qbEntity;

    // TODO: How get updated QBChatDialog and return to DTO
    localStorage.setItem('qbMessages', JSON.stringify(qbMessages));

    const newDTO: RemoteMessageDTO = await this.messageDTOMapper.toTDO(
      qbEntity,
    );

    return Promise.resolve(newDTO);
  }

  // eslint-disable-next-line @typescript-eslint/require-await,class-methods-use-this,@typescript-eslint/no-unused-vars
  async deleteMessage(dto: RemoteMessageDTO): Promise<void> {
    const qbEntity: QBChatMessage = await this.messageDTOMapper.fromDTO(dto);

    const storageValue = localStorage.getItem('qbMessages');
    const result: string = storageValue ?? '';

    const qbMessages: Record<string, QBChatMessage> =
      result === '' ? {} : JSON.parse(result);

    if (qbMessages === null || qbMessages === undefined) {
      return Promise.reject(
        new RemoteDataSourceException(
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
        ),
      );
    }
    // TODO: try catch for delete
    const filteredqbMessages: Record<string, QBChatMessage> = {};

    Object.keys(qbMessages).forEach((key) => {
      if (key !== qbEntity._id) {
        filteredqbMessages[key] = qbMessages[key];
      }
    });

    localStorage.setItem('qbMessages', JSON.stringify(filteredqbMessages));

    return Promise.resolve();
  }

  // eslint-disable-next-line @typescript-eslint/require-await,class-methods-use-this,@typescript-eslint/no-unused-vars
  async createFile(dto: RemoteFileDTO): Promise<RemoteFileDTO> {
    console.log('call createFile in remote with params: ', JSON.stringify(dto));
    const qbParam: QBBlobCreateUploadParams = await this.fileDTOMapper.fromDTO(
      dto,
    );

    const result = await QBCreateAndUploadContent(qbParam).catch((err) => {
      console.log('Error: ', stringifyError(err));
      throw new RemoteDataSourceException(
        INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
        INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
        `exception: ${stringifyError(err)}`,
      );
    });

    if (result === null || result === undefined) {
      return Promise.reject(
        new RemoteDataSourceException(
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_MESSAGE,
          INCORRECT_REMOTE_DATASOURCE_DATA_EXCEPTION_CODE,
        ),
      );
    }

    const qbContentObject: QBBlobCreate = result as QBBlobCreate;
    const newDto: RemoteFileDTO = await this.fileDTOMapper.toTDO<
      QBBlobCreate,
      RemoteFileDTO
    >(qbContentObject);

    console.log(
      'have result in createFile in remote ds: ',
      JSON.stringify(newDto),
    );

    return Promise.resolve(newDto);
  }

  // eslint-disable-next-line @typescript-eslint/require-await,class-methods-use-this,@typescript-eslint/no-unused-vars
  async getFile(dto: RemoteFileDTO): Promise<RemoteFileDTO> {
    throw new Error('Method not implemented.');
  }

  // eslint-disable-next-line @typescript-eslint/require-await,class-methods-use-this,@typescript-eslint/no-unused-vars
  async deleteFile(dto: RemoteFileDTO): Promise<void> {
    throw new Error('Method not implemented.');
  }

  // eslint-disable-next-line @typescript-eslint/require-await,class-methods-use-this,@typescript-eslint/no-unused-vars
  async subscribeToChatConnectionEvents(
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    fileId: string,
  ): Promise<void> {
    throw new Error('Method not implemented.');
  }
}
