import { useForm } from '@tidb-cloud-uikit/form'
import {
  Group,
  Modal,
  Select,
  TextInput,
  Button,
  Input,
  Typography,
  Flex,
  ActionIcon,
  Menu,
  Box,
  Skeleton,
  notifier,
  Popover
} from '@tidbcloud/uikit'
import { IconPlus, IconXClose, IconFile06, IconDotsHorizontal } from '@tidbcloud/uikit/icons'
import { observer } from 'mobx-react'
import { useEffect, useRef, useState } from 'react'
import { useHistory } from 'react-router'

import { useHelpCenterContext } from 'dbaas/screens/HelpCenter/context'
import {
  getSqlEditorFiles,
  createSqlEditorFile,
  useDeleteSqlEditorFile,
  updateSqlEditorFile,
  createsSqlEditorSession,
  getIsTableExist
} from 'dbaas/services'
import { createDataApp, createEndpoint, getDataApps, useListAppCluster } from 'dbaas/services/dataservice'
import { getErrorMessage } from 'dbaas/services/errorCodes'
import useStores from 'dbaas/stores/useStores'
import { eventTracking } from 'dbaas/utils/tracking'

import { filterEmojis } from '../../DataService/helpers'
import { DataApp, EndPoint } from '../../DataService/types'
import { useSQLEditorContext } from '../context/editor-context'
import { getEditor } from '../sql-code-editor/editor-cache'
import { SqlFile, OpenedFile } from '../types'

import { preTips, useAffixTips, useNewFileContent } from './fileContent'
import { useStyles } from './index.styles'

const SqlFiles = () => {
  const [isRename, setIsRename] = useState(false)
  const [deleteOpenVisible, setDeleteOpenVisible] = useState(false)
  const operFile = useRef<SqlFile | null>(null)
  const [renameFile, setRenameFile] = useState<SqlFile | null>(null)
  const [loading, setLoading] = useState(true)
  const {
    orgId,
    projectId,
    clusterId,
    sqlFiles,
    setSqlFiles,
    editedSqlFile,
    editedSqlFileRef,
    setEditedSqlFile,
    getSqlFiles,
    isCreatingFile,
    setIsCreatingFile,
    setIsFromImport,
    openedFiles,
    setOpenedFiles
  } = useSQLEditorContext()
  const [filterSqlFiles, setFilterSqlFiles] = useState<SqlFile[]>([])
  const [filter, setFilter] = useState('')
  const [appList, setAppList] = useState<DataApp[]>([])
  const [isGenEnpModalOpen, setIsGenEnpModalOpen] = useState(false)
  const [selectApp, setSelectApp] = useState('')
  const [generateAppFile, setGenerateAppFile] = useState<SqlFile | null>(null)
  const {
    store: { preloadedSQLEditor, currentCluster },
    actions: { setPreloadedSQLEditor }
  } = useStores()
  const history = useHistory()
  const { setChat2queryReady } = useHelpCenterContext()
  const [addFileTipsOpened, setAddFileTipsOpened] = useState(false)
  const { classes } = useStyles()
  const deleteMutation = useDeleteSqlEditorFile()
  const affixTips = useAffixTips()
  const newFileContent = useNewFileContent()

  const {
    data: datasources,
    isLoading,
    isFetching
  } = useListAppCluster(orgId, projectId, selectApp, {
    query: {
      refetchOnWindowFocus: false,
      enabled: !!orgId && !!projectId && !!selectApp
    }
  })

  useEffect(() => {
    init()
  }, [clusterId])

  useEffect(() => {
    const files = (sqlFiles && sqlFiles.slice()) || []
    setFilterSqlFiles(files.filter((file: SqlFile) => file.name.toLowerCase().includes(filter)))
  }, [sqlFiles])

  useEffect(() => {
    if (editedSqlFile?.id < 0) {
      return
    }

    try {
      localStorage.setItem('SqlEditorEditedFileId', `${editedSqlFile.id}`)

      const openFiles = openedFiles.slice()
      if (!openFiles.find((item) => item.id === editedSqlFile?.id)) {
        openFiles.push({
          id: editedSqlFile?.id,
          name: editedSqlFile?.name
        })
        setOpenedFiles(openFiles)
      }

      localStorage.setItem('SqlEditorOpenedFiles', JSON.stringify(openFiles.map((file) => file.id)))
    } catch (_e) {}
  }, [editedSqlFile?.id])

  const initNoImport = async () => {
    setChat2queryReady(false)
    const res = await getSqlEditorFiles(orgId, projectId, clusterId)

    if (res.code !== 200) {
      setLoading(false)
      return
    }

    const data = (res.data || []) as any as SqlFile[]

    if (!data.length) {
      setIsCreatingFile(true)
      createInitFile()
    } else {
      setSqlFiles(data)

      initOpenFiles(data)

      const lastFile = localStorage.getItem('SqlEditorEditedFileId') || ''
      const cur = data.find((file) => `${file.id}` === lastFile) || data[0]
      setEditedSqlFile(cur)
      editedSqlFileRef.current = cur

      // @FIXME after code change
      setChat2queryReady(true)
      setLoading(false)
    }
  }

  const createInitFile = async () => {
    const initName = 'Getting Started'
    const createRes = await createSqlEditorFile(orgId, projectId, clusterId, {
      name: initName,
      content: newFileContent
    })

    const file = {
      id: createRes.data,
      content: newFileContent,
      name: initName
    } as SqlFile

    setSqlFiles([file])

    setTimeout(() => {
      setEditedSqlFile(file)
      editedSqlFileRef.current = file
      setIsCreatingFile(false)
      setLoading(false)
      // @FIXME after code change
      setChat2queryReady(true)
    })
  }

  const initOpenFiles = (files: SqlFile[]) => {
    const lastIds = localStorage.getItem('SqlEditorOpenedFiles') || '[]'

    try {
      const openedIds = JSON.parse(lastIds)
      if (!openedIds.length) {
        return
      }

      const openedFiles: OpenedFile[] = []
      openedIds.forEach((id: number) => {
        const matchedFile = files.find((file) => file.id === id)
        if (matchedFile) {
          openedFiles.push({
            id: matchedFile.id,
            name: matchedFile.name
          })
        }
      })

      if (openedFiles.length) {
        setOpenedFiles(openedFiles)
      }
    } catch (_e) {}
  }

  const init = async () => {
    const { table = '', db = '' } = preloadedSQLEditor || {}

    if (!table || !db) {
      initNoImport()
      return
    }

    try {
      const res = await getIsTableExist(orgId, projectId, clusterId, db, table)
      const { is_exist, session_id } = res.data || {}
      if (!is_exist) {
        initNoImport()
        return
      }

      const name = 'New query'
      const content = `${preTips}\n${affixTips}\nUSE ${db};\nSELECT * from ${db}.${table} limit 10;`
      const createRes = await createSqlEditorFile(orgId, projectId, clusterId, {
        name,
        content
      })

      if (createRes.code !== 200) {
        initNoImport()
        return
      }

      setLoading(false)

      const file: SqlFile = {
        id: createRes.data || 0,
        content,
        sessionId: session_id,
        name
      }

      await getSqlFiles(false, file)

      setPreloadedSQLEditor(null)
      setIsFromImport(true)
    } catch (_e) {
      initNoImport()
    }
  }

  const deleteFileConfirm = async () => {
    if (!operFile.current) {
      return
    }

    await deleteMutation.mutateAsync({
      orgId,
      projectId,
      clusterId,
      sqlFileId: operFile.current?.id
    })

    // await deleteSqlEditorFile(orgId, projectId, clusterId, operFile.current?.id)
    setDeleteOpenVisible(false)

    const files = sqlFiles.filter((item) => item.id !== operFile.current?.id)
    setSqlFiles(files)

    const tabFiles = openedFiles.filter((item) => item.id !== operFile.current?.id)
    setOpenedFiles(tabFiles)

    if (`${operFile.current?.id}` === `${editedSqlFile.id}`) {
      let selectFile = files[0]
      if (tabFiles.length) {
        const file = files.find((file) => file.id === tabFiles[tabFiles.length - 1].id)
        if (file) {
          selectFile = file
        }
      }

      setEditedSqlFile(selectFile)
      editedSqlFileRef.current = selectFile
    }
  }

  const selectFileHandler = (file: SqlFile) => {
    const activeEditor = getEditor(file.id + '')
    let content = file.content
    if (activeEditor) {
      const { state } = activeEditor.editor
      content = state.doc.toString()
    }

    setEditedSqlFile({
      ...file,
      content
    })
    editedSqlFileRef.current = {
      ...file,
      content
    }
  }

  const filterChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { value } = e.target
    const val = value.toLowerCase()
    setFilter(val)

    if (!val) {
      setFilterSqlFiles(sqlFiles.slice())
      return
    }

    const list = sqlFiles.slice()
    const data = list.filter((file: SqlFile) => file.name.toLowerCase().includes(val))
    setFilterSqlFiles(data)
  }

  const clearFilter = () => {
    setFilter('')
    setFilterSqlFiles(sqlFiles.slice())
  }

  const addNewFile = async () => {
    if (isCreatingFile) {
      return
    }

    setIsCreatingFile(true)

    try {
      const fileParams = {
        name: `New query`,
        content: newFileContent
      }
      const createRes = await createSqlEditorFile(orgId, projectId, clusterId, fileParams)

      const { code, data: id } = createRes

      if (code !== 200) {
        setIsCreatingFile(false)
        return
      }

      const curFile = {
        id: id ?? -1,
        ...fileParams
      }

      setEditedSqlFile(curFile)
      editedSqlFileRef.current = curFile

      const files = sqlFiles.slice()
      files.unshift(curFile)

      setSqlFiles(files)
      setIsCreatingFile(false)

      const session = await createsSqlEditorSession(orgId, projectId, clusterId, {})
      const currentFile = {
        ...editedSqlFileRef.current,
        sessionId: session.data
      }

      setEditedSqlFile(currentFile)
      editedSqlFileRef.current = currentFile

      const file = files.find((item) => item.id === id)
      if (file) {
        file.sessionId = session.data
        setSqlFiles(files)
      }
    } catch {
      setIsCreatingFile(false)
    }
  }

  const fileRenameKeydown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (!renameFile?.name?.length && e.key === '-') {
      e.preventDefault()
      return false
    }

    if (e.key === 'Enter') {
      fileRenameBlur()
      return
    }

    if (!/[-A-Za-z0-9\s]/.test(e.key)) {
      e.preventDefault()
      return false
    }
  }

  const fileRenameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { value } = e.target

    setRenameFile({
      ...renameFile,
      name: value
    } as SqlFile)
  }

  const fileRenameBlur = async () => {
    setIsRename(false)

    let name = (renameFile?.name || '').trim()
    if (!renameFile || !name) {
      return
    }

    const preName = editedSqlFileRef.current.name
    if (renameFile.id === editedSqlFileRef.current.id) {
      setEditedSqlFile({
        ...editedSqlFileRef.current,
        name
      })
      editedSqlFileRef.current.name = name
    }

    updateName(name, renameFile.id)

    try {
      const res = await updateSqlEditorFile(orgId, projectId, clusterId, renameFile.id, {
        ...renameFile,
        name
      })

      if (res.code !== 200) {
        notifier.error(res.message)
        // reset file name
        if (preName !== name) {
          updateName(preName, renameFile.id)
        }
        return
      }

      getSqlFiles()
    } catch (e) {
      if (preName !== name) {
        updateName(preName, renameFile.id)
      }
      notifier.error(getErrorMessage(e))
    }
  }

  const updateName = (name: string, id: number) => {
    const files = [...sqlFiles]
    const file = files.find((item) => item.id === id)
    if (file) {
      file.name = name
      setSqlFiles(files)
    }

    const openedList = [...openedFiles]
    const opened = openedList.find((item) => item.id === id)
    if (opened) {
      opened.name = name
      setOpenedFiles(openedList)
    }
  }

  const getAppList = async () => {
    try {
      const res = await getDataApps(orgId, projectId, {
        apptype: 'dataapi'
      })
      if (res.code === 200) {
        setAppList((res.data || []) as any as DataApp[])
        setIsGenEnpModalOpen(true)
      }
    } catch (_e) {}
  }

  useEffect(() => {
    if (isGenEnpModalOpen) {
      form.setFieldValue('app_name', generateAppFile?.name || '')
      setSelectApp(appList.length ? appList[0].id : '')
    }
  }, [isGenEnpModalOpen, appList])

  const form = useForm({
    validateInputOnChange: true,
    initialValues: {
      app_name: ''
    },
    validate: {
      app_name: (value) => {
        return !value ? 'Please enter your endpoint name.' : null
      }
    }
  })

  const generateApp = async (values: { app_name: string }) => {
    const { app_name } = values
    const createRes = await createEndpoint(orgId, projectId, selectApp, {
      name: filterEmojis(app_name),
      sql_template: generateAppFile?.content,
      method: 'GET',
      cluster_id: isLoading || isFetching ? '0' : (datasources?.data?.[0]?.cluster_id ?? '0'),
      settings: {
        row_limit: 1000,
        timeout: 30000
      }
    })

    if (createRes.code === 200) {
      const endp = createRes?.endpoint as EndPoint
      localStorage.setItem('data-service-edit-endpoint', `${endp.id}-${endp.dataappp_id}`)
      localStorage.setItem('data-service-edit-app', '')
      closeAppModal()
      history.push('/project/data-service')
    }
  }

  const closeAppModal = () => {
    setIsGenEnpModalOpen(false)
    form.reset()
  }

  const createApp = async () => {
    const res = await createDataApp(orgId, projectId, {
      app_name: 'New App',
      app_type: 'dataapi',
      cluster_ids: [clusterId]
    })

    if (res.code === 200) {
      setAppList([
        {
          ...res.data?.app_info,
          endpoints: [
            {
              ...(res.data?.endpoint as any as EndPoint)
            }
          ]
        } as any as DataApp
      ])
    }
  }

  return (
    <Flex direction="column" h="100%">
      <Flex
        gap={8}
        sx={{
          position: 'relative',
          margin: '12px 16px 0 8px'
        }}
      >
        <Popover shadow="md" withArrow position="left" opened={addFileTipsOpened}>
          <Popover.Target>
            <ActionIcon
              aria-label="Create a new SQL file"
              variant="default"
              size={32}
              loading={isCreatingFile}
              data-mp-event="SQL Editor Add New SQL Editor File Button Clicked"
              data-mp-position="Side Menu"
              data-testid="chat2query-create-new-file"
              onClick={() => {
                console.log(addFileTipsOpened)
                addNewFile()
                setAddFileTipsOpened(false)
              }}
              onMouseEnter={() => setAddFileTipsOpened(true)}
              onMouseLeave={() => setAddFileTipsOpened(false)}
            >
              <IconPlus size={16} />
            </ActionIcon>
          </Popover.Target>
          <Popover.Dropdown>Create a new SQL file</Popover.Dropdown>
        </Popover>

        <Input
          maxLength={64}
          placeholder="Search"
          styles={() => ({
            flex: 1,
            input: {
              height: 32,
              minHeight: 32,
              fontSize: 14
            }
          })}
          w={'100%'}
          value={filter}
          onFocus={() => eventTracking('SQL Editor Search File Focus')}
          onChange={filterChange}
        />
        {filter && (
          <ActionIcon
            sx={(theme) => ({
              position: 'absolute',
              right: 24,
              top: 2,
              color: theme.colors.carbon[7],
              cursor: 'pointer'
            })}
          >
            <IconXClose size={18} onClick={clearFilter} />
          </ActionIcon>
        )}
      </Flex>

      {loading && (
        <Box mt={24} mb={24} mr={16} sx={{ overflow: 'hidden' }}>
          {Array(20)
            .fill(0)
            .map((_, idx) => (
              <Skeleton mb={24} height={16} key={idx} />
            ))}
        </Box>
      )}

      <Box
        sx={{
          position: 'relative',
          padding: '8px 16px 8px 8px',
          overflowY: 'auto',
          zIndex: 1,
          flex: 1
        }}
      >
        {!loading && !!sqlFiles.length && (
          <>
            {!filterSqlFiles.length && (
              <Typography variant="body-lg" mt={24}>
                No query files
              </Typography>
            )}

            {filterSqlFiles.map((file: SqlFile) => (
              <Flex
                key={file.id}
                align="center"
                justify="space-between"
                sx={(theme) => ({
                  color: theme.colors.carbon[8],
                  fontSize: 14,
                  borderRadius: theme.defaultRadius,
                  height: '32px',
                  padding: '0 8px',
                  cursor: 'pointer',
                  whiteSpace: 'nowrap',
                  marginBottom: 2,
                  backgroundColor: file.id === editedSqlFile?.id ? theme.colors.carbon[4] : theme.white,
                  '&:hover': {
                    backgroundColor: file.id === editedSqlFile?.id ? theme.colors.carbon[4] : theme.colors.carbon[2]
                  }
                })}
                className={classes.fileItem}
              >
                <Flex
                  align="center"
                  sx={() => ({
                    flex: 1,
                    overflow: 'hidden',
                    textOverflow: 'ellipsis',
                    lineHeight: '32px'
                  })}
                  onClick={() => selectFileHandler(file)}
                >
                  <Box component={IconFile06} mr={8} size={16} />

                  {isRename && operFile.current?.id === file.id ? (
                    <Input
                      autoFocus
                      value={renameFile?.name}
                      maxLength={64}
                      onKeyDown={fileRenameKeydown}
                      onChange={fileRenameChange}
                      onBlur={fileRenameBlur}
                      sx={() => ({
                        flex: 1,
                        input: {
                          height: 24,
                          minHeight: 24,
                          lineHeight: '24px'
                        }
                      })}
                    />
                  ) : (
                    <Box sx={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis' }}>{file.name}</Box>
                  )}
                </Flex>

                {!(isRename && operFile.current?.id === file.id) && (
                  <Menu offset={-2} withinPortal={false}>
                    <Menu.Target>
                      <ActionIcon className="dropdown" variant="subtle">
                        <IconDotsHorizontal size={16} />
                      </ActionIcon>
                    </Menu.Target>
                    <Menu.Dropdown>
                      <Menu.Item
                        data-mp-event="SQL Editor File Rename Button Clicked"
                        onClick={() => {
                          setIsRename(true)

                          const activeEditor = getEditor(file.id + '')

                          let content = file.content
                          if (activeEditor) {
                            const { state } = activeEditor.editor
                            content = state.doc.toString()
                          }

                          setRenameFile({
                            ...file,
                            content
                          })
                          operFile.current = {
                            ...file,
                            content
                          }
                        }}
                      >
                        Rename
                      </Menu.Item>
                      {currentCluster?.computed.isServerless && (
                        <Menu.Item
                          onClick={() => {
                            setGenerateAppFile(file)
                            getAppList()
                          }}
                        >
                          Generate endpoint
                        </Menu.Item>
                      )}

                      {sqlFiles.length > 1 && (
                        <Menu.Item
                          onClick={() => {
                            eventTracking('SQL Editor File Delete Button Clicked')

                            setDeleteOpenVisible(true)
                            operFile.current = file
                          }}
                        >
                          Delete
                        </Menu.Item>
                      )}
                    </Menu.Dropdown>
                  </Menu>
                )}
              </Flex>
            ))}
          </>
        )}

        <Modal
          opened={deleteOpenVisible}
          centered
          size={400}
          onClose={() => setDeleteOpenVisible(false)}
          title="Delete SQL File"
        >
          <Typography variant="label-lg">Are you sure you want to delete "{operFile.current?.name}"?</Typography>
          <Group justify="flex-end" mt="md">
            <Button
              variant="default"
              data-mp-event="Delete File Cancel Button Clicked"
              onClick={() => setDeleteOpenVisible(false)}
            >
              Cancel
            </Button>
            <Button
              variant="light"
              color="red"
              loading={deleteMutation.isLoading}
              data-mp-event="Delete File Confirm Button Clicked"
              onClick={deleteFileConfirm}
            >
              Confirm
            </Button>
          </Group>
        </Modal>
      </Box>

      <Modal opened={isGenEnpModalOpen} centered size={400} onClose={() => closeAppModal()} title="Generate endpoint">
        <form onSubmit={form.onSubmit((values) => generateApp(values))}>
          <Select
            label="Generate endpoint to Data App"
            placeholder="select a Data App"
            data={appList.map((item) => {
              return {
                label: item.app_name,
                value: `${item.id}`
              }
            })}
            searchable
            value={selectApp}
            maxDropdownHeight={400}
            onChange={(value) => {
              setSelectApp(value || '')
            }}
            nothingFoundMessage={
              <Group
                align="center"
                gap={8}
                c="carbon.8"
                px={8}
                py={4}
                sx={(t) => ({
                  cursor: 'pointer',
                  '&:hover': {
                    background: t.colors.carbon[3]
                  },
                  borderRadius: 4
                })}
                onClick={createApp}
              >
                <IconPlus size={16} />
                <Typography variant="body-lg">Create a Data App</Typography>
              </Group>
            }
          />

          <TextInput mt={16} label="Endpoint Name" placeholder="input name" {...form.getInputProps('app_name')} />

          <Group justify="flex-end" mt={32}>
            <Button variant="default" onClick={() => closeAppModal()} data-mp-event="mp-cancel-1691461013931">
              Cancel
            </Button>
            <Button type="submit" data-mp-event="mp-generate-1691461013933">
              Generate
            </Button>
          </Group>
        </form>
      </Modal>
    </Flex>
  )
}

export default observer(SqlFiles)
