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

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

import Button from '@material-hu/components/design-system/Buttons/Button';

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 {
  Status,
  ChangeChannelOption,
} from 'src/pages/dashboard/tickets/components';
import { useTranslation } from 'src/pages/dashboard/tickets/i18n';
import {
  ticketKeys,
  addTicketMessageDataReaction,
  removeTicketMessageDataReaction,
  editTicketMessage,
  clearTicketDetailUnreadMessages,
  updateTransitionTicket,
} from 'src/pages/dashboard/tickets/queries';
import { getChatMessages } from 'src/services/chats';
import { assignTicket, closeTicket } from 'src/services/tickets';
import { EventName } from 'src/types/amplitude';
import { MessageReactionSocket } from 'src/types/reaction';
import {
  isReadOnlyChat,
  getAction,
  canMakeAction,
  iAmResponsible,
  getMessages,
} from 'src/utils/chats';

import {
  ChatMessages,
  ChatThreadToolbar,
  ChatThreadContainer,
  ChatThreadFooter,
} from 'src/components/dashboard/chat/ChatThread';

export type TicketThreadProps = {};

const TicketThread: FC<TicketThreadProps> = () => {
  const { id } = useParams();
  const showGeneralError = useGeneralError();
  const { t } = useTranslation();
  const queryClient = useQueryClient();
  const socket = useSocket();
  const { user, instance } = useAuth();

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

  const assignMutation = useMutation(assignTicket, {
    onSuccess: response => {
      const { data: thread } = response;
      logEvent(EventName.TOPIC_TICKET_ASSIGN, {
        topicId: thread.topic?.id,
        ticketId: thread.id,
        agentId: user.id,
      });
      updateTransitionTicket(id, user, ticketKeys.list.unassigned());
    },
    onError: error => {
      showGeneralError(error, t('ERROR_ASSIGNING_TICKET'));
    },
  });

  const closeMutation = useMutation(closeTicket, {
    onSuccess: response => {
      const { data: thread } = response;
      logEvent(EventName.TOPIC_TICKET_CLOSE, {
        topicId: thread.topic?.id,
        ticketId: thread.id,
        agentId: user.id,
      });
      updateTransitionTicket(id, user, ticketKeys.list.closed());
    },
    onError: error => {
      showGeneralError(error, t('ERROR_CLOSING_TICKET'));
    },
  });

  const [typing, setTyping] = useState(false);
  const [typingCounter, setTypingCounter] = useState(0);

  const debounceTypingCounter = useDebounce(typingCounter, 2000);

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

  const { data, isLoading, otherUser } = useChat(ticketKeys.detail(id), id);

  const {
    data: messageData,
    isFetchingNextPage,
    hasNextPage,
    fetchNextPage,
  } = useInfiniteQuery(
    ticketKeys.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);

  useEffect(
    () => () => {
      clearTicketDetailUnreadMessages(Number(id));
      queryClient.removeQueries(ticketKeys.messages.list(id), { exact: true });
    },
    [id],
  );

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

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

    socket.listenEvent(EVENTS_SOCKETS.TYPING, setUserTyping);

    socket.listenEvent(EVENTS_SOCKETS.MESSAGE_EDITED, deleteOrEditMessage);

    return () => {
      socket.closeEvent(EVENTS_SOCKETS.TYPING, setUserTyping);

      socket.closeEvent(EVENTS_SOCKETS.MESSAGE_EDITED, deleteOrEditMessage);
    };
  }, [socket, id, instance.id, user.id]);

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

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

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

      if (chatId === Number(id)) {
        removeTicketMessageDataReaction(
          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 handleAssign = () => {
    const ticketId = data?.data?.id;
    assignMutation.mutate(Number(ticketId));
  };

  const handleClose = async () => {
    const ticketId = data?.data?.id;
    closeMutation.mutate(Number(ticketId));
  };

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

  const action = getAction(status);
  const withAction = canMakeAction(action, data?.data, user.id, otherUser);
  const toAssign = action === 'ASSIGNED';
  const toChangeChannel = action === 'ASSIGNED' || action === 'CLOSED';

  const isResponsible = iAmResponsible(data?.data, user.id, otherUser);

  const actionHandlers = {
    ASSIGNED: handleAssign,
    CLOSED: handleClose,
  };

  const actionTitles = {
    ASSIGNED: t('ASSIGN_ME_TICKET'),
    CLOSED: t('CLOSE_TICKET'),
  };

  const menuOptions = [
    {
      id: 'new-channel',
      enabled: toChangeChannel,
      option: <ChangeChannelOption thread={data?.data} />,
    },
  ];

  return (
    <ChatThreadContainer inViewMessagesEnd={inViewMessagesEnd}>
      {!isLoading && chat && !!messages.length && (
        <>
          <ChatThreadToolbar
            thread={data?.data}
            otherUser={otherUser}
            menuOptions={menuOptions}
          />
          <Status
            withAction={toAssign || withAction}
            onAction={actionHandlers[action]}
            actionTitle={actionTitles[action]}
          />
          <Box className="messagesContainer">
            <ChatMessages
              id={Number(id)}
              formData={form}
              messages={messages}
              chatType={chatType}
              chatStatus={status}
              unreadMessages={chat?.unreadMessages}
              markedAsUnread={chat?.markedAsUnread}
              topic={topic}
              isResponsible={isResponsible}
              isFetchingNextPage={isFetchingNextPage}
              hasNextPage={hasNextPage}
              fetchNextPage={fetchNextPage}
              messagesEndRef={messagesEndRef}
              setRef={setRef}
              inViewMessagesEnd={inViewMessagesEnd}
              end={
                toAssign && (
                  <Divider>
                    <Button
                      variant="outlined"
                      onClick={handleAssign}
                    >
                      {t('ASSIGN_ME_TICKET')}
                    </Button>
                  </Divider>
                )
              }
            />
          </Box>
          {typing && (
            <Box
              component="img"
              src={typingLogo}
              className="typingLogo"
            />
          )}
          {!isReadOnlyChat(status, chatType, isResponsible, topic?.deleted) && (
            <ChatThreadFooter
              messageAddProps={{
                id: Number(id),
                chatType,
              }}
            />
          )}
        </>
      )}
    </ChatThreadContainer>
  );
};

export default TicketThread;
