// ignore_for_file: use_build_context_synchronously

import 'dart:developer';

import 'package:flutter/cupertino.dart';
import 'package:mc_common_app/classes/app_state.dart';
import 'package:mc_common_app/classes/consts.dart';
import 'package:mc_common_app/extensions/string_extensions.dart';
import 'package:mc_common_app/generated/locale_keys.g.dart';
import 'package:mc_common_app/main.dart';
import 'package:mc_common_app/models/chat_models/chat_message_model.dart';
import 'package:mc_common_app/models/general_models/generic_resp_model.dart';
import 'package:mc_common_app/models/requests_models/provider_offers_model.dart';
import 'package:mc_common_app/repositories/chat_repo.dart';
import 'package:mc_common_app/repositories/request_repo.dart';
import 'package:mc_common_app/utils/enums.dart';
import 'package:mc_common_app/utils/utils.dart';
import 'package:mc_common_app/view_models/requests_view_model.dart';
import 'package:provider/provider.dart';
import 'package:signalr_core/signalr_core.dart';
import 'package:easy_localization/easy_localization.dart';

class ChatVM extends ChangeNotifier {
  final ChatRepo chatRepo;
  final RequestRepo requestRepo;

  ChatVM({required this.chatRepo, required this.requestRepo});

  HubConnection? hubConnection;

  String chatMessageText = "";

  updateChatMessageText(String value) {
    chatMessageText = value;
  }

  clearChatMessageText() {
    chatMessageText = "";
    notifyListeners();
  }

  int latestOfferId = 0;

  updateLatestOfferId(var value) {
    latestOfferId = value;
    notifyListeners();
  }

  bool isUserOnChatScreen = false;

  final ScrollController scrollController = ScrollController();

  void scrollChatDown() {
    if (!isUserOnChatScreen) {
      return;
    }

    scrollController.animateTo(
      scrollController.position.maxScrollExtent + 200, // for the text field
      duration: const Duration(seconds: 1),
      curve: Curves.fastOutSlowIn,
    );
  }

  List<OfferRequestCommentModel> offerRejectModelList = [
    OfferRequestCommentModel(
      index: 0,
      isSelected: false,
      title: LocaleKeys.changedMind.tr(),
    ),
    OfferRequestCommentModel(
      index: 1,
      isSelected: false,
      title: LocaleKeys.veryHighPrice.tr(),
    ),
    OfferRequestCommentModel(
      index: 2,
      isSelected: false,
      title: LocaleKeys.alreadySold.tr(),
    ),
    OfferRequestCommentModel(
      index: 3,
      isSelected: true,
      title: LocaleKeys.otherVar.tr(),
    ),
  ];

  OfferRequestCommentModel selectedOfferRequestCommentModel = OfferRequestCommentModel(
    index: 3,
    isSelected: true,
    title: LocaleKeys.otherVar.tr(),
  );

  void updateSelectionInOfferRejectModelList(int index) {
    for (var value in offerRejectModelList) {
      value.isSelected = false;
    }
    selectedOfferRequestCommentModel = offerRejectModelList[index];
    offerRejectModelList[index].isSelected = true;
    notifyListeners();
  }

  String rejectOfferDescription = "";
  String rejectOfferDescriptionError = "";

  void updateRejectOfferDescription(String value) {
    rejectOfferDescription = value;
    if (value.isNotEmpty) {
      rejectOfferDescriptionError = "";
    }
  }

  //REJECTING OFFER
  bool isRejectOfferButtonValidated() {
    if (selectedOfferRequestCommentModel.index != offerRejectModelList.length - 1) {
      return true;
    }
    bool isValidated = true;
    if (rejectOfferDescription.isEmpty) {
      rejectOfferDescriptionError = GlobalConsts.descriptionError;
      isValidated = false;
    } else {
      rejectOfferDescriptionError = "";
    }

    notifyListeners();

    return isValidated;
  }

  Future<void> onNewMessageReceivedForRequestOffer({required List<ChatMessageModel> messages, bool isMyOwnOffer = false, required RequestsVM requestsVM}) async {
    if (AppState().currentAppType == AppType.customer) {
      for (var msg in messages) {
        int providerIndex = serviceProviderOffersList.indexWhere((element) => element.providerUserId == msg.senderUserID);
        if (providerIndex != -1) {
          serviceProviderOffersList[providerIndex].chatMessages!.add(msg);
          scrollChatDown();
        }
      }
    } else {
      for (var msg in messages) {
        logger.i(msg);
        // Where we need to call this function for saving a message in chat we will use receiverUserID and in those cases where received ID is null , it means it is a signal R call
        int providerIndex = -1;
        if (isMyOwnOffer) {
          providerIndex = requestsVM.myFilteredRequests.indexWhere((element) => (element.customerUserID == msg.receiverUserID && element.id == msg.requestID));
        } else {
          providerIndex = requestsVM.myFilteredRequests.indexWhere((element) => (element.customerUserID == msg.senderUserID && element.id == msg.requestID));
        }
        log("providerIndex: $providerIndex");
        if (providerIndex != -1) {
          requestsVM.addChatMessagesInRequestsModel(msg: msg, index: providerIndex);
          scrollChatDown();
        }
      }
    }
    notifyListeners();
  }

  Future<void> onNewMessageReceivedForAds({required List<ChatMessageModel> messages}) async {
    for (var msg in messages) {
      currentMessagesForAds.add(msg);
    }
    scrollChatDown();
    notifyListeners();
  }

  void subscribeToReceiveRequestOfferMessages(BuildContext context) {
    hubConnection!.on(SignalrConsts.receiveMessageRequestOffer, (List<Object?>? arguments) {
      if (arguments == null || arguments.isEmpty) return;
      List<ChatMessageModel> chat = [];
      for (var message in arguments) {
        final chatMessage = ChatMessageModel.fromJson(message as Map<String, dynamic>);
        chat.add(chatMessage);
      }
      onNewMessageReceivedForRequestOffer(messages: chat, requestsVM: context.read<RequestsVM>());
      logger.i(arguments);
      // Utils.showToast(arguments.toString());
    });
  }

  void subscribeToReceiveAdMessages(BuildContext context) {
    hubConnection!.on(SignalrConsts.receiveMessageAds, (List<Object?>? arguments) {
      if (arguments == null || arguments.isEmpty) return;
      List<ChatMessageModel> chat = [];
      for (var message in arguments) {
        final chatMessage = ChatMessageModel.fromJson(message as Map<String, dynamic>);
        chat.add(chatMessage);
      }
      onNewMessageReceivedForAds(messages: chat);
      logger.i(arguments);
      // Utils.showToast(arguments.toString());
    });
  }

  Future<void> buildHubConnection(BuildContext context) async {
    if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
      try {
        hubConnection = await chatRepo.getHubConnection();
        await hubConnection!.start();
        subscribeToReceiveRequestOfferMessages(context);
        subscribeToReceiveAdMessages(context);
        hubConnection!.onclose((exception) {
          logger.i("onClose: ${exception.toString()}");
          buildHubConnection(context);
        });
        hubConnection!.onreconnecting((exception) => () => logger.i("onReconnecting: ${exception.toString()}"));
        hubConnection!.onreconnected((connectionId) => () => logger.i("onReconnected: ${connectionId.toString()}"));
      } catch (e) {
        logger.i("Error: ${e.toString()}");
      }

      notifyListeners();
    }
  }

  Future<bool> onOfferSendForRequest({
    required String receiverId,
    required ChatMessageTypeEnum chatMessageType,
    required String message,
    required int requestId,
    required String offerPrice,
    required BuildContext context,
  }) async {
    if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
      logger.i("Hub Not Connected!!");
      await buildHubConnection(context);
    }
    if (hubConnection!.state == HubConnectionState.connected) {
      final providerId = AppState().getUser.data!.userInfo!.providerId;
      final obj = <String, dynamic>{
        "ReceiverUserID": receiverId,
        "MessageType": chatMessageType.getIdFromChatMessageTypeEnum(),
        "ChatText": message,
        "RequestID": requestId,
        "ReqOfferID": 0,
        "ReqOffer": <String, dynamic>{
          "RequestID": requestId,
          "Price": double.parse(offerPrice),
          "ServiceProviderID": providerId,
          "OfferStatus": RequestOfferStatusEnum.offer.getIdFromRequestOfferStatusEnum(),
          "Comment": message,
        },
      };
      logger.i(obj);

      hubConnection!.invoke(
        SignalrConsts.sendMessageRequestOffer,
        args: <Object>[obj],
      ).catchError((e) {
        logger.i("error in invoking SendMessageRequestOffer: ${e.toString()}");
        Utils.showToast(e.toString());
        return false;
      });
    }

    return true;
  }

  Future<bool> onTextMessageSendForRequest({
    required String receiverId,
    required ChatMessageTypeEnum chatMessageType,
    required String message,
    required int requestId,
    required String offerPrice,
    required BuildContext context,
  }) async {
    if (message.isEmpty) return false;

    if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
      logger.i("Hub Not Connected!!");

      await buildHubConnection(context);
    }
    if (hubConnection!.state == HubConnectionState.connected) {
      final userId = AppState().getUser.data!.userInfo!.userId.toString();
      final name = AppState().getUser.data!.userInfo!.firstName.toString();

      final obj = <String, dynamic>{
        "ReceiverUserID": receiverId,
        "SenderUserID": userId,
        "MessageType": chatMessageType.getIdFromChatMessageTypeEnum(),
        "ChatText": message,
        "RequestID": requestId,
        "ReqOfferID": latestOfferId,
        // "ReqOffer": {}
      };
      logger.i(obj);

      hubConnection!.invoke(
        SignalrConsts.sendMessageRequestOffer,
        args: <Object>[obj],
      ).catchError((e) {
        logger.i("error in invoking SendMessage: ${e.toString()}");
        Utils.showToast(e.toString());
        return false;
      });

      ChatMessageModel chatMessageModel = ChatMessageModel(
        messageType: chatMessageType.getIdFromChatMessageTypeEnum(),
        chatText: message,
        isMyMessage: true,
        requestID: requestId,
        senderName: name,
        senderUserID: userId,
        chatMessageTypeEnum: ChatMessageTypeEnum.freeText,
        receiverUserID: receiverId,
      );

      if (AppState().currentAppType == AppType.customer) {
        int providerIndex = serviceProviderOffersList.indexWhere((element) => element.providerUserId == receiverId);
        if (providerIndex != -1) {
          serviceProviderOffersList[providerIndex].chatMessages!.add(chatMessageModel);
        }
      } else {
        int providerIndex = context.read<RequestsVM>().myFilteredRequests.indexWhere((element) => (element.customerUserID == receiverId && element.id == requestId));
        log("providerIndex2:$providerIndex");
        if (providerIndex != -1) {
          context.read<RequestsVM>().addChatMessagesInRequestsModel(msg: chatMessageModel, index: providerIndex);
        }
      }
      return true;
    }
    return false;
  }

  List<ServiceProvidersOffers> serviceProviderOffersList = [];

  Future<void> getOffersFromProvidersByRequest({required int requestId, required BuildContext context}) async {
    try {
      Utils.showLoading(context);
      ProviderOffersModel providerOffersModel = await requestRepo.getOffersFromProvidersByRequest(requestId: requestId);
      Utils.hideLoading(context);
      serviceProviderOffersList.clear();
      serviceProviderOffersList = providerOffersModel.serviceProviders ?? [];
      notifyListeners();
    } catch (e) {
      logger.i(e.toString());
      Utils.showToast(e.toString());
      Utils.hideLoading(context);
    }
  }

  Future<void> getUsersChatMessagesForCustomer({
    required BuildContext context,
    required int providerId,
    required int requestOfferId,
    required int requestId,
    required int providerOfferIndex,
  }) async {
    try {
      int customerId = AppState().getUser.data!.userInfo!.customerId!;
      Utils.showLoading(context);
      List<ChatMessageModel> chatMessages = await chatRepo.getUsersChatMessagesForRequests(providerId: providerId, customerId: customerId, requestOfferId: requestOfferId, requestId: requestId);
      serviceProviderOffersList[providerOfferIndex].chatMessages = chatMessages;
      if (serviceProviderOffersList[providerOfferIndex].chatMessages != null) {
        for (var message in serviceProviderOffersList[providerOfferIndex].chatMessages!) {
          if (message.chatMessageTypeEnum == ChatMessageTypeEnum.offer) {
            updateLatestOfferId(message.reqOfferID ?? 0);
            log("latestOfferId: $latestOfferId");
          }
        }
      }
      Utils.hideLoading(context);
      notifyListeners();
    } catch (e) {
      logger.i(e.toString());
      Utils.showToast(e.toString());
      Utils.hideLoading(context);
    }
  }

  Future<void> getUsersChatMessagesForProvider({
    required BuildContext context,
    required int customerId,
    required int requestOfferId,
    required int requestId,
    required int customerRequestIndex,
  }) async {
    try {
      int providerId = AppState().getUser.data!.userInfo!.providerId!;
      Utils.showLoading(context);
      List<ChatMessageModel> chatMessages = await chatRepo.getUsersChatMessagesForRequests(providerId: providerId, customerId: customerId, requestOfferId: requestOfferId, requestId: requestId);
      context.read<RequestsVM>().overwriteChatMessagesInRequestsModel(messages: chatMessages, index: customerRequestIndex, context: context);
      Utils.hideLoading(context);
    } catch (e) {
      logger.i(e.toString());
      Utils.showToast(e.toString());
      Utils.hideLoading(context);
    }
  }

  Future<int> onActionOfferTapped({required BuildContext context, required RequestOfferStatusEnum requestOfferStatusEnum, required int reqOfferId, required String comments}) async {
    try {
      GenericRespModel genericRespModel = await requestRepo.updateOfferRequestStatus(requestOfferStatusEnum: requestOfferStatusEnum, requestOfferId: reqOfferId, comments: comments);
      Utils.showToast(genericRespModel.message.toString());
      return genericRespModel.messageStatus == 1 ? reqOfferId : -1;
    } catch (e) {
      logger.i(e.toString());
      Utils.showToast(e.toString());
      Utils.hideLoading(context);
      return -1;
    }
  }

  Future<bool> onSendMessageForActionOnRequestOffer({
    required String receiverId,
    required ChatMessageTypeEnum chatMessageType,
    required String comments,
    required int requestId,
    required int serviceProviderID,
    required int requestOfferID,
    required String offerPrice,
    required RequestOfferStatusEnum requestOfferStatusEnum,
    required BuildContext context,
  }) async {
    if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
      logger.i("Hub Not Connected!!");

      await buildHubConnection(context);
    }
    if (hubConnection!.state == HubConnectionState.connected) {
      var obj = <String, dynamic>{
        "ReceiverUserID": receiverId,
        "MessageType": chatMessageType.getIdFromChatMessageTypeEnum(),
        "ChatText": comments,
        "RequestID": requestId,
        "ReqOfferID": requestOfferID,
        "ReqOffer": <String, dynamic>{
          "ID": requestOfferID,
          "RequestID": requestId,
          "Price": double.parse(offerPrice),
          "ServiceProviderID": serviceProviderID,
          "OfferStatus": requestOfferStatusEnum.getIdFromRequestOfferStatusEnum(),
          "Comment": comments,
        },
      };
      logger.i(obj);

      hubConnection!.invoke(
        SignalrConsts.sendMessageRequestOffer,
        args: <Object>[obj],
      ).catchError((e) {
        logger.i("error in invoking SendMessageRequestOffer: ${e.toString()}");
        Utils.showToast(e.toString());
        return false;
      });
    }

    return true;
  }

  // =================== Ads ======================

  List<ChatMessageModel> currentMessagesForAds = [];

  Future<void> getUsersChatMessagesForAd({required BuildContext context, int? adID, int? adsChatBuyerId, String? userID, required bool isForBuyer}) async {
    try {
      Utils.showLoading(context);
      currentMessagesForAds = await chatRepo.getUsersChatMessagesForAds(userID: userID, adID: adID, isForBuyer: isForBuyer, adsChatBuyerId: adsChatBuyerId);
      Utils.hideLoading(context);

      List<int> unreadMessageIds = [];

      for (var msg in currentMessagesForAds) {
        if (!msg.isRead!) {
          unreadMessageIds.add(msg.id!);
        }
      }

      if (unreadMessageIds.isNotEmpty) {
        await markMessagesAsRead(context, unreadMessageIds, ChatTypeEnum.ads);
      }

      notifyListeners();
    } catch (e) {
      logger.i(e.toString());
      Utils.showToast(e.toString());
      Utils.hideLoading(context);
    }
  }

  Future<bool> markMessagesAsRead(BuildContext context, List<int> messageIds, ChatTypeEnum chatTypeEnum) async {
    bool status = false;
    try {
      Utils.showLoading(context);
      status = await chatRepo.markMessageAsRead(messageIds: messageIds, chatTypeEnum: chatTypeEnum);
      Utils.hideLoading(context);
    } catch (e) {
      logger.i(e.toString());
      Utils.showToast(e.toString());
      Utils.hideLoading(context);
      status = false;
    }
    return status;
  }

  Future<bool> onTextMessageSendForAds({
    required String receiverId,
    required ChatMessageTypeEnum chatMessageType,
    required String message,
    required int adId,
    required BuildContext context,
  }) async {
    if (message.isEmpty) return false;

    if (hubConnection == null || hubConnection!.state != HubConnectionState.connected) {
      logger.i("Hub Not Connected!!");
      await buildHubConnection(context);
    }

    if (hubConnection!.state == HubConnectionState.connected) {
      final userId = AppState().getUser.data!.userInfo!.userId.toString();
      final name = AppState().getUser.data!.userInfo!.firstName.toString();
      final obj = <String, dynamic>{
        "ReceiverUserID": receiverId,
        // "SenderUserID": userId,
        "MessageType": chatMessageType.getIdFromChatMessageTypeEnum(),
        "ChatText": message,
        "AdsID": adId,
      };
      logger.i("$obj");

      hubConnection!.invoke(
        SignalrConsts.sendMessageAds,
        args: <Object>[obj],
      ).catchError((e) {
        logger.i("error in invoking SendMessage: ${e.toString()}");
        Utils.showToast(e.toString());
        return false;
      });

      ChatMessageModel chatMessageModel = ChatMessageModel(
        messageType: chatMessageType.getIdFromChatMessageTypeEnum(),
        chatText: message,
        isMyMessage: true,
        senderName: name,
        senderUserID: userId,
        chatMessageTypeEnum: ChatMessageTypeEnum.freeText,
        receiverUserID: receiverId,
      );

      currentMessagesForAds.add(chatMessageModel);

      notifyListeners();

      if (AppState().currentAppType == AppType.customer) {
      } else {}
      return true;
    }
    return false;
  }
}
