import type { IOnCompleted, IOnData, IOnError, IOnMessageReplace, IOtherOptions } from './base'
import type { FormData as HumanInputFormData } from '@/app/(humanInputLayout)/form/[token]/form'
import type { FeedbackType } from '@/app/components/base/chat/chat/type'
import type { ChatConfig } from '@/app/components/base/chat/types'
import type { AccessMode } from '@/models/access-control'
import type { AppConversationData, AppData, AppMeta, ConversationItem } from '@/models/share'
import { WEB_APP_SHARE_CODE_HEADER_NAME } from '@/config'
import {
  del as consoleDel,
  get as consoleGet,
  patch as consolePatch,
  post as consolePost,
  delPublic as del,
  getPublic as get,
  patchPublic as patch,
  postPublic as post,
  ssePost,
  upload,
} from './base'
import { getWebAppAccessToken } from './webapp-auth'

export enum AppSourceType {
  webApp = 'webApp',
  installedApp = 'installedApp',
  tryApp = 'tryApp',
}

const apiPrefix = {
  [AppSourceType.webApp]: '',
  [AppSourceType.installedApp]: 'installed-apps',
  [AppSourceType.tryApp]: 'trial-apps',
}

function getIsPublicAPI(appSourceType: AppSourceType) {
  return appSourceType === AppSourceType.webApp
}

function getAction(action: 'get' | 'post' | 'del' | 'patch', appSourceType: AppSourceType) {
  const isNeedLogin = !getIsPublicAPI(appSourceType)
  switch (action) {
    case 'get':
      return isNeedLogin ? consoleGet : get
    case 'post':
      return isNeedLogin ? consolePost : post
    case 'patch':
      return isNeedLogin ? consolePatch : patch
    case 'del':
      return isNeedLogin ? consoleDel : del
  }
}

export function getUrl(url: string, appSourceType: AppSourceType, appId: string) {
  const hasPrefix = appSourceType !== AppSourceType.webApp
  return hasPrefix
    ? `${apiPrefix[appSourceType]}/${appId}/${url.startsWith('/') ? url.slice(1) : url}`
    : url
}

export const stopChatMessageResponding = async (
  appId: string,
  taskId: string,
  appSourceType: AppSourceType,
  installedAppId = '',
) => {
  return getAction(
    'post',
    appSourceType,
  )(getUrl(`chat-messages/${taskId}/stop`, appSourceType, installedAppId))
}

export const sendCompletionMessage = async (
  body: Record<string, any>,
  {
    onData,
    onCompleted,
    onError,
    onMessageReplace,
    getAbortController,
  }: {
    onData: IOnData
    onCompleted: IOnCompleted
    onError: IOnError
    onMessageReplace: IOnMessageReplace
    getAbortController?: (abortController: AbortController) => void
  },
  appSourceType: AppSourceType,
  installedAppId = '',
) => {
  return ssePost(
    getUrl('completion-messages', appSourceType, installedAppId),
    {
      body: {
        ...body,
        response_mode: 'streaming',
      },
    },
    {
      onData,
      onCompleted,
      isPublicAPI: getIsPublicAPI(appSourceType),
      onError,
      onMessageReplace,
      getAbortController,
    },
  )
}

export const sendWorkflowMessage = async (
  body: Record<string, any>,
  otherOptions: IOtherOptions,
  appSourceType: AppSourceType,
  appId = '',
) => {
  return ssePost(
    getUrl('workflows/run', appSourceType, appId),
    {
      body: {
        ...body,
        response_mode: 'streaming',
      },
    },
    {
      ...otherOptions,
      isPublicAPI: getIsPublicAPI(appSourceType),
    },
  )
}

export const stopWorkflowMessage = async (
  _appId: string,
  taskId: string,
  appSourceType: AppSourceType,
  installedAppId = '',
) => {
  if (!taskId) return
  return getAction(
    'post',
    appSourceType,
  )(getUrl(`workflows/tasks/${taskId}/stop`, appSourceType, installedAppId))
}

export const fetchAppInfo = async () => {
  return get('/site') as Promise<AppData>
}

export const fetchConversations = async (
  appSourceType: AppSourceType,
  installedAppId = '',
  last_id?: string,
  pinned?: boolean,
  limit?: number,
) => {
  return getAction('get', appSourceType)(getUrl('conversations', appSourceType, installedAppId), {
    params: {
      limit: limit || 20,
      ...(last_id ? { last_id } : {}),
      ...(pinned !== undefined ? { pinned } : {}),
    },
  }) as Promise<AppConversationData>
}

export const pinConversation = async (
  appSourceType: AppSourceType,
  installedAppId = '',
  id: string,
) => {
  return getAction(
    'patch',
    appSourceType,
  )(getUrl(`conversations/${id}/pin`, appSourceType, installedAppId))
}

export const unpinConversation = async (
  appSourceType: AppSourceType,
  installedAppId = '',
  id: string,
) => {
  return getAction(
    'patch',
    appSourceType,
  )(getUrl(`conversations/${id}/unpin`, appSourceType, installedAppId))
}

export const delConversation = async (
  appSourceType: AppSourceType,
  installedAppId = '',
  id: string,
) => {
  return getAction(
    'del',
    appSourceType,
  )(getUrl(`conversations/${id}`, appSourceType, installedAppId))
}

export const renameConversation = async (
  appSourceType: AppSourceType,
  installedAppId = '',
  id: string,
  name: string,
) => {
  return getAction('post', appSourceType)(
    getUrl(`conversations/${id}/name`, appSourceType, installedAppId),
    { body: { name } },
  )
}

export const generationConversationName = async (
  appSourceType: AppSourceType,
  installedAppId = '',
  id: string,
) => {
  return getAction('post', appSourceType)(
    getUrl(`conversations/${id}/name`, appSourceType, installedAppId),
    { body: { auto_generate: true } },
  ) as Promise<ConversationItem>
}

export const fetchChatList = async (
  conversationId: string,
  appSourceType: AppSourceType,
  installedAppId = '',
) => {
  return getAction('get', appSourceType)(getUrl('messages', appSourceType, installedAppId), {
    params: { conversation_id: conversationId, limit: 20, last_id: '' },
  }) as any
}

// Abandoned API interface
// export const fetchAppVariables = async () => {
//   return get(`variables`)
// }

// init value. wait for server update
export const fetchAppParams = async (appSourceType: AppSourceType, appId = '') => {
  return getAction(
    'get',
    appSourceType,
  )(getUrl('parameters', appSourceType, appId)) as Promise<ChatConfig>
}

export const fetchWebSAMLSSOUrl = async (appCode: string, redirectUrl: string) => {
  return getAction('get', AppSourceType.webApp)(
    getUrl('/enterprise/sso/saml/login', AppSourceType.webApp, ''),
    {
      params: {
        app_code: appCode,
        redirect_url: redirectUrl,
      },
    },
  ) as Promise<{ url: string }>
}

export const fetchWebOIDCSSOUrl = async (appCode: string, redirectUrl: string) => {
  return getAction('get', AppSourceType.webApp)(
    getUrl('/enterprise/sso/oidc/login', AppSourceType.webApp, ''),
    {
      params: {
        app_code: appCode,
        redirect_url: redirectUrl,
      },
    },
  ) as Promise<{ url: string }>
}

export const fetchWebOAuth2SSOUrl = async (appCode: string, redirectUrl: string) => {
  return getAction('get', AppSourceType.webApp)(
    getUrl('/enterprise/sso/oauth2/login', AppSourceType.webApp, ''),
    {
      params: {
        app_code: appCode,
        redirect_url: redirectUrl,
      },
    },
  ) as Promise<{ url: string }>
}

export const fetchMembersSAMLSSOUrl = async (appCode: string, redirectUrl: string) => {
  return getAction('get', AppSourceType.webApp)(
    getUrl('/enterprise/sso/members/saml/login', AppSourceType.webApp, ''),
    {
      params: {
        app_code: appCode,
        redirect_url: redirectUrl,
      },
    },
  ) as Promise<{ url: string }>
}

export const fetchMembersOIDCSSOUrl = async (appCode: string, redirectUrl: string) => {
  return getAction('get', AppSourceType.webApp)(
    getUrl('/enterprise/sso/members/oidc/login', AppSourceType.webApp, ''),
    {
      params: {
        app_code: appCode,
        redirect_url: redirectUrl,
      },
    },
  ) as Promise<{ url: string }>
}

export const fetchMembersOAuth2SSOUrl = async (appCode: string, redirectUrl: string) => {
  return getAction('get', AppSourceType.webApp)(
    getUrl('/enterprise/sso/members/oauth2/login', AppSourceType.webApp, ''),
    {
      params: {
        app_code: appCode,
        redirect_url: redirectUrl,
      },
    },
  ) as Promise<{ url: string }>
}

export const fetchAppMeta = async (appSourceType: AppSourceType, installedAppId = '') => {
  return getAction(
    'get',
    appSourceType,
  )(getUrl('meta', appSourceType, installedAppId)) as Promise<AppMeta>
}

export const updateFeedback = async (
  { url, body }: { url: string; body: FeedbackType },
  appSourceType: AppSourceType,
  installedAppId = '',
) => {
  return getAction('post', appSourceType)(getUrl(url, appSourceType, installedAppId), { body })
}

export const fetchMoreLikeThis = async (
  messageId: string,
  appSourceType: AppSourceType,
  installedAppId = '',
) => {
  return getAction('get', appSourceType)(
    getUrl(`/messages/${messageId}/more-like-this`, appSourceType, installedAppId),
    {
      params: {
        response_mode: 'blocking',
      },
    },
  )
}

export const saveMessage = (
  messageId: string,
  appSourceType: AppSourceType,
  installedAppId = '',
) => {
  return getAction('post', appSourceType)(
    getUrl('/saved-messages', appSourceType, installedAppId),
    { body: { message_id: messageId } },
  )
}

export const fetchSavedMessage = async (appSourceType: AppSourceType, installedAppId = '') => {
  return getAction('get', appSourceType)(
    getUrl('/saved-messages', appSourceType, installedAppId),
    {},
    {
      silent: true,
    },
  )
}

export const removeMessage = (
  messageId: string,
  appSourceType: AppSourceType,
  installedAppId = '',
) => {
  return getAction(
    'del',
    appSourceType,
  )(getUrl(`/saved-messages/${messageId}`, appSourceType, installedAppId))
}

export const fetchSuggestedQuestions = (
  messageId: string,
  appSourceType: AppSourceType,
  installedAppId = '',
) => {
  return getAction(
    'get',
    appSourceType,
  )(getUrl(`/messages/${messageId}/suggested-questions`, appSourceType, installedAppId))
}

export const audioToText = (
  appSourceType: AppSourceType,
  appId: string | undefined,
  body: FormData,
  signal?: AbortSignal,
) => {
  if (appSourceType !== AppSourceType.webApp && !appId)
    throw new Error('An app ID is required for console speech-to-text requests.')
  return getAction('post', appSourceType)(
    getUrl('/audio-to-text', appSourceType, appId ?? ''),
    { body, signal },
    { bodyStringify: false, deleteContentType: true },
  ) as Promise<{ text: string }>
}

export const textToAudioStream = (
  url: string,
  appSourceType: AppSourceType,
  header: { content_type: string },
  body: {
    streaming: boolean
    voice?: string
    message_id?: string
    text?: string | null | undefined
  },
) => {
  return getAction('post', appSourceType)(url, { body, header }, { needAllResponseContent: true })
}

export const fetchAccessToken = async ({
  userId,
  appCode,
}: {
  userId?: string
  appCode: string
}) => {
  const headers = new Headers()
  headers.append(WEB_APP_SHARE_CODE_HEADER_NAME, appCode)
  const accessToken = getWebAppAccessToken()
  if (accessToken) headers.append('Authorization', `Bearer ${accessToken}`)
  const params = new URLSearchParams()
  if (userId) params.append('user_id', userId)
  const url = `/passport?${params.toString()}`
  return get<{ access_token: string }>(url, { headers }) as Promise<{ access_token: string }>
}

export const getUserCanAccess = (appId: string, isInstalledApp: boolean) => {
  if (isInstalledApp)
    return consoleGet<{ result: boolean }>(`/enterprise/webapp/permission?appId=${appId}`)

  return get<{ result: boolean }>(`/webapp/permission?appId=${appId}`)
}

export const getAppAccessModeByAppCode = (appCode: string) => {
  return get<{ accessMode: AccessMode }>(`/webapp/access-mode?appCode=${appCode}`)
}

export const getHumanInputForm = (token: string) => {
  return get<HumanInputFormData>(`/form/human_input/${token}`)
}

type HumanInputFormUploadTokenResponse = {
  upload_token: string
  expires_at: number
}

type HumanInputFormLocalFileUploadResponse = {
  created_at: number
  created_by: string
  extension: string
  id: string
  mime_type: string
  name: string
  preview_url: string | null
  size: number
  source_url: string
}

type HumanInputFormRemoteFileUploadResponse = {
  created_at: number
  created_by: string
  extension: string
  id: string
  mime_type: string
  name: string
  size: number
  url: string
}

type HumanInputFormLocalFileUploadParams = {
  formToken: string
  file: File
  onProgressCallback: (progress: number) => void
  onSuccessCallback: (res: HumanInputFormLocalFileUploadResponse) => void
  onErrorCallback: (error?: unknown) => void
}

const humanInputFormUploadTokenCache = new Map<string, HumanInputFormUploadTokenResponse>()
const UPLOAD_TOKEN_REFRESH_BUFFER_SECONDS = 30

const getHumanInputFormUploadToken = async (formToken: string) => {
  const cachedToken = humanInputFormUploadTokenCache.get(formToken)
  const now = Math.floor(Date.now() / 1000)

  if (cachedToken && cachedToken.expires_at > now + UPLOAD_TOKEN_REFRESH_BUFFER_SECONDS)
    return cachedToken.upload_token

  const tokenResponse = await post<HumanInputFormUploadTokenResponse>(
    `/form/human_input/${formToken}/upload-token`,
  )
  humanInputFormUploadTokenCache.set(formToken, tokenResponse)
  return tokenResponse.upload_token
}

const uploadHumanInputFormFile = async (
  formToken: string,
  formData: FormData,
  onProgress?: (e: ProgressEvent) => void,
) => {
  const uploadToken = await getHumanInputFormUploadToken(formToken)

  return upload(
    {
      xhr: new XMLHttpRequest(),
      data: formData,
      headers: {
        Authorization: `bearer ${uploadToken}`,
      },
      onprogress: onProgress,
    },
    true,
    '/human-input-forms/files',
  )
}

export const uploadHumanInputFormLocalFile = async ({
  formToken,
  file,
  onProgressCallback,
  onSuccessCallback,
  onErrorCallback,
}: HumanInputFormLocalFileUploadParams) => {
  const formData = new FormData()
  formData.append('file', file)

  const onProgress = (e: ProgressEvent) => {
    if (e.lengthComputable) {
      const percent = Math.floor((e.loaded / e.total) * 100)
      onProgressCallback(percent)
    }
  }

  try {
    const response = (await uploadHumanInputFormFile(
      formToken,
      formData,
      onProgress,
    )) as HumanInputFormLocalFileUploadResponse

    onSuccessCallback(response)
  } catch (error) {
    onErrorCallback(error)
  }
}

export const uploadHumanInputFormRemoteFileInfo = async (
  formToken: string,
  url: string,
): Promise<HumanInputFormRemoteFileUploadResponse> => {
  const formData = new FormData()
  formData.append('url', url)

  return uploadHumanInputFormFile(
    formToken,
    formData,
  ) as Promise<HumanInputFormRemoteFileUploadResponse>
}

export const submitHumanInputForm = (
  token: string,
  data: {
    inputs: Record<string, unknown>
    action: string
  },
) => {
  return post(`/form/human_input/${token}`, { body: data })
}
