import { continueChatQuery } from '@/queries/continueChatQuery'
import { saveClientLogsQuery } from '@/queries/saveClientLogsQuery'
import {
  BotContext,
  ChatChunk as ChatChunkType,
  InputSubmitContent,
  OutgoingLog,
} from '@/types'
import { executeClientSideAction } from '@/utils/executeClientSideActions'
import {
  formattedMessages,
  setFormattedMessages,
} from '@/utils/formattedMessagesSignal'
import { getAnswerContent } from '@/utils/getAnswerContent'
import { persist } from '@/utils/persist'
import { newAnswer, setNewAnswer } from '@/utils/setAnswerSignal'
import { setStreamingMessage } from '@/utils/streamingMessageSignal'
import { isNotDefined } from '@indite.io/lib'
import {
  ChatLog,
  ContinueChatResponse,
  InputBlock,
  Message,
  StartChatResponse,
  TextInputBlock,
  Theme,
} from '@indite.io/schemas'
import { InputBlockType } from '@indite.io/schemas/features/blocks/inputs/constants'
import { defaultGuestAvatarIsEnabled, defaultHostAvatarIsEnabled } from '@indite.io/schemas/features/bot/theme/constants'
import { HTTPError } from 'ky'
import {
  createEffect,
  createSignal,
  For,
  onCleanup,
  onMount,
  Show
} from 'solid-js'
import { GuestBubble } from '../bubbles/GuestBubble'
import { Input } from '../InputChatBlock'
import { ChatChunk } from './ChatChunk'
import { LoadingChunk } from './LoadingChunk'
import { PopupBlockedToast } from './PopupBlockedToast'
import { updateChatHistory } from './HistoryChunk'

const autoScrollBottomToleranceScreenPercent = 0.6
const bottomSpacerHeight = 90

const parseDynamicTheme = (
  initialTheme: Theme,
  dynamicTheme: ContinueChatResponse['dynamicTheme']
): Theme => ({
  ...initialTheme,
  chat: {
    ...initialTheme.chat,
    hostAvatar:
      initialTheme.chat?.hostAvatar && dynamicTheme?.hostAvatarUrl
        ? {
          ...initialTheme.chat.hostAvatar,
          url: dynamicTheme.hostAvatarUrl,
        }
        : initialTheme.chat?.hostAvatar,
    guestAvatar:
      initialTheme.chat?.guestAvatar && dynamicTheme?.guestAvatarUrl
        ? {
          ...initialTheme.chat.guestAvatar,
          url: dynamicTheme?.guestAvatarUrl,
        }
        : initialTheme.chat?.guestAvatar,
  },
})

type Bot = {
  id: string;
  theme: Record<string, unknown>; // Adjust as necessary based on your theme structure
  settings: Record<string, unknown>; // Adjust as necessary based on your settings structure
};

type ConversationData = {
  sessionId: string;
  bot: Bot;
  messages: Message[];
  input: InputBlock;
};

type Props = {
  data: ConversationData
  initialChatReply: StartChatResponse
  context: BotContext
  onNewInputBlock?: (inputBlock: InputBlock) => void
  onAnswer?: (answer: { message: string; blockId: string }) => void
  onEnd?: () => void
  onNewLogs?: (logs: OutgoingLog[]) => void
  onProgressUpdate?: (progress: number) => void
  botContainerWidth?: string
}

export const ConversationContainer = (props: Props) => {
  let chatContainer: HTMLDivElement | undefined
  const [chatChunks, setChatChunks, isRecovered, setIsRecovered] = persist(
    createSignal<ChatChunkType[]>([
      {
        input: props.initialChatReply.input,
        messages: props.initialChatReply.messages,
        clientSideActions: props.initialChatReply.clientSideActions,
      },
    ]),
    {
      key: `bot-${props.context.bot.id}-chatChunks`,
      storage: props.context.storage,
      onRecovered: () => {
        setTimeout(() => {
          chatContainer?.scrollTo(0, chatContainer.scrollHeight)
        }, 200)
      },
    }
  )
  const [dynamicTheme, setDynamicTheme] = createSignal<
    ContinueChatResponse['dynamicTheme']
  >(props.initialChatReply.dynamicTheme)
  const [theme, setTheme] = createSignal(props.initialChatReply.bot.theme)
  const [isSending, setIsSending] = createSignal(false)
  const [blockedPopupUrl, setBlockedPopupUrl] = createSignal<string>()
  const [hasError, setHasError] = createSignal(false)

  const [chunkIndex, setChunkIndex] = createSignal<number>(0)

  onMount(() => {
    ; (async () => {
      const initialChunk = chatChunks()[0]
      if (!initialChunk.clientSideActions) return
      const actionsBeforeFirstBubble = initialChunk.clientSideActions.filter(
        (action) => isNotDefined(action.lastBubbleBlockId)
      )
      await processClientSideActions(actionsBeforeFirstBubble)
    })()
  })

  const streamMessage = ({ id, message }: { id: string; message: string }) => {
    setIsSending(false)
    const lastChunk = [...chatChunks()].pop()
    updateChatHistory(chatChunks())
    if (!lastChunk) return
    if (lastChunk.streamingMessageId !== id)
      setChatChunks((displayedChunks) => [
        ...displayedChunks,
        {
          messages: [],
          streamingMessageId: id,
        },
      ])
    setStreamingMessage({ id, content: message })
  }

  createEffect(() => {
    setTheme(
      parseDynamicTheme(props.initialChatReply.bot.theme, dynamicTheme())
    )
  })

  const saveLogs = async (clientLogs?: ChatLog[]) => {
    if (!clientLogs) return
    props.onNewLogs?.(clientLogs)
    if (props.context.isPreview) return
    await saveClientLogsQuery({
      apiHost: props.context.apiHost,
      sessionId: props.initialChatReply.sessionId,
      clientLogs,
    })
  }

  const sendMessage = async (answer?: InputSubmitContent) => {
    setIsRecovered(false)
    setHasError(false)
    const lastChunk = [...chatChunks()].pop()
    if (!lastChunk) return
    if (!lastChunk.streamingMessageId && !lastChunk.clientSideActions && answer) setNewAnswer((prevAnswers) => ({ ...prevAnswers, [chunkIndex()]: answer }))

    const currentInputBlock = [...chatChunks()].pop()?.input
    if (currentInputBlock?.id && props.onAnswer && answer)
      props.onAnswer({
        message: getAnswerContent(answer),
        blockId: currentInputBlock.id,
      })
    const longRequest = setTimeout(() => {
      setIsSending(true)
    }, 1000)
    autoScrollToBottom()
    const { data, error } = await continueChatQuery({
      apiHost: props.context.apiHost,
      sessionId: props.initialChatReply.sessionId,
      message: convertSubmitContentToMessage(answer),
      // history: historyData,
    })

    clearTimeout(longRequest)
    setIsSending(false)
    if (error) {
      setHasError(true)
      const errorLogs = [
        {
          description: 'Failed to send the reply',
          details:
            error instanceof HTTPError
              ? {
                status: error.response.status,
                body: await error.response.json(),
              }
              : error,
          status: 'error',
        },
      ]
      await saveClientLogsQuery({
        apiHost: props.context.apiHost,
        sessionId: props.initialChatReply.sessionId,
        clientLogs: errorLogs,
      })
      props.onNewLogs?.(errorLogs)
      return
    }
    if (!data) return
    if (data.chatLimitExceededError) window.location.reload()
    if (data.progress) props.onProgressUpdate?.(data.progress)
    if (data.lastMessageNewFormat) {
      setFormattedMessages([
        ...formattedMessages(),
        {
          inputIndex: [...chatChunks()].length - 1,
          formattedMessage: data.lastMessageNewFormat as string,
        },
      ])
    }
    if (data.logs) props.onNewLogs?.(data.logs)
    if (data.dynamicTheme) setDynamicTheme(data.dynamicTheme)
    if (data.input && props.onNewInputBlock) {
      props.onNewInputBlock(data.input)
    }
    if (data.clientSideActions) {
      const actionsBeforeFirstBubble = data.clientSideActions.filter((action) =>
        isNotDefined(action.lastBubbleBlockId)
      )
      await processClientSideActions(actionsBeforeFirstBubble)
      if (
        data.clientSideActions.length === 1 &&
        data.clientSideActions[0].type === 'stream' &&
        data.messages.length === 0 &&
        data.input === undefined
      )
        return
    }

    setChatChunks((displayedChunks) => [
      ...displayedChunks,
      {
        input: data.input,
        messages: data.messages,
        clientSideActions: data.clientSideActions,
      },
    ])
  }

  const autoScrollToBottom = (lastElement?: HTMLDivElement, offset = 0) => {
    if (!chatContainer) return

    const bottomTolerance =
      chatContainer.clientHeight * autoScrollBottomToleranceScreenPercent -
      bottomSpacerHeight

    const isBottomOfLastElementInView =
      chatContainer.scrollTop + chatContainer.clientHeight >=
      chatContainer.scrollHeight - bottomTolerance

    if (isBottomOfLastElementInView) {
      setTimeout(() => {
        chatContainer?.scrollTo(
          0,
          lastElement
            ? lastElement.offsetTop - offset
            : chatContainer.scrollHeight
        )
      }, 50)
    }
  }

  const handleAllBubblesDisplayed = async () => {
    const lastChunk = [...chatChunks()].pop()
    if (!lastChunk) return
    if (isNotDefined(lastChunk.input)) {
      props.onEnd?.()
    }
  }

  const handleNewBubbleDisplayed = async (blockId: string) => {
    const lastChunk = [...chatChunks()].pop()
    if (!lastChunk) return
    if (lastChunk.clientSideActions) {
      const actionsToExecute = lastChunk.clientSideActions.filter(
        (action) => action.lastBubbleBlockId === blockId
      )
      await processClientSideActions(actionsToExecute)
    }
  }

  const processClientSideActions = async (
    actions: NonNullable<ContinueChatResponse['clientSideActions']>
  ) => {
    if (isRecovered()) return
    for (const action of actions) {
      if (
        'streamOpenAiChatCompletion' in action ||
        'httpRequestToExecute' in action ||
        'stream' in action
      )
        setIsSending(true)
      const response = await executeClientSideAction({
        clientSideAction: action,
        context: {
          apiHost: props.context.apiHost,
          sessionId: props.initialChatReply.sessionId,
        },
        onMessageStream: streamMessage,
      })
      if (response && 'logs' in response) saveLogs(response.logs)
      if (response && 'replyToSend' in response) {
        setIsSending(false)
        sendMessage(
          response.replyToSend
            ? { type: 'text', value: response.replyToSend }
            : undefined
        )
        return
      }
      if (response && 'blockedPopupUrl' in response)
        setBlockedPopupUrl(response.blockedPopupUrl)
    }
  }

  onCleanup(() => {
    setStreamingMessage(undefined)
    setFormattedMessages([])
  })

  const handleSkip = () => sendMessage(undefined)

  return (
    <div class='h-[calc(100%-68px)]  w-full flex justify-center'>
      <div class='bot-chat-view w-full flex flex-col justify-between pt-5 pb-12'>
        <div
          ref={chatContainer}
          class="scrollable-container  relative flex w-full flex-col gap-2 overflow-y-auto scroll-smooth px-3 "
        >
          <For each={chatChunks()}>
            {(chatChunk, index) => {
              setChunkIndex(index())
              return (
                <>
                  <Show when={newAnswer()[index() - 1] && !hasError()}>
                    <GuestBubble
                      answer={newAnswer()[index() - 1]}
                      showAvatar={
                        theme().chat?.guestAvatar?.isEnabled ?? defaultGuestAvatarIsEnabled
                      }
                      avatarSrc={theme().chat?.guestAvatar?.url}
                      hasHostAvatar={theme().chat?.hostAvatar?.isEnabled ??
                        defaultHostAvatarIsEnabled}
                    />
                  </Show>
                  <ChatChunk
                    botContainerWidth={props.botContainerWidth}
                    index={index()}
                    messages={chatChunk.messages}
                    input={chatChunk.input}
                    theme={theme()}
                    settings={props.initialChatReply.bot.settings}
                    streamingMessageId={chatChunk.streamingMessageId}
                    context={props.context}
                    hideAvatar={
                      !chatChunk.input &&
                      ((chatChunks()[index() + 1]?.messages ?? 0).length > 0 ||
                        chatChunks()[index() + 1]?.streamingMessageId !== undefined ||
                        (chatChunk.messages.length > 0 && isSending()))
                    }
                    hasError={hasError() && index() === chatChunks().length - 1}
                    isTransitionDisabled={index() !== chatChunks().length - 1}
                    onNewBubbleDisplayed={handleNewBubbleDisplayed}
                    onAllBubblesDisplayed={handleAllBubblesDisplayed}
                    onSubmit={sendMessage}
                    onScrollToBottom={autoScrollToBottom}
                    onSkip={handleSkip}
                  />

                </>
              )
            }}
          </For>


          <Show when={isSending()}>
            <LoadingChunk theme={theme()} />

          </Show>
          <Show when={blockedPopupUrl()} keyed>
            {(blockedPopupUrl) => (
              <div class="flex justify-end">
                <PopupBlockedToast
                  url={blockedPopupUrl}
                  onLinkClick={() => setBlockedPopupUrl(undefined)}
                />
              </div>
            )}
          </Show>
          <BottomSpacer />
        </div>

        <div >
          <Show when={

            !isSending() && (
              chatChunks()?.[chunkIndex()]?.input?.type === InputBlockType.TEXT ||
              chatChunks()?.[chunkIndex()]?.input?.type === InputBlockType.NUMBER ||
              chatChunks()?.[chunkIndex()]?.input?.type === InputBlockType.EMAIL ||
              chatChunks()?.[chunkIndex()]?.input?.type === InputBlockType.URL ||
              chatChunks()?.[chunkIndex()]?.input?.type === InputBlockType.PHONE
            )}>

            <Input
              context={props.context}
              block={chatChunks()[chunkIndex()].input as TextInputBlock}
              chunkIndex={chunkIndex()}
              isInputPrefillEnabled={false}
              existingAnswer={''}
              onSubmit={sendMessage}
              onTransitionEnd={autoScrollToBottom}
              onSkip={handleSkip}
            />
          </Show>
        </div>

      </div>
    </div>
  )
}

const BottomSpacer = () => (
  <div
    class="bot-bottom-spacer w-full flex-shrink-0"
    style={{ height: bottomSpacerHeight + 'px' }}
  />
)

const convertSubmitContentToMessage = (
  answer: InputSubmitContent | undefined
): Message | undefined => {
  if (!answer) return
  if (answer.type === 'text')
    return {
      type: 'text',
      text: answer.value,
      attachedFileUrls: answer.attachments?.map((attachment) => attachment.url),
    }
  if (answer.type === 'recording') return { type: 'audio', url: answer.url }
}
