import { useEffect } from 'react';

import { queryClient } from 'src/config/react-query';
import { EVENTS_SOCKETS } from 'src/constants/sockets';
import { useAuth } from 'src/contexts/JWTContext';
import { useSocket } from 'src/contexts/SocketContext';
import { clearGroupsNotifications } from 'src/services/groups';
import { type Comment } from 'src/types/comments';
import { type GroupPost, GroupPostStates } from 'src/types/groups';
import { type Reaction } from 'src/types/reaction';
import { addReaction, removeReaction } from 'src/utils/reactions';

import { FeedSource } from '../../../../types/feed';
import {
  addFeedAndPinListDataReaction,
  addKeyUpdatesListDataReaction,
  addVoteInPoll,
  feedKeys,
  postDeleted,
  postUpdated,
  removeFeedAndPinListDataReaction,
  removeKeyUpdatesListDataReaction,
} from '../../feed/queries';
import {
  addUserGroupFeedListDataReaction,
  profileKeys,
  removeUserGroupFeedListDataReaction,
} from '../../profile/queries';
import {
  addGroupFeedListDataReaction,
  dropGroupChildComment,
  dropGroupComment,
  groupPostDeleted,
  groupPostUpdated,
  invalidateGroupNotifications,
  invalidateGroupScheduledPostList,
  pushGroupFeedListData,
  removeGroupFeedListDataReaction,
  unshiftGroupChildComment,
  unshiftGroupComment,
  updateGroupChildComment,
  updateGroupComment,
  updateGroupReactionChildCommentData,
  updateGroupReactionCommentData,
  updateGroupReactionCommentDataForFeed,
  updateGroupVoteInPoll,
} from '../queries';

type Props = {
  enabled?: boolean;
  source: FeedSource;
  profileUserId?: number; // Optional, only used for PROFILE_GROUP_POSTS
  onAddNewPost?: () => void; // Optional, only used for FEED source
};

type NewPostEvent = GroupPost;

type NewCommentEvent = {
  comment: Comment;
  groupId: number;
  postId: number;
};

type ReactionEvent = {
  reaction: {
    emoji: string;
    unified: string;
    isLoggedUserReaction: boolean;
  };
  groupId: number;
  postId: number;
};

type RemovePostEvent = {
  groupId: number;
  state: GroupPostStates;
};

type UpdatePostEvent = {
  groupId: number;
  id: number;
};

type RemoveCommentEvent = {
  commentId: number;
  parentId: number | null;
  groupId: number;
  postId: number;
};

type UpdateCommentEvent = {
  comment: Comment;
  groupId: number;
  postId: number;
};

type RemovePostReactionEvent = {
  emoji: string;
  groupId: number;
  postId: number;
  userId: number;
};

type CommentReactionEvent = {
  commentId: number;
  groupId: number;
  postId: number;
  reaction: {
    emoji: string;
    unified: string;
    isLoggedUserReaction: boolean;
  };
  parentId: number | null;
};

type RemoveCommentReactionEvent = {
  commentId: number;
  emoji: string;
  groupId: number;
  postId: number;
  userId: number;
  parentId: number | null;
};

type PollAnswerEvent = {
  groupId: number;
  postId: number;
  pollOptionId: number;
  userId: number;
};

const useAttachCommonGroupListeners = ({
  enabled = true,
  source,
  profileUserId,
  onAddNewPost,
}: Props) => {
  const socket = useSocket();
  const { user } = useAuth();
  const userId = user?.id;

  useEffect(() => {
    if (!enabled) {
      return;
    }

    const addNewPost = (post: NewPostEvent) => {
      if (source === FeedSource.FEED) {
        onAddNewPost?.();
      } else if (source === FeedSource.KEY_UPDATES) {
        queryClient.invalidateQueries(feedKeys.keyUpdates.all());
      } else if (source === FeedSource.PROFILE_GROUP_POSTS) {
        queryClient.invalidateQueries(profileKeys.groupFeed.all());
      } else {
        if (post.user.id !== userId)
          pushGroupFeedListData(post, post.groupId.toString());
        clearGroupsNotifications(`${post.groupId}`).then(() => {
          // Explicitly invalidate sidebar notifications as fallback
          // in case socket event is not received
          invalidateGroupNotifications();
        });
      }
    };

    const addNewComment = ({ comment, groupId, postId }: NewCommentEvent) => {
      if (comment.parentId !== null) {
        unshiftGroupChildComment(
          postId,
          `${groupId}`,
          comment.parentId,
          comment,
        );
      } else {
        unshiftGroupComment(postId, `${groupId}`, comment);
        if (source === FeedSource.PROFILE_GROUP_POSTS) {
          queryClient.invalidateQueries(profileKeys.groupFeed.all());
        }
      }
    };

    const addNewReaction = ({ reaction, groupId, postId }: ReactionEvent) => {
      if (source === FeedSource.FEED) {
        addFeedAndPinListDataReaction(
          postId,
          reaction.emoji,
          reaction.unified,
          reaction.isLoggedUserReaction,
        );
      } else if (source === FeedSource.KEY_UPDATES) {
        addKeyUpdatesListDataReaction(
          postId,
          reaction.emoji,
          reaction.unified,
          reaction.isLoggedUserReaction,
        );
      } else if (source === FeedSource.PROFILE_GROUP_POSTS) {
        if (profileUserId) {
          addUserGroupFeedListDataReaction(
            profileUserId,
            postId,
            reaction.emoji,
            reaction.unified,
            reaction.isLoggedUserReaction,
          );
        }
      } else {
        addGroupFeedListDataReaction(
          postId,
          `${groupId}`,
          reaction.emoji,
          reaction.unified,
          reaction.isLoggedUserReaction,
        );
      }
    };

    const removePost = ({ groupId, state }: RemovePostEvent) => {
      if (source === FeedSource.FEED) {
        postDeleted();
      } else if (source === FeedSource.KEY_UPDATES) {
        queryClient.invalidateQueries(feedKeys.keyUpdates.all());
      } else if (source === FeedSource.PROFILE_GROUP_POSTS) {
        queryClient.invalidateQueries(profileKeys.groupFeed.all());
      } else {
        state === GroupPostStates.SCHEDULED
          ? invalidateGroupScheduledPostList(groupId)
          : groupPostDeleted(`${groupId}`);
      }
    };

    const updatePost = ({ groupId, id }: UpdatePostEvent) => {
      if (source === FeedSource.FEED) {
        postUpdated(id);
      } else if (source === FeedSource.KEY_UPDATES) {
        queryClient.invalidateQueries(feedKeys.keyUpdates.all());
      } else if (source === FeedSource.PROFILE_GROUP_POSTS) {
        queryClient.invalidateQueries(profileKeys.groupFeed.all());
      } else {
        groupPostUpdated(`${groupId}`, id);
      }
    };

    const removeComment = ({
      commentId,
      parentId,
      groupId,
      postId,
    }: RemoveCommentEvent) => {
      parentId !== null
        ? dropGroupChildComment(postId, `${groupId}`, parentId, commentId)
        : dropGroupComment(postId, `${groupId}`, commentId);
      if (source === FeedSource.PROFILE_GROUP_POSTS) {
        queryClient.invalidateQueries(profileKeys.groupFeed.all());
      }
    };

    const updateComment = ({
      comment,
      groupId,
      postId,
    }: UpdateCommentEvent) => {
      comment.parentId !== null
        ? updateGroupChildComment(
            postId,
            `${groupId}`,
            comment.parentId,
            comment,
          )
        : updateGroupComment(postId, `${groupId}`, comment);
      if (source === FeedSource.PROFILE_GROUP_POSTS) {
        queryClient.invalidateQueries(profileKeys.groupFeed.all());
      }
    };

    const removePostReaction = ({
      emoji,
      groupId,
      postId,
      userId: reactionUserId,
    }: RemovePostReactionEvent) => {
      if (source === FeedSource.FEED) {
        removeFeedAndPinListDataReaction(
          postId,
          emoji,
          reactionUserId === userId,
        );
      } else if (source === FeedSource.KEY_UPDATES) {
        removeKeyUpdatesListDataReaction(
          postId,
          emoji,
          reactionUserId === userId,
        );
      } else if (source === FeedSource.PROFILE_GROUP_POSTS) {
        if (profileUserId) {
          removeUserGroupFeedListDataReaction(
            profileUserId,
            postId,
            emoji,
            reactionUserId === userId,
          );
        }
      } else {
        removeGroupFeedListDataReaction(
          postId,
          `${groupId}`,
          emoji,
          reactionUserId === userId,
        );
      }
    };

    const addCommentReaction = ({
      commentId,
      groupId,
      postId,
      reaction,
      parentId,
    }: CommentReactionEvent) => {
      const updater = (reactions: Reaction[] | undefined) =>
        addReaction(
          reactions,
          reaction.emoji,
          reaction.unified,
          reaction.isLoggedUserReaction,
        );
      if (
        source === FeedSource.FEED ||
        source === FeedSource.KEY_UPDATES ||
        source === FeedSource.PROFILE_GROUP_POSTS
      )
        parentId !== null
          ? updateGroupReactionChildCommentData(
              postId,
              `${groupId}`,
              parentId,
              commentId,
              updater,
            )
          : updateGroupReactionCommentDataForFeed(
              postId,
              `${groupId}`,
              commentId,
              updater,
            );
      else
        parentId !== null
          ? updateGroupReactionChildCommentData(
              postId,
              `${groupId}`,
              parentId,
              commentId,
              updater,
            )
          : updateGroupReactionCommentData(
              postId,
              `${groupId}`,
              commentId,
              updater,
            );
    };

    const removeCommentReaction = ({
      commentId,
      emoji,
      groupId,
      postId,
      userId: reactionUserId,
      parentId,
    }: RemoveCommentReactionEvent) => {
      const updater = (reactions: Reaction[] | undefined) =>
        removeReaction(reactions, emoji, reactionUserId === userId);
      if (
        source === FeedSource.FEED ||
        source === FeedSource.KEY_UPDATES ||
        source === FeedSource.PROFILE_GROUP_POSTS
      )
        parentId !== null
          ? updateGroupReactionChildCommentData(
              postId,
              `${groupId}`,
              parentId,
              commentId,
              updater,
            )
          : updateGroupReactionCommentDataForFeed(
              postId,
              `${groupId}`,
              commentId,
              updater,
            );
      else
        parentId !== null
          ? updateGroupReactionChildCommentData(
              postId,
              `${groupId}`,
              parentId,
              commentId,
              updater,
            )
          : updateGroupReactionCommentData(
              postId,
              `${groupId}`,
              commentId,
              updater,
            );
    };

    const updatePollAnswer = ({
      groupId,
      postId,
      pollOptionId,
      userId: pollUserId,
    }: PollAnswerEvent) => {
      if (source === FeedSource.FEED) {
        addVoteInPoll(postId, pollOptionId, pollUserId === userId);
      } else if (source === FeedSource.KEY_UPDATES) {
        queryClient.invalidateQueries(feedKeys.keyUpdates.all());
      } else if (source === FeedSource.GROUP && pollUserId !== userId) {
        updateGroupVoteInPoll(
          postId,
          `${groupId}`,
          pollOptionId,
          pollUserId === userId,
        );
      }
    };

    socket.listenEvent(EVENTS_SOCKETS.GROUP_POST_CREATED, addNewPost);
    socket.listenEvent(EVENTS_SOCKETS.GROUP_POST_EDITED, updatePost);
    socket.listenEvent(EVENTS_SOCKETS.GROUP_POST_DELETED, removePost);
    socket.listenEvent(
      EVENTS_SOCKETS.GROUP_POST_COMMENT_CREATED,
      addNewComment,
    );
    socket.listenEvent(EVENTS_SOCKETS.GROUP_POST_COMMENT_EDITED, updateComment);
    socket.listenEvent(
      EVENTS_SOCKETS.GROUP_POST_COMMENT_DELETED,
      removeComment,
    );
    socket.listenEvent(
      EVENTS_SOCKETS.GROUP_POST_REACTION_CREATED,
      addNewReaction,
    );
    socket.listenEvent(
      EVENTS_SOCKETS.GROUP_POST_REACTION_DELETED,
      removePostReaction,
    );
    socket.listenEvent(
      EVENTS_SOCKETS.GROUP_POST_COMMENT_REACTION_CREATED,
      addCommentReaction,
    );
    socket.listenEvent(
      EVENTS_SOCKETS.GROUP_POST_COMMENT_REACTION_DELETED,
      removeCommentReaction,
    );
    socket.listenEvent(
      EVENTS_SOCKETS.GROUP_POST_POLL_ANSWER_CREATED,
      updatePollAnswer,
    );

    return () => {
      socket.closeEvent(EVENTS_SOCKETS.GROUP_POST_CREATED, addNewPost);
      socket.closeEvent(EVENTS_SOCKETS.GROUP_POST_EDITED, updatePost);
      socket.closeEvent(EVENTS_SOCKETS.GROUP_POST_DELETED, removePost);
      socket.closeEvent(
        EVENTS_SOCKETS.GROUP_POST_COMMENT_CREATED,
        addNewComment,
      );
      socket.closeEvent(
        EVENTS_SOCKETS.GROUP_POST_COMMENT_EDITED,
        updateComment,
      );
      socket.closeEvent(
        EVENTS_SOCKETS.GROUP_POST_COMMENT_DELETED,
        removeComment,
      );
      socket.closeEvent(
        EVENTS_SOCKETS.GROUP_POST_REACTION_CREATED,
        addNewReaction,
      );
      socket.closeEvent(
        EVENTS_SOCKETS.GROUP_POST_REACTION_DELETED,
        removePostReaction,
      );
      socket.closeEvent(
        EVENTS_SOCKETS.GROUP_POST_COMMENT_REACTION_CREATED,
        addCommentReaction,
      );
      socket.closeEvent(
        EVENTS_SOCKETS.GROUP_POST_COMMENT_REACTION_DELETED,
        removeCommentReaction,
      );
      socket.closeEvent(
        EVENTS_SOCKETS.GROUP_POST_POLL_ANSWER_CREATED,
        updatePollAnswer,
      );
    };
  }, [userId, enabled, socket]);
};

export default useAttachCommonGroupListeners;
