import { UserEventCategory } from '@spiffy-ai/commerce-api-client';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import { ReactNode, createContext, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Logger from 'src/application/logging/logger';
import { NextMessageRequest, Suggestion, UserEvent } from 'src/application/models';
import { Message, MessageRole, MessageType } from 'src/application/models/message';
import {
  initializedAtom,
  messagesAtom,
  pendingResponseAtom,
  responseStreamingAtom,
  suggestionsAtom,
} from 'src/atoms/chat/chatState';
import {
  processUserEventAtom,
  userEventQueueAtom,
  userQueueEventCountAtom,
} from 'src/atoms/chat/messageQueue';
import { useSystemSettingsContext } from '../../hooks/SystemSettingsContext';
import { WidgetInteractionComponent } from '../../hooks/WidgetInteraction/types';
import { SalesAgentChatAPI, useSalesAgentChatAPI } from './chatAPI';
import {
  buildFormSubmittedDisplayContent,
  createFormSubmittedUserMessage,
} from './formSubmittedUtils';
import { useSalesAgentService } from './salesAgentService';

export interface SalesAgent extends SalesAgentChatAPI {
  // Messages are the full list of all turns
  messages: Message[][];
  // Pending messages are messages that the user has clicked/typed but have not yet been sent to the backend.
  pendingMessages: UserEvent[];
  // Suggestions are the list of suggestions that the user can click on in response to the most recent message from the backend
  suggestions: Suggestion[];
  // True while the response is streaming from the backend
  isResponseStreaming: boolean;
  // True while the response is pending from the backend
  isPendingResponse: boolean;
  // True when the sales agent is initialized
  isInitialized: boolean;
}

export interface MockSalesAgentData {
  messages?: Message[][];
  suggestions?: Suggestion[];
}

const SalesAgentContext = createContext<SalesAgent | undefined>(undefined);

const logger = new Logger('salesAgentContext');

export interface SalesAgentProviderProps {
  children: ReactNode;
  previewMode?: boolean;
  mockData?: MockSalesAgentData;
}

export const SalesAgentProvider: React.FC<SalesAgentProviderProps> = ({
  children,
  previewMode = false,
  mockData,
}) => {
  const userEvents = useAtomValue(userEventQueueAtom);
  const userQueueEventCount = useAtomValue(userQueueEventCountAtom);
  const markUserEventsProcessed = useSetAtom(processUserEventAtom);
  const [isHydrated, setIsHydrated] = useState(false);
  const [isInitialized, setIsInitialized] = useAtom(initializedAtom);
  const settingsContext = useSystemSettingsContext();
  const setMessages = useSetAtom(messagesAtom);
  const setSuggestions = useSetAtom(suggestionsAtom);

  const { getStreamingResponses, createResponsePayload, getSuggestions, hydrateMessages } =
    useSalesAgentService();

  const isSendingRef = useRef(false);

  // Initialize with mock data in preview mode
  useEffect(() => {
    if (previewMode && mockData) {
      if (mockData.messages) {
        setMessages(mockData.messages);
      }
      if (mockData.suggestions) {
        setSuggestions(mockData.suggestions);
      }
      setIsInitialized(true);
    }
  }, [previewMode, mockData, setMessages, setSuggestions, setIsInitialized]);

  // TODO: Ensure that amplitude events are being emitted

  // TODO: Add support for the streaming responses to function

  // TODO: Add support for the hydration of the messages from the backend on initialization
  const sendMessagesToBackend = useCallback(
    async (requestPayload: NextMessageRequest) => {
      try {
        await getStreamingResponses(requestPayload);
        setIsInitialized(true);

        // Remove the pending message from the user events
        requestPayload.userEvents?.map(userEvent => markUserEventsProcessed([userEvent.eventId]));
      } catch (error) {
        logger.logError('error sending message', error);
      }
    },
    [getStreamingResponses, setIsInitialized, markUserEventsProcessed],
  );

  // This is the primary event loop for communicating with the backend API
  // It will be triggered when there are pending messages to be sent to the backend
  // It will be responsible for sending the messages to the backend and receiving the responses
  useEffect(() => {
    // Skip backend communication in preview mode
    if (previewMode) {
      return;
    }
    // Wait for the messages to be hydrated before sending messages to the backend
    if (!isHydrated) {
      return;
    }

    if (userQueueEventCount > 0 && !isSendingRef.current) {
      isSendingRef.current = true;
      const payloadEvents = userEvents.slice(0, userQueueEventCount);

      payloadEvents.forEach(userEvent => {
        if (userEvent && userEvent.category === UserEventCategory.QueryTyped) {
          const newMessage: Message = {
            id: userEvent.eventId,
            role: MessageRole.User,
            type: MessageType.QueryTyped,
            createdAt: userEvent.createdAt,
            metadata: {
              content: userEvent.attributes.query,
            },
          };
          setMessages(current => [...current, [newMessage]]);
        }
        if (userEvent && userEvent.category === UserEventCategory.SuggestionClicked) {
          const newMessage: Message = {
            id: userEvent.eventId,
            role: MessageRole.User,
            type: MessageType.QueryTyped,
            createdAt: userEvent.createdAt,
            metadata: {
              content: userEvent.attributes.content ?? '',
            },
          };
          setMessages(current => [...current, [newMessage]]);
        }
        if (userEvent && userEvent.category === UserEventCategory.FormSubmitted) {
          const { formResponseId, filledSchema } = userEvent.attributes;
          setMessages(prevMessagesState => {
            const formSubmittedContent = buildFormSubmittedDisplayContent(
              prevMessagesState,
              formResponseId,
              filledSchema,
            );
            const newUserMessage = createFormSubmittedUserMessage(userEvent, formSubmittedContent);
            const turnsWithoutForm = prevMessagesState
              .map(turn =>
                turn.filter(msg => !(msg.type === MessageType.Form && msg.id === formResponseId)),
              )
              .filter(turn => turn.length > 0);
            return [...turnsWithoutForm, [newUserMessage]];
          });
        }
      });

      sendMessagesToBackend(
        createResponsePayload({
          userEvents: payloadEvents,
          generationParams: settingsContext.generationParams,
        }),
      ).finally(() => {
        isSendingRef.current = false;
      });
    } else if (isInitialized) {
      getSuggestions(
        createResponsePayload({
          userEvents: [],
          generationParams: settingsContext.generationParams,
        }),
      );
    }
  }, [
    isHydrated,
    createResponsePayload,
    getSuggestions,
    isInitialized,
    setMessages,
    sendMessagesToBackend,
    settingsContext.generationParams,
    userEvents,
    userQueueEventCount,
    previewMode,
  ]);

  useEffect(() => {
    const startHydration = async () => {
      // Skip hydration in preview mode
      if (previewMode) {
        return;
      }

      if (!isHydrated) {
        // Rehydrate any messages from the backend
        try {
          await hydrateMessages();
        } catch (error) {
          logger.logError('error hydrating messages', error);
        } finally {
          setIsHydrated(true);
        }
      }
    };
    startHydration();
  }, [isInitialized, hydrateMessages, previewMode, isHydrated]);

  return <SalesAgentContext.Provider value={undefined}>{children}</SalesAgentContext.Provider>;
};

export const useSalesAgent = (contextWidget?: WidgetInteractionComponent): SalesAgent => {
  const messages = useAtomValue(messagesAtom);
  const suggestions = useAtomValue(suggestionsAtom);
  const pendingMessages = useAtomValue(userEventQueueAtom);
  const isResponseStreaming = useAtomValue(responseStreamingAtom);
  const isPendingResponse = useAtomValue(pendingResponseAtom);
  const isInitialized = useAtomValue(initializedAtom);
  const {
    logPageVisit,
    logUserEvent,
    onSuggestionClicked,
    onTypedMessageSubmitted,
    onFormResponseSubmitted,
  } = useSalesAgentChatAPI(contextWidget);

  const salesAgent: SalesAgent = useMemo(() => {
    return {
      messages,
      suggestions,
      pendingMessages,
      isInitialized,
      isResponseStreaming,
      isPendingResponse,
      logPageVisit,
      logUserEvent,
      onSuggestionClicked,
      onTypedMessageSubmitted,
      onFormResponseSubmitted,
    };
  }, [
    messages,
    suggestions,
    pendingMessages,
    isInitialized,
    isResponseStreaming,
    isPendingResponse,
    logPageVisit,
    logUserEvent,
    onSuggestionClicked,
    onTypedMessageSubmitted,
    onFormResponseSubmitted,
  ]);
  return salesAgent;
};
