import { ADD_QUERY_TO_EVENT_STREAM_MUTATION } from '@/sse-db-event/graphql/mutations/AddQueryToEventStreamMutation';
import { REMOVE_QUERY_FROM_EVENT_STREAM_MUTATION } from '@/sse-db-event/graphql/mutations/RemoveQueryFromEventStreamMutation';
import { activeQueryListenersState } from '@/sse-db-event/states/activeQueryListenersState';
import { requiredQueryListenersState } from '@/sse-db-event/states/requiredQueryListenersState';
import { shouldDestroyEventStreamState } from '@/sse-db-event/states/shouldDestroyEventStreamState';
import { sseEventStreamIdState } from '@/sse-db-event/states/sseEventStreamIdState';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { ApolloError, useMutation } from '@apollo/client';
import { isNonEmptyString } from '@sniptt/guards';
import { useEffect } from 'react';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import {
  compareArraysOfObjectsByProperty,
  isDefined,
} from 'twenty-shared/utils';
import { useDebouncedCallback } from 'use-debounce';
import {
  type AddQuerySubscriptionInput,
  type RemoveQueryFromEventStreamInput,
} from '~/generated-metadata/graphql';

export const SSEQuerySubscribeEffect = () => {
  const sseEventStreamId = useRecoilValue(sseEventStreamIdState);

  const [addQueryToEventStream] = useMutation<
    boolean,
    { input: AddQuerySubscriptionInput }
  >(ADD_QUERY_TO_EVENT_STREAM_MUTATION);

  const [removeQueryFromEventStream] = useMutation<
    void,
    { input: RemoveQueryFromEventStreamInput }
  >(REMOVE_QUERY_FROM_EVENT_STREAM_MUTATION);

  const requiredQueryListeners = useRecoilValue(requiredQueryListenersState);
  const activeQueryListeners = useRecoilValue(activeQueryListenersState);

  const updateQueryListeners = useRecoilCallback(
    ({ set, snapshot }) =>
      async () => {
        if (!isDefined(sseEventStreamId)) {
          return;
        }

        const requiredQueryListeners = getSnapshotValue(
          snapshot,
          requiredQueryListenersState,
        );

        const activeQueryListeners = getSnapshotValue(
          snapshot,
          activeQueryListenersState,
        );

        const queryListenersToAdd = requiredQueryListeners.filter(
          (listener) =>
            !activeQueryListeners.some(
              (activeListener) => activeListener.queryId === listener.queryId,
            ),
        );

        const queryListenersToRemove = activeQueryListeners.filter(
          (listener) =>
            !requiredQueryListeners.some(
              (requiredListener) =>
                requiredListener.queryId === listener.queryId,
            ),
        );

        try {
          for (const queryListenerToAdd of queryListenersToAdd) {
            await addQueryToEventStream({
              variables: {
                input: {
                  eventStreamId: sseEventStreamId,
                  queryId: queryListenerToAdd.queryId,
                  operationSignature: queryListenerToAdd.operationSignature,
                },
              },
            });
          }

          for (const queryListenerToRemove of queryListenersToRemove) {
            await removeQueryFromEventStream({
              variables: {
                input: {
                  eventStreamId: sseEventStreamId,
                  queryId: queryListenerToRemove.queryId,
                },
              },
            });
          }
        } catch (error) {
          if (error instanceof ApolloError) {
            const subCode = error.graphQLErrors[0]?.extensions?.subCode;

            switch (subCode) {
              case 'EVENT_STREAM_DOES_NOT_EXIST':
              case 'EVENT_STREAM_ALREADY_EXISTS': {
                set(activeQueryListenersState, []);
                set(shouldDestroyEventStreamState, true);
                return;
              }
              default: {
                throw new Error(
                  `Unhandled error for event stream: ${error.message}`,
                );
              }
            }
          }
        }

        set(activeQueryListenersState, requiredQueryListeners);
      },
    [addQueryToEventStream, removeQueryFromEventStream, sseEventStreamId],
  );

  const debouncedUpdateQueryListeners = useDebouncedCallback(
    updateQueryListeners,
    1000,
    { leading: true },
  );

  useEffect(() => {
    if (!isNonEmptyString(sseEventStreamId)) {
      return;
    }

    const areRequiredQueryListenersDifferentFromActiveQueryListeners =
      compareArraysOfObjectsByProperty(
        requiredQueryListeners,
        activeQueryListeners,
        'queryId',
      );

    if (areRequiredQueryListenersDifferentFromActiveQueryListeners) {
      debouncedUpdateQueryListeners();
    }
  }, [
    sseEventStreamId,
    requiredQueryListeners,
    activeQueryListeners,
    debouncedUpdateQueryListeners,
  ]);

  return null;
};
