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

import { AxiosError, AxiosResponse } from 'axios';

import { queryClient } from 'src/config/react-query';
import { parseCreateViewFormValues } from 'src/pages/dashboard/serviceManagement/utils';
import {
  createView,
  deleteView,
  editView,
  getView,
  getViews,
  renameView,
  reorderView,
} from 'src/pages/dashboard/serviceManagement/services';
import {
  FilterFormValues,
  ServiceManagementError,
  View,
  ViewForm,
  ViewListItem,
} from 'src/pages/dashboard/serviceManagement/types';
import { Pagination } from 'src/types/services';
import { useLokaliseTranslation } from 'src/utils/i18n';

import { DUPLICATE_ENTITY_ERROR_CODE } from '../constants';
import { serviceManagementKeys } from '../queries';
import { serviceManagementRoutes } from '../routes';

import useSnackbar from './useSnackbar';

export type EditView = {
  id: string;
  name: string;
  filters: FilterFormValues;
};

const useViews = (
  viewId?: string,
  onViewSuccessfullyFetched?: (view: View) => void,
) => {
  const { t } = useLokaliseTranslation('service_management');
  const { showSnackbar } = useSnackbar();
  const navigate = useNavigate();

  const { data: currentView, isLoading: isLoadingCurrentView } = useQuery(
    serviceManagementKeys.views.detail(viewId!),
    () => getView(viewId!),
    {
      select: res => res.data,
      enabled: !!viewId,
      keepPreviousData: false,
      onSuccess: res => {
        onViewSuccessfullyFetched?.(res);
      },
    },
  );

  const viewsQuery = useInfiniteQuery(
    serviceManagementKeys.views.list(),
    ({ pageParam = 1 }) => getViews(pageParam),
    {
      getNextPageParam: lastPage =>
        lastPage.data.page === lastPage.data.totalPages
          ? undefined
          : lastPage.data.page + 1,
    },
  );

  const deleteViewMutation = useMutation(
    (view: ViewListItem) => deleteView(view.id),
    {
      onSuccess: (_, deletedView) => {
        showSnackbar({
          title: t('VIEW_DELETE_SUCCESS', { name: deletedView.name }),
          variant: 'success',
        });
        queryClient.invalidateQueries(serviceManagementKeys.views.list());
      },
      onError: () => {
        showSnackbar({ title: t('VIEW_DELETE_ERROR'), variant: 'error' });
      },
    },
  );

  const reorderViewMutation = useMutation(
    ({ id, order }: { id: string; order: number }) =>
      reorderView(id, order + 1),
    {
      onMutate: async ({ id, order }) => {
        // Cancel any ongoing refetches so they don't overwrite our optimistic update
        await queryClient.cancelQueries(serviceManagementKeys.views.list());
        // Snapshot of the previous value
        const previousData = queryClient.getQueryData<
          InfiniteData<AxiosResponse<Pagination<ViewListItem>>>
        >(serviceManagementKeys.views.list());

        // If no previous data, nothing to optimistically update
        if (!previousData) {
          return { previousData: null };
        }

        const { pages, pageParams } = previousData;
        const allViews = pages.flatMap(page => page.data.items);
        const currentIndex = allViews.findIndex(view => view.id === id);

        if (currentIndex === -1) {
          // If the item isn't found, just return the previous data without changes
          return { previousData };
        }

        const [movedView] = allViews.splice(currentIndex, 1);
        allViews.splice(order, 0, movedView);

        // Use the first page's pagination info as a reference
        const limit = pages[0].data.limit;
        const totalCount = allViews.length;
        const totalPages = Math.ceil(totalCount / limit);

        // Re-chunk the allViews into pages matching the original number of pages
        const newAxiosPages = pages.map((page, pageIndex) => {
          const start = pageIndex * limit;
          const end = start + limit;
          const pageItems = allViews.slice(start, end);

          return {
            ...page,
            data: {
              ...page.data,
              items: pageItems,
              count: totalCount,
              totalPages: totalPages,
              page: pageIndex + 1,
            },
          };
        });

        // Update the query data optimistically
        queryClient.setQueryData(serviceManagementKeys.views.list(), {
          pages: newAxiosPages,
          pageParams,
        });

        // Return context for rollback in case of error
        return { previousData };
      },
      onError: (_, __, context) => {
        // Roll back to previous data if mutation fails
        if (context?.previousData) {
          queryClient.setQueryData(
            serviceManagementKeys.views.list(),
            context.previousData,
          );
        }
        showSnackbar({ title: t('VIEW_REORDER_ERROR'), variant: 'error' });
      },
      onSuccess: () => {
        showSnackbar({ title: t('VIEW_REORDER_SUCCESS'), variant: 'success' });
      },
      onSettled: () => {
        // re-fetch query to ensure data is up to date
        queryClient.invalidateQueries(serviceManagementKeys.views.list());
      },
    },
  );

  const renameViewMutation = useMutation(
    ({ id, name }: { id: string; name: string }) => renameView(id, name),
    {
      onSuccess: async () => {
        await queryClient.invalidateQueries(serviceManagementKeys.views.all());
        showSnackbar({ title: t('VIEW_RENAME_SUCCESS'), variant: 'success' });
      },
      onError: () => {
        showSnackbar({ title: t('VIEW_RENAME_ERROR'), variant: 'error' });
      },
    },
  );

  const createViewMutation = useMutation(
    (values: ViewForm) => createView(parseCreateViewFormValues(values)),
    {
      onSuccess: async response => {
        await queryClient.invalidateQueries(serviceManagementKeys.views.all());
        navigate(
          serviceManagementRoutes.agentSide.viewDetail(response.data.id),
        );
        showSnackbar({ title: t('VIEW_CREATE_SUCCESS'), variant: 'success' });
      },
      onError: (error: AxiosError<ServiceManagementError>) => {
        if (error?.response?.data?.code === DUPLICATE_ENTITY_ERROR_CODE) {
          showSnackbar({ title: t('VIEW_CREATE_DUPLICATE'), variant: 'error' });
          return;
        }
        showSnackbar({ title: t('VIEW_CREATE_ERROR'), variant: 'error' });
      },
    },
  );

  const saveViewMutation = useMutation(
    ({ id, filters, name }: EditView) =>
      editView(id, parseCreateViewFormValues({ ...filters, name })),
    {
      onSuccess: async () => {
        await queryClient.invalidateQueries(serviceManagementKeys.views.all());
        showSnackbar({
          title: t('VIEW_SAVE_SUCCESS', { name: currentView?.name }),
          variant: 'success',
        });
      },
      onError: () => {
        showSnackbar({
          title: t('VIEW_SAVE_ERROR', { name: currentView?.name }),
          variant: 'error',
        });
      },
    },
  );

  return {
    currentView,
    isLoadingCurrentView,
    viewsQuery,
    deleteViewMutation,
    renameViewMutation,
    reorderViewMutation,
    createViewMutation,
    saveViewMutation,
  };
};

export default useViews;
