import { FC, useEffect, useState, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueryClient, useMutation, useInfiniteQuery } from 'react-query';
import { matchPath, useParams } from 'react-router-dom';

import 'src/components/dashboard/chat/i18n';
import 'src/pages/dashboard/tickets/i18n';

import { useDebounce } from '@material-hu/hooks/useDebounce';
import Box from '@material-hu/mui/Box';
import Drawer from '@material-hu/mui/Drawer';
import Snackbar from '@material-hu/mui/Snackbar';

import useSnackbar from '@material-hu/components/design-system/Snackbar';

import typingLogo from 'src/assets/gif/typing.gif';
import { logEvent } from 'src/config/logging';
import { EVENTS_SOCKETS } from 'src/constants/sockets';
import { useAuth } from 'src/contexts/JWTContext';
import { useSocket } from 'src/contexts/SocketContext';
import useChat from 'src/hooks/queryHooks/chat';
import useGeneralError from 'src/hooks/useGeneralError';
import useLastMessageInView from 'src/hooks/useLastMessageInView';
import usePermissions from 'src/hooks/usePermissions';
import useTimeInScreen from 'src/hooks/useTimeInScreen';
import { getChatMessages } from 'src/services/chats';
import { giveFeedback } from 'src/services/tickets';
import { EventName } from 'src/types/amplitude';
import { ChatType } from 'src/types/chats';
import { FormType } from 'src/types/forms';
import { MessageReactionSocket } from 'src/types/reaction';
import {
  isReadOnlyChat,
  getAction,
  canMakeAction,
  addLastMessage,
  addLastMessageChat,
  canAddMessage,
  getEventNameChat,
  getMessages,
  getLastPage,
  canSendMessage,
  isGroupOrRegular,
} from 'src/utils/chats';
import { UserPermissions } from 'src/utils/permissions';

import {
  ChatMessages,
  ChatThreadToolbar,
  ChatQualify,
  ChatInfo,
  LeftGroupOption,
  ArchiveChatOption,
  ChatThreadContainer,
  ChatThreadFooter,
} from 'src/components/dashboard/chat/ChatThread';
import {
  chatKeys,
  setChatDetailData,
  setMessageListData,
  addChatMessageDataReaction,
  removeChatMessageDataReaction,
  deleteChatOfCache,
} from 'src/components/dashboard/chat/queries';
import { editMessage } from 'src/components/dashboard/chat/sockets';
import { formSkeletonRoutes } from 'src/components/dashboard/form/routes';

import ChatThreadInfo from './ChatThreadInfo';

const { VIEW_REGULAR_CHATS, VIEW_TICKETS } = UserPermissions;

const giveFeedbackWrap = ({ id, data }) => giveFeedback(id, data);

const SURVEY_PATH = formSkeletonRoutes.form.chat(FormType.SURVEY);

export type ChatThreadProps = {};

const ChatThread: FC<ChatThreadProps> = () => {
  const [typing, setTyping] = useState(false);
  const [typingCounter, setTypingCounter] = useState(0);
  const [isGroupInfoOpen, setIsGroupInfoOpen] = useState(false);

  const { id } = useParams();
  const { enqueueSnackbar } = useSnackbar();
  const showGeneralError = useGeneralError();
  const { t } = useTranslation(['CHAT', 'TICKETS']);
  const queryClient = useQueryClient();
  const socket = useSocket();

  const { user, instance } = useAuth();
  const { messagesEndRef, setRef, inViewMessagesEnd } = useLastMessageInView();

  const feedbackMutation = useMutation(giveFeedbackWrap);

  const { hasAll: canViewChats } = usePermissions([VIEW_REGULAR_CHATS]);
  const { hasAll: canViewTickets } = usePermissions([VIEW_TICKETS]);

  const debounceTypingCounter = useDebounce(typingCounter, 2000);

  const { data, isLoading, refetch, otherUser, participants } = useChat(
    chatKeys.detail(id),
    id,
  );

  const {
    data: messageData,
    isFetchingNextPage,
    hasNextPage,
    fetchNextPage,
  } = useInfiniteQuery(
    chatKeys.messages.list(id),
    ({ pageParam = 1 }) => getChatMessages(id, pageParam),
    {
      getNextPageParam: (lastPage, pages) => {
        const { page, totalPages } = pages[pages.length - 1]?.data || {};

        return page < totalPages ? pages.length + 1 : undefined;
      },
      onError: err => showGeneralError(err, t('ERROR_LOADING_MESSAGES')),
    },
  );

  const messages = getMessages(messageData);
  const lastPage = getLastPage(messageData);
  const isNewChat = data?.data?.chat?.lastMessageId === null;

  const isSurveyThread = !!matchPath(
    {
      path: SURVEY_PATH,
      end: false,
    },
    location.pathname,
  );

  useEffect(() => {
    setTyping(false);
  }, [debounceTypingCounter]);

  useEffect(() => {
    setIsGroupInfoOpen(false);
  }, [id]);

  useEffect(() => {
    const setUserTyping = typingData => {
      if (
        typingData.typingUser.id !== user.id &&
        Number(id) === typingData.chatId
      ) {
        setTypingCounter(preTypingCounter => preTypingCounter + 1);
        setTyping(true);
      }
    };

    const addMessage = chatItem => {
      if (chatItem.chat.id === Number(id)) {
        setMessageListData(Number(id), oldData => {
          if (!oldData) return oldData;

          if (canAddMessage(chatItem, oldData.items)) {
            oldData.items = addLastMessageChat(oldData.items, chatItem);
          }
          return oldData;
        });
      }
    };

    const closeTicket = chatItem => {
      if (chatItem.chat.id === Number(id)) {
        setMessageListData(Number(id), oldData => {
          if (!oldData) return oldData;

          if (canAddMessage(chatItem, oldData.items)) {
            oldData.items = addLastMessageChat(oldData.items, chatItem);
          }

          return oldData;
        });

        setChatDetailData(Number(id), oldData => {
          oldData.status = chatItem.status;
          oldData.responsibleId = otherUser?.id;
          return oldData;
        });
      }
    };

    const deleteOrEditMessage = messageItem => {
      if (messageItem.chatId === Number(id)) {
        editMessage(messageItem);
      }
    };

    const newAssigmentOfResponsable = chatItem => {
      if (chatItem.chat.id === Number(id)) {
        setChatDetailData(Number(id), oldData => {
          oldData = addLastMessage(oldData, chatItem);
          oldData.responsibleId = otherUser?.id;
          return oldData;
        });
      }
    };

    const refetchIfUnarchive = ({ chatId }) => {
      if (chatId === Number(id)) refetch();
    };

    socket.listenEvent(EVENTS_SOCKETS.CHAT_MESSAGE_USER, addMessage);

    socket.listenEvent(
      EVENTS_SOCKETS.UPDATE_FORM_CHAT_ASSIGNMENT,
      newAssigmentOfResponsable,
    );

    socket.listenEvent(EVENTS_SOCKETS.FORM_CHAT_CLOSED, addMessage);

    socket.listenEvent(EVENTS_SOCKETS.UPDATE_TICKET_ASSIGNMENT, addMessage);

    socket.listenEvent(EVENTS_SOCKETS.CLOSE_TICKET, closeTicket);

    socket.listenEvent(EVENTS_SOCKETS.MESSAGE_EDITED, deleteOrEditMessage);

    socket.listenEvent(EVENTS_SOCKETS.TYPING, setUserTyping);

    socket.listenEvent(EVENTS_SOCKETS.CHAT_UNARCHIVED, refetchIfUnarchive);

    return () => {
      deleteChatOfCache(id);
      socket.closeEvent(EVENTS_SOCKETS.CHAT_MESSAGE_USER, addMessage);

      socket.closeEvent(
        EVENTS_SOCKETS.UPDATE_FORM_CHAT_ASSIGNMENT,
        newAssigmentOfResponsable,
      );
      socket.closeEvent(EVENTS_SOCKETS.FORM_CHAT_CLOSED, addMessage);

      socket.closeEvent(EVENTS_SOCKETS.UPDATE_TICKET_ASSIGNMENT, addMessage);

      socket.closeEvent(EVENTS_SOCKETS.CLOSE_TICKET, closeTicket);

      socket.closeEvent(EVENTS_SOCKETS.MESSAGE_EDITED, deleteOrEditMessage);

      socket.closeEvent(EVENTS_SOCKETS.TYPING, setUserTyping);

      socket.closeEvent(EVENTS_SOCKETS.CHAT_UNARCHIVED, refetchIfUnarchive);
    };
  }, [
    socket,
    queryClient,
    id,
    user.id,
    instance.id,
    user.firstName,
    user.lastName,
    user.profilePicture,
  ]);

  useEffect(() => {
    const addMessageReaction = (reaction: MessageReactionSocket) => {
      const { emoji, unified, messageId, chatId, userId } = reaction;

      if (chatId === Number(id)) {
        addChatMessageDataReaction(
          chatId,
          messageId,
          emoji,
          unified,
          userId === user.id,
        );
      }
    };

    const removeMessageReaction = (reaction: MessageReactionSocket) => {
      const { emoji, messageId, chatId, userId } = reaction;

      if (chatId === Number(id)) {
        removeChatMessageDataReaction(
          chatId,
          messageId,
          emoji,
          userId === user.id,
        );
      }
    };

    socket.listenEvent(EVENTS_SOCKETS.NEW_MESSAGE_REACTION, addMessageReaction);

    socket.listenEvent(
      EVENTS_SOCKETS.REMOVE_MESSAGE_REACTION,
      removeMessageReaction,
    );

    return () => {
      socket.closeEvent(
        EVENTS_SOCKETS.NEW_MESSAGE_REACTION,
        addMessageReaction,
      );

      socket.closeEvent(
        EVENTS_SOCKETS.REMOVE_MESSAGE_REACTION,
        removeMessageReaction,
      );
    };
  }, [socket, id, user.id]);

  const handleFeedback = async (value: number) => {
    try {
      const ticketId = data?.data?.id;
      const topicId = data?.data?.topic?.id;
      const agentId = data?.data?.responsibleId;

      await feedbackMutation.mutateAsync({
        id: Number(ticketId),
        data: { value },
      });

      logEvent(EventName.TOPIC_TICKET_RATING, {
        topicId,
        ticketId,
        agentId,
        rating: value,
      });
      queryClient.invalidateQueries(chatKeys.detail(id));
      enqueueSnackbar({
        title: t('SUCCESS_QUALIFYING_TICKET'),
        variant: 'success',
      });
    } catch (err) {
      showGeneralError(err, t('ERROR_QUALIFYING_TICKET'));
    }
  };

  const {
    chat = undefined,
    topic = undefined,
    status = undefined,
    form = undefined,
    chatType = undefined,
  } = data?.data || {};

  const eventProperties = useMemo(
    () => ({
      chatId: chat?.id,
      userId: otherUser?.id,
    }),
    [chat?.id, otherUser?.id],
  );

  useTimeInScreen(getEventNameChat(chatType), eventProperties);

  const action = getAction(status);

  const withAction = canMakeAction(action, data?.data, user.id, otherUser);
  const toQualify = withAction && action === 'QUALIFIED';

  const menuOptions = [
    {
      id: 'archive-chat',
      enabled: isGroupOrRegular(chatType),
      option: <ArchiveChatOption thread={data?.data} />,
    },
    {
      id: 'left-group',
      enabled: chatType === ChatType.REGULAR_GROUP,
      option: <LeftGroupOption thread={data?.data} />,
    },
  ];

  const getEnd = () => {
    if (topic?.deleted) {
      return (
        <ChatInfo
          body={t('TICKETS:WARNING_TICKET_TOPIC_DELETED')}
          plainText
        />
      );
    }
    if (toQualify && chatType !== 'FORM') {
      return <ChatQualify onFeedback={handleFeedback} />;
    }

    return null;
  };

  const handleOpenGroupInfo = () => setIsGroupInfoOpen(true);

  const handleCloseGroupInfo = () => setIsGroupInfoOpen(false);

  const isGroupChat = data?.data?.chatType === ChatType.REGULAR_GROUP;

  return (
    <>
      <ChatThreadContainer
        inViewMessagesEnd={inViewMessagesEnd}
        sx={{
          display: 'flex',
          flexDirection: 'column',
          width: '100%',
          minWidth: 0,
        }}
      >
        {!isLoading && chat && (!!messages?.length || isNewChat) && (
          <>
            <ChatThreadToolbar
              otherUser={otherUser}
              thread={data?.data}
              menuOptions={menuOptions}
              OnOpenGroupInfo={handleOpenGroupInfo}
              participants={participants}
              isGroupInfoOpen={isGroupInfoOpen}
            />
            <Box className="messagesContainer">
              <ChatMessages
                id={Number(id)}
                formData={form}
                messages={messages}
                chatType={chatType}
                chatStatus={status}
                unreadMessages={chat?.unreadMessages}
                markedAsUnread={chat?.markedAsUnread}
                topic={topic}
                isFetchingNextPage={isFetchingNextPage}
                hasNextPage={hasNextPage}
                fetchNextPage={fetchNextPage}
                lastPage={lastPage}
                end={getEnd()}
                messagesEndRef={messagesEndRef}
                setRef={setRef}
                inViewMessagesEnd={inViewMessagesEnd}
              />
            </Box>
            {typing && (
              <Box
                component="img"
                src={typingLogo}
                className="typingLogo"
              />
            )}
            {otherUser?.deleted && !isLoading && (
              <Snackbar
                anchorOrigin={{
                  vertical: 'bottom',
                  horizontal: 'center',
                }}
                open={otherUser?.deleted}
                message={t('USER_DELETED_FEEDBACK')}
                sx={{
                  position: 'absolute',
                  transform: 'translate(-50%, 50%)',
                  width: '100%',
                  bottom: '80px !important',
                  '& .MuiPaper-root': {
                    borderRadius: '4px',
                  },
                }}
              />
            )}
            {!isReadOnlyChat(status, chatType, false, topic?.deleted) &&
              !isSurveyThread &&
              canSendMessage(chatType, canViewChats, canViewTickets) &&
              !isLoading &&
              !otherUser?.deleted && (
                <ChatThreadFooter
                  messageAddProps={{
                    id: Number(id),
                    enableTrackAmplitude:
                      chatType === ChatType.REGULAR_GROUP ||
                      chatType === ChatType.REGULAR,
                    otherUserId: otherUser?.id,
                    chatType,
                  }}
                />
              )}
          </>
        )}
      </ChatThreadContainer>

      {isGroupInfoOpen && !isLoading && isGroupChat && (
        <Drawer
          id="chat-thread-info-drawer"
          variant="persistent"
          anchor="right"
          open={isGroupInfoOpen}
          onClose={handleCloseGroupInfo}
          PaperProps={{
            sx: { width: '340px' },
          }}
          sx={{
            width: '340px',
            transition: 'transform 0.3s ease-in-out',
            willChange: 'transform',
            transform: isGroupInfoOpen ? 'translateX(0%)' : 'translateX(100%)',
          }}
        >
          <ChatThreadInfo
            id={chat?.id}
            onClose={handleCloseGroupInfo}
          />
        </Drawer>
      )}
    </>
  );
};

export default ChatThread;
