import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants'
import { ChatCompletionOpenAIOptions } from '@typebot.io/schemas/features/blocks/integrations/openai'
import { OpenAI } from 'openai'
import { decryptV2 } from '@typebot.io/lib/api/encryption/decryptV2'
import { forgedBlocks } from '@typebot.io/forge-repository/definitions'
import { VariableStore } from '@typebot.io/forge'
import {
  ParseVariablesOptions,
  parseVariables,
} from '@typebot.io/variables/parseVariables'
import { getOpenAIChatCompletionStream } from './legacy/getOpenAIChatCompletionStream'
import { getCredentials } from '../queries/getCredentials'
import { getSession } from '../queries/getSession'
import { getBlockById } from '@typebot.io/schemas/helpers'
import { isForgedBlockType } from '@typebot.io/schemas/features/blocks/forged/helpers'
import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesInSession'
import { updateSession } from '../queries/updateSession'
import { deepParseVariables } from '@typebot.io/variables/deepParseVariables'

type Props = {
  sessionId: string
  messages: OpenAI.Chat.ChatCompletionMessage[] | undefined
}

export const getMessageStream = async ({ sessionId, messages }: Props) => {
  const session = await getSession(sessionId)

  if (!session?.state || !session.state.currentBlockId)
    return { status: 404, message: 'Could not find session' }

  const { group, block } = getBlockById(
    session.state.currentBlockId,
    session.state.typebotsQueue[0].typebot.groups
  )
  if (!block || !group)
    return {
      status: 404,
      message: 'Could not find block or group',
    }

  if (!('options' in block))
    return {
      status: 400,
      message: 'This block does not have options',
    }

  if (block.type === IntegrationBlockType.OPEN_AI && messages) {
    try {
      const stream = await getOpenAIChatCompletionStream(
        session.state,
        block.options as ChatCompletionOpenAIOptions,
        messages
      )
      if (!stream)
        return {
          status: 500,
          message: 'Could not create stream',
        }

      return { stream }
    } catch (error) {
      if (error instanceof OpenAI.APIError) {
        const { message } = error
        return {
          status: 500,
          message,
        }
      } else {
        throw error
      }
    }
  }
  if (!isForgedBlockType(block.type))
    return {
      status: 400,
      message: 'This block does not have a stream function',
    }

  const blockDef = forgedBlocks[block.type]
  const action = blockDef?.actions.find((a) => a.name === block.options?.action)

  if (!action || !action.run?.stream)
    return {
      status: 400,
      message: 'This block does not have a stream function',
    }

  try {
    if (!block.options.credentialsId)
      return { status: 404, message: 'Could not find credentials' }
    const credentials = await getCredentials(block.options.credentialsId)
    if (!credentials)
      return { status: 404, message: 'Could not find credentials' }
    const decryptedCredentials = await decryptV2(
      credentials.data,
      credentials.iv
    )

    const variables: VariableStore = {
      list: () => session.state.typebotsQueue[0].typebot.variables,
      get: (id: string) => {
        const variable = session.state.typebotsQueue[0].typebot.variables.find(
          (variable) => variable.id === id
        )
        return variable?.value
      },
      parse: (text: string, params?: ParseVariablesOptions) =>
        parseVariables(
          session.state.typebotsQueue[0].typebot.variables,
          params
        )(text),
      set: async (id: string, value: unknown) => {
        const variable = session.state.typebotsQueue[0].typebot.variables.find(
          (variable) => variable.id === id
        )
        if (!variable) return
        await updateSession({
          id: session.id,
          state: updateVariablesInSession(session.state)([
            { ...variable, value },
          ]),
          isReplying: undefined,
        })
      },
    }
    const stream = await action.run.stream.run({
      credentials: decryptedCredentials,
      options: deepParseVariables(
        session.state.typebotsQueue[0].typebot.variables
      )(block.options),
      variables,
    })
    if (!stream) return { status: 500, message: 'Could not create stream' }

    return { stream }
  } catch (error) {
    if (error instanceof OpenAI.APIError) {
      const { message } = error
      return {
        status: 500,
        message,
      }
    }
    return {
      status: 500,
      message: 'Could not create stream',
    }
  }
}
