import { FC, useEffect, useState } from 'react';
import { Helmet } from 'react-helmet-async';

import { useInfiniteQuery, useMutation, useQuery } from 'react-query';
import { useLocation } from 'react-router';

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

import Box from '@material-hu/mui/Box';
import Container from '@material-hu/mui/Container';
import Grid from '@material-hu/mui/Grid';
import Stack from '@material-hu/mui/Stack';
import Typography from '@material-hu/mui/Typography';

import HuChip from '@material-hu/components/design-system/Chip';
import HuSearch from '@material-hu/components/design-system/Inputs/Search';

import { EVENTS_SOCKETS } from 'src/constants/sockets';
import { useAuth } from 'src/contexts/JWTContext';
import { useSocket } from 'src/contexts/SocketContext';
import useCustomServerTranslation from 'src/hooks/useCustomServerTranslation';
import useGeneralError from 'src/hooks/useGeneralError';
import useHuGoTheme from 'src/hooks/useHuGoTheme';
import {
  getArticleList,
  clearNewsNotifications,
  getArticlesTopics,
} from 'src/services/news';
import { ArticleReactionSocket } from 'src/types/news';
import { formatTitle } from 'src/utils/helmetUtils';
import { useLokaliseTranslation as useTranslation } from 'src/utils/i18n';

import InfiniteList from 'src/components/list/InfiniteList';
import { SearchEmptyState } from 'src/components/SearchEmptyState';

import NewsCard from './components/NewsCard';
import NewsListSkeleton, {
  TopicChipsSkeleton,
} from './components/NewsListSkeleton';
import {
  newsKeys,
  addArticleListDataReaction,
  increaseCommentAmount,
  decreaseCommentAmount,
  removeArticleListDataReaction,
  clearNewsNotifications as clearNotificationsQuery,
} from './queries';

type LocationState = {
  activeFilter: number;
};

const News: FC = () => {
  const { t } = useTranslation(['news', 'general']);
  const title = useCustomServerTranslation({
    module: 'ARTICLES',
    defaultTranslationKey: 'news:articles',
  });
  const HuGoThemeProvider = useHuGoTheme();
  const socket = useSocket();
  const { user: loggedUser } = useAuth();
  const showGeneralError = useGeneralError();
  const { state } = useLocation();
  const { activeFilter } = (state as LocationState) || { activeFilter: null };

  const [filter, setFilter] = useState<number | null>(activeFilter);
  const [searchValue, setSearchValue] = useState('');
  const debouncedSearch = useDebounce(searchValue, 300);
  const searchQuery = debouncedSearch.trim();

  const newsQueryKey = newsKeys.listFilter(filter, searchQuery);
  const {
    data: newsData,
    isLoading,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery(
    newsQueryKey,
    ({ pageParam = undefined }: { pageParam?: string }) =>
      getArticleList(pageParam, filter, searchQuery),
    {
      getNextPageParam: lastPage =>
        lastPage.data?.length > 0
          ? lastPage.data[lastPage.data.length - 1].publicationDateTime
          : undefined,
      onError: err => showGeneralError(err, t('news:error_loading_articles')),
    },
  );

  const handleChangeFilter = (id: number | null) => {
    if (filter == id) setFilter(null);
    else setFilter(id);
  };

  const { data: newsTopics = [], isLoading: topicsLoading } = useQuery(
    newsKeys.topics(),
    () => getArticlesTopics(),
    {
      select: r => r.data.sort((a, b) => a.name.localeCompare(b.name)),
    },
  );

  const { mutate: clearNotificationsMutate } = useMutation(
    clearNewsNotifications,
    {
      onMutate: () => {
        clearNotificationsQuery();
      },
    },
  );

  useEffect(() => {
    clearNotificationsMutate();
  }, []);

  const allArticles = newsData?.pages.flatMap(page => page.data ?? []) ?? [];

  const apiHasArticles = allArticles.length > 0;

  const isSearchActive = searchQuery.length > 0;

  const showNoArticlesAtAll =
    !isLoading && !!newsData && !apiHasArticles && !isSearchActive;

  const showSearchNoResults =
    !isLoading &&
    !isFetchingNextPage &&
    !!newsData &&
    isSearchActive &&
    !apiHasArticles &&
    !hasNextPage;

  const listIsEmpty = showNoArticlesAtAll || showSearchNoResults;

  const emptyStateLabel = showSearchNoResults ? (
    <SearchEmptyState label={t('news:no_topics')} />
  ) : (
    t('news:no_articles')
  );

  const addArticleReactionInCache = (reaction: ArticleReactionSocket) => {
    const { emoji, userId, articleId, unified } = reaction;

    addArticleListDataReaction(
      filter,
      searchQuery,
      articleId,
      emoji,
      unified,
      userId === loggedUser?.id,
    );
  };

  const removeArticleReactionInCache = (
    reaction: Omit<ArticleReactionSocket, 'unified'>,
  ) => {
    const { emoji, userId, articleId } = reaction;
    removeArticleListDataReaction(
      filter,
      searchQuery,
      articleId,
      emoji,
      userId === loggedUser?.id,
    );
  };

  useEffect(() => {
    const increaseComments = (data: { articleId: number }) =>
      increaseCommentAmount(data.articleId, filter, searchQuery);

    const decreaseComments = (data: { articleId: number }) =>
      decreaseCommentAmount(data.articleId, filter, searchQuery);

    const addArticleReaction = (data: ArticleReactionSocket) =>
      loggedUser?.id !== data.userId && addArticleReactionInCache(data);

    const removeArticleReaction = (
      data: Omit<ArticleReactionSocket, 'unified'>,
    ) => loggedUser?.id !== data.userId && removeArticleReactionInCache(data);

    socket.listenEvent(
      EVENTS_SOCKETS.ARTICLE_COMMENT_CREATED,
      increaseComments,
    );

    socket.listenEvent(
      EVENTS_SOCKETS.ARTICLE_COMMENT_DELETED,
      decreaseComments,
    );

    socket.listenEvent(EVENTS_SOCKETS.NEW_ARTICLE_REACTION, addArticleReaction);

    socket.listenEvent(
      EVENTS_SOCKETS.REMOVE_ARTICLE_REACTION,
      removeArticleReaction,
    );

    return () => {
      socket.closeEvent(
        EVENTS_SOCKETS.ARTICLE_COMMENT_CREATED,
        increaseComments,
      );

      socket.closeEvent(
        EVENTS_SOCKETS.ARTICLE_COMMENT_DELETED,
        decreaseComments,
      );

      socket.closeEvent(
        EVENTS_SOCKETS.NEW_ARTICLE_REACTION,
        addArticleReaction,
      );

      socket.closeEvent(
        EVENTS_SOCKETS.REMOVE_ARTICLE_REACTION,
        removeArticleReaction,
      );
    };
  }, [socket, loggedUser, filter, searchQuery]);

  return (
    <HuGoThemeProvider>
      <Helmet>
        <title>{formatTitle(title)}</title>
      </Helmet>
      <Box
        sx={{
          backgroundColor: theme => theme.palette.new.background.layout.default,
          minHeight: '100%',
          pt: 4,
          pb: 8,
        }}
      >
        <Container maxWidth="xl">
          <Stack sx={{ gap: 2, mb: 3 }}>
            <Typography
              color={theme => theme.palette.new.text.neutral.default}
              variant="globalXL"
              fontWeight="semiBold"
              component="h1"
            >
              {title}
            </Typography>
            <HuSearch
              variant="custom"
              value={searchValue}
              onChange={setSearchValue}
              placeholder={t('news:query_placeholder')}
              sx={{
                maxWidth: 720,
                width: '100%',
                '& .MuiInputBase-root': {
                  backgroundColor: theme =>
                    theme.palette.new.background.elements.default,
                },
              }}
            />
            {topicsLoading ? (
              <TopicChipsSkeleton />
            ) : (
              <Stack
                flexDirection="row"
                sx={{ flexWrap: 'wrap', gap: 1 }}
              >
                <HuChip
                  label={t('news:all')}
                  isSelected={!filter}
                  onClick={() => handleChangeFilter(null)}
                />
                {newsTopics.map(topic => (
                  <HuChip
                    key={topic.id}
                    label={topic.name}
                    isSelected={filter === topic.id}
                    onClick={() => handleChangeFilter(topic.id)}
                  />
                ))}
              </Stack>
            )}
          </Stack>
          <InfiniteList
            isSuccess={!!newsData}
            isLoading={isLoading}
            isEmpty={listIsEmpty}
            noResultsLabel={emptyStateLabel}
            fetchNextPage={fetchNextPage}
            hasNextPage={hasNextPage}
            isFetchingNextPage={isFetchingNextPage}
            renderSkeleton={<NewsListSkeleton />}
            loadingSkeleton
          >
            <Grid
              container
              spacing={3}
            >
              {allArticles.map(post => (
                <Grid
                  item
                  key={post.id}
                  lg={4}
                  md={6}
                  xs={12}
                >
                  <NewsCard
                    {...post}
                    filter={filter}
                    searchQuery={searchQuery}
                  />
                </Grid>
              ))}
            </Grid>
          </InfiniteList>
        </Container>
      </Box>
    </HuGoThemeProvider>
  );
};

export default News;
