import { type RefObject } from 'react';
import { type InfiniteData, useInfiniteQuery, useMutation } from 'react-query';

import { type AxiosResponse } from 'axios';

import { queryClient } from 'src/config/react-query';
import useGeneralError from 'src/hooks/useGeneralError';
import {
  eventsKeys,
  unshiftEventPostComment,
} from 'src/pages/dashboard/events/queries';
import { feedKeys, unshiftComment } from 'src/pages/dashboard/feed/queries';
import {
  groupsKeys,
  unshiftGroupComment,
} from 'src/pages/dashboard/groups/queries';
import {
  getEventPostCommentsPaginated,
  postEventComment,
} from 'src/services/events';
import {
  getGroupPostCommentsPaginated,
  postGroupComment,
} from 'src/services/groups';
import { getPostCommentsPaginated, postComment } from 'src/services/posts';
import { type Comment, type PostCommentPayload } from 'src/types/comments';
import { type CursorPagination } from 'src/types/services';
import { type LiveStreamPost } from 'src/types/stream';
import { type MeUser } from 'src/types/user';
import { getCommentsList, getCurrentCommentsCount } from 'src/utils/comments';
import { useLokaliseTranslation } from 'src/utils/i18n';
import { getListLength, getNextPage } from 'src/utils/pagination';

import { optimistCommentUpdate } from '../../utils';

const COMMENTS_PER_PAGE = 10;

type CommentPageResponse = AxiosResponse<
  CursorPagination<Comment[]> | Comment[]
>;

type CommentQueryConfig = {
  key: unknown[];
  fn: (context: { pageParam?: unknown }) => Promise<CommentPageResponse>;
  getNextPageParam: (
    lastPage: CommentPageResponse,
    allPages: CommentPageResponse[],
  ) => unknown;
};

const normalizeCommentsSelect = (
  response: InfiniteData<CommentPageResponse>,
): InfiniteData<AxiosResponse<Comment[]>> => ({
  pages: response.pages.map(page => ({
    ...page,
    data: 'items' in page.data ? page.data.items : page.data,
  })),
  pageParams: response.pageParams,
});

type UseLiveStreamCommentsParams = {
  post: LiveStreamPost;
  commentsEnabled: boolean;
  commentsEndRef: RefObject<HTMLDivElement | null>;
  currentUser: MeUser | null;
};

const useLiveStreamComments = (params: UseLiveStreamCommentsParams) => {
  const { post, commentsEnabled, commentsEndRef, currentUser } = params;
  const { id, group, event, commentCount } = post;
  const { t } = useLokaliseTranslation('livestream');
  const showGeneralError = useGeneralError();

  const commentQueryConfig: CommentQueryConfig = (() => {
    if (event) {
      return {
        key: eventsKeys.comment.liveStream(id),
        fn: ({ pageParam }) =>
          getEventPostCommentsPaginated(
            event.id,
            id,
            String(pageParam ?? ''),
            COMMENTS_PER_PAGE,
          ),
        getNextPageParam: lastPage =>
          'cursor' in lastPage.data ? lastPage.data.cursor : undefined,
      };
    }
    if (group) {
      return {
        key: groupsKeys.comment.liveStream(id, group.id.toString()),
        fn: ({ pageParam }) =>
          getGroupPostCommentsPaginated(
            id,
            String(pageParam ?? ''),
            group.id,
            COMMENTS_PER_PAGE,
          ),
        getNextPageParam: lastPage =>
          'cursor' in lastPage.data ? lastPage.data.cursor : undefined,
      };
    }
    return {
      key: feedKeys.comment.liveStream(id),
      fn: ({ pageParam }) =>
        getPostCommentsPaginated(id, Number(pageParam ?? 1), COMMENTS_PER_PAGE),
      getNextPageParam: (_, allPages) =>
        getNextPage(getListLength(allPages), COMMENTS_PER_PAGE, allPages),
    };
  })();

  const {
    data,
    isLoading: isLoadingComments,
    fetchNextPage,
    isFetchingNextPage,
    isRefetching,
  } = useInfiniteQuery<CommentPageResponse, Error, AxiosResponse<Comment[]>>(
    commentQueryConfig.key,
    commentQueryConfig.fn,
    {
      getNextPageParam: commentQueryConfig.getNextPageParam,
      select: normalizeCommentsSelect,
      enabled: commentsEnabled,
      onError: () => showGeneralError(t('ERROR_LOADING_COMMENTS')),
    },
  );

  const comments: Comment[] = data ? getCommentsList(data) : [];
  const commentsSorted = comments.toReversed();
  const currentCommentsCount = getCurrentCommentsCount(data);
  const showLoadMoreComments =
    currentCommentsCount < commentCount && !isRefetching;
  const hasNoComments = comments.length === 0 && !isLoadingComments;

  const mutationConfig = (() => {
    if (event) {
      return {
        fn: (body: PostCommentPayload) => postEventComment(event.id, id, body),
        queryKey: eventsKeys.comment.liveStream(id),
        onSuccess: (comment: Comment) =>
          unshiftEventPostComment(id, event.id, comment),
      };
    }
    if (group) {
      return {
        fn: (body: PostCommentPayload) => postGroupComment(id, body, group.id),
        queryKey: groupsKeys.comment.liveStream(id, group.id.toString()),
        onSuccess: (comment: Comment) =>
          unshiftGroupComment(id, group.id.toString(), comment),
      };
    }
    return {
      fn: (body: PostCommentPayload) => postComment(id, body),
      queryKey: feedKeys.comment.liveStream(id),
      onSuccess: (comment: Comment) => unshiftComment(id, comment),
    };
  })();

  const postMutation = useMutation(mutationConfig.fn, {
    onMutate: async newComment => {
      const previousComments = await optimistCommentUpdate(
        currentUser,
        newComment,
        mutationConfig.queryKey,
      );
      return previousComments;
    },
    onSuccess: response => mutationConfig.onSuccess(response?.data),
    onError: (_, __, context) => {
      if (context?.previousComments) {
        queryClient.setQueryData(
          mutationConfig.queryKey,
          context.previousComments,
        );
      }
    },
    onSettled: () => {
      commentsEndRef.current?.scrollIntoView();
    },
  });

  return {
    commentsSorted,
    showLoadMoreComments,
    hasNoComments,
    isLoadingComments,
    isFetchingNextPage,
    fetchNextPage,
    postMutation,
  };
};

export default useLiveStreamComments;
