import { ActionIcon, Box, Flex, HoverCard, Input, Skeleton, Typography } from '@tidbcloud/uikit'
import { Tree } from '@tidbcloud/uikit/biz'
import { useDebouncedValue } from '@tidbcloud/uikit/hooks'
import {
  IconBrackets,
  IconCalendar,
  IconDatabase01,
  IconDatabaseSystem,
  IconLayoutGrid02,
  IconNumberic01,
  IconRefreshCw02,
  IconString01,
  IconVector,
  IconXClose
} from '@tidbcloud/uikit/icons'
import { cloneDeep } from 'lodash-es'
import { observer } from 'mobx-react'
import { useEffect, useMemo, useRef, useState } from 'react'

import DbErrorMsg, { ErrorInfo } from 'dbaas/screens/SqlEditorV2/DatabaseList/DbErrorMsg'
import { useStyles } from 'dbaas/screens/SqlEditorV2/DatabaseList/index.styles'
import { getAllDbData } from 'dbaas/services'
import useStores from 'dbaas/stores/useStores'
import { eventTracking } from 'dbaas/utils/tracking'

import { useDataApiContext } from '../context'
import { showErrorMsg } from '../helpers'

type DB = {
  name: string
  tables: {
    name: string
    columns: Columns[]
  }[]
}[]

type Columns = {
  col: string
  data_type: string
}

const DatabaseList = () => {
  const {
    store: { activeProjectId, tenantData }
  } = useStores()
  const { dbList, setDbList, isDbNeedFresh, setIsDbNeedFresh, editEndpoint, clusterDataSource } = useDataApiContext()
  const [loading, setLoading] = useState(true)
  const [dbError, setDbError] = useState(false)
  const [dbErrorMsg, setDbErrorMsg] = useState<ErrorInfo | undefined>()
  const ref = useRef<HTMLDivElement>(null)
  const [filter, setFilter] = useState('')
  const filterRef = useRef('')
  const [debouncedSearchText] = useDebouncedValue(filter, 300)
  const [dbAllData, setDbAllData] = useState<any[]>([])
  const dbAllDataRef = useRef<any[]>([])
  const [filterDbList, setFilterDbList] = useState<any[]>([])
  const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([])
  const [autoExpandParent, setAutoExpandParent] = useState(false)
  const expandedKeysRef = useRef<React.Key[]>([])
  const loadedRef = useRef(false)
  const { classes } = useStyles()

  useEffect(() => {
    getAll()
  }, [])

  useEffect(() => {
    if (isDbNeedFresh) {
      setLoading(true)
      getAll()
      setIsDbNeedFresh(false)
    }
  }, [isDbNeedFresh])

  useEffect(() => {
    expandedKeysRef.current = expandedKeys
  }, [expandedKeys])

  useEffect(() => {
    if (!editEndpoint || !editEndpoint.cluster_id || editEndpoint.cluster_id === '0') {
      return
    }

    if (!clusterDataSource?.some((item) => item.cluster_id === editEndpoint.cluster_id)) {
      return
    }

    setDbList([])
    getAll()
  }, [editEndpoint?.cluster_id])

  const formatDb = (data: DB) => {
    return data.map((db, index) => {
      return {
        name: db.name,
        isLeaf: !(db.tables || []).length,
        loaded: true,
        key: `${index}`,
        tables: db.tables,
        children: (db.tables || []).map((table, idx) => {
          return {
            name: table.name,
            isLeaf: false,
            loaded: false,
            key: `${index}-${idx}`,
            columns: table.columns,
            children: (table.columns || []).map((meta, metaIndex) => {
              return {
                title: (
                  <Typography variant="body-md" c="carbon.7" lh="28px" truncate>
                    {meta.col}
                  </Typography>
                ),
                isLeaf: true,
                name: meta.col,
                key: `${index}-${idx}-${metaIndex}`,
                icon: getSchemaIcon(meta.data_type),
                children: [],
                type: meta.data_type
              }
            })
          }
        })
      }
    })
  }

  const getAll = async () => {
    setDbError(false)
    setFilterDbList([])
    setDbList([])
    setLoading(true)

    try {
      const res = await getAllDbData(tenantData.id, activeProjectId, `${editEndpoint?.cluster_id}`, {
        isbrief: 'false'
      })

      setLoading(false)

      if (res.code !== 200) {
        setDbError(true)
        setDbErrorMsg(
          res.message
            ? {
                code: res.code as number,
                message: res.message
              }
            : undefined
        )
        return
      }

      const dbs = formatDb((res?.data ?? []) as DB)
      setDbList(dbs)
      setDbAllData(dbs)
      dbAllDataRef.current = dbs
      loadedRef.current = true
      setFilterDbList(dbs)
    } catch (e: any) {
      setLoading(false)
      setDbError(true)
      showErrorMsg(e)
      setDbErrorMsg(
        e.response?.data?.message
          ? {
              code: e.response.data?.code,
              message: e.response.data.message
            }
          : undefined
      )
    }
  }

  useEffect(() => {
    const keys = localStorage.getItem('Chat2querySelectedKeys') || '[]'
    const keyData = JSON.parse(keys)
    setExpandedKeys(keyData)
  }, [dbAllData])

  const toggleExpand = (dbKey: string, tabKey?: string) => {
    const keys = new Set(expandedKeysRef.current)

    if (tabKey) {
      if (!keys.delete(tabKey)) {
        keys.add(tabKey)
      }
    } else {
      keys.has(dbKey) ? keys.delete(dbKey) : keys.add(dbKey)
    }

    onExpand(Array.from(keys))
  }

  const getSchemaIcon = (typeRaw: string) => {
    const type = typeRaw.toUpperCase()
    if (
      [
        'BIT',
        'BOOLEAN',
        'TINYINT',
        'SMALLINT',
        'MEDIUMINT',
        'INTEGER',
        'INT',
        'BIGINT',
        'FLOAT',
        'DOUBLE',
        'DECIMAL',
        'NUMERIC'
      ].includes(type)
    ) {
      return <IconNumberic01 />
    }

    if (
      [
        'CHAR',
        'VARCHAR',
        'TEXT',
        'TINYTEXT',
        'MEDIUMTEXT',
        'LONGTEXT',
        'BINARY',
        'VARBINARY',
        'BLOB',
        'TINYBLOB',
        'MEDIUMBLOB',
        'LONGBLOB',
        'ENUM',
        'SET'
      ].includes(type)
    ) {
      return <IconString01 />
    }

    if (['DATE', 'TIME', 'DATETIME', 'TIMESTAMP', 'YEAR'].includes(type)) {
      return <IconCalendar />
    }

    if (type === 'JSON') {
      return <IconBrackets />
    }

    if (['VECTOR<FLOAT>', 'VECTOR<FLOAT16>', 'VECTOR'].includes(type)) {
      return <IconVector color="#999" strokeWidth={2} />
    }
  }

  const handleFilter = () => {
    if (!debouncedSearchText) {
      setFilterDbList(dbAllData)
      return
    }
    const newExpandedKeys: any = new Set()

    if (debouncedSearchText !== filterRef.current) {
      return
    }

    const dbs = (cloneDeep(dbAllData) || []).filter((db: any) => {
      db.children = (db.children || []).filter((table: any) => {
        const prevChildren = table.children
        const metas = (table.children || []).filter((meta: any) => {
          return meta.name.toLowerCase().includes(debouncedSearchText)
        })
        table.children = metas
        if (metas.length) {
          newExpandedKeys.add(table.key)
        } else {
          newExpandedKeys.delete(table.key)
          if (table.name.toLowerCase().includes(debouncedSearchText)) {
            newExpandedKeys.add(db.key)
            table.children = prevChildren
          }
        }
        return newExpandedKeys.has(table.key) || table.name.toLowerCase().includes(debouncedSearchText)
      })
      return db.children.length || db.name.toLowerCase().includes(debouncedSearchText)
    })

    setFilterDbList(dbs)

    setAutoExpandParent(true)
    setExpandedKeys(Array.from(newExpandedKeys))
  }

  useEffect(() => {
    handleFilter()
  }, [debouncedSearchText])

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

    setFilter(val)
    filterRef.current = val

    if (!val) {
      setTimeout(() => {
        clearFilter()
      }, 300)
      return
    }
  }

  const clearFilter = () => {
    setFilter('')
    filterRef.current = ''

    const keys = localStorage.getItem('Chat2querySelectedKeys') || '[]'
    onExpand(JSON.parse(keys))
  }

  const onExpand = (keys: React.Key[]) => {
    setAutoExpandParent(false)
    const filters = keys.filter((key: string) => {
      const arrs = `${key}`.split('-')
      if (keys.includes(arrs[0])) {
        return true
      }
      return false
    })
    setExpandedKeys(filters)
    localStorage.setItem('Chat2querySelectedKeys', JSON.stringify(filters))
  }

  const treeData = useMemo(() => {
    const systemDatabse = ['INFORMATION_SCHEMA', 'PERFORMANCE_SCHEMA', 'MYSQL']
    return filterDbList.map((db, index) => {
      // table level
      if (db.children) {
        db.children = db.children.map((table: any, idx: number) => {
          return {
            ...table,
            title: (
              <Typography
                c="carbon.7"
                lh="28px"
                sx={{
                  flex: 1
                }}
                truncate
                onClick={() => toggleExpand(`${index}`, `${index}-${idx}`)}
              >
                {table.name}
              </Typography>
            ),
            icon: <IconLayoutGrid02 />
          }
        })
      }
      // database level
      return {
        ...db,
        title: (
          <Typography
            variant="body-md"
            c="carbon.7"
            lh="28px"
            sx={{
              flex: 1
            }}
            truncate
          >
            <span onClick={() => toggleExpand(`${index}`)}>{db.name}</span>
          </Typography>
        ),
        icon: systemDatabse.includes(db.name.toUpperCase()) ? (
          <IconDatabaseSystem strokeWidth={2} />
        ) : (
          <IconDatabase01 />
        )
      }
    })
  }, [filterDbList])

  return (
    <Flex direction="column" h="100%" pb={40}>
      <Flex mt={12} p="0 16px 0 16px">
        <HoverCard withArrow shadow="md" position="left">
          <HoverCard.Target>
            <ActionIcon variant="default" size={32}>
              <IconRefreshCw02 onClick={getAll} size={16} />
            </ActionIcon>
          </HoverCard.Target>
          <HoverCard.Dropdown>Refresh</HoverCard.Dropdown>
        </HoverCard>

        <Box
          sx={{
            position: 'relative',
            marginLeft: 8,
            flex: 1
          }}
        >
          <Input
            maxLength={64}
            placeholder="Search"
            value={filter}
            styles={() => ({
              input: {
                height: 32,
                minHeight: 32,
                fontSize: 14
              }
            })}
            onFocus={() => eventTracking('Data Service Search File Focus')}
            onInput={filterChange}
          />
          {filter && (
            <ActionIcon
              sx={{
                position: 'absolute',
                right: 4,
                top: 2,
                color: '#999',
                cursor: 'pointer'
              }}
            >
              <IconXClose size={18} onClick={clearFilter} />
            </ActionIcon>
          )}
        </Box>
      </Flex>

      {!!loading && (
        <Box m="24px 16px 24px 16px" sx={{ overflow: 'hidden' }}>
          {Array(20)
            .fill(0)
            .map((_, i) => (
              <Skeleton key={i} mb={24} height={16} />
            ))}
        </Box>
      )}

      <Box
        sx={{
          position: 'relative',
          flex: 1,
          overflowY: 'auto',
          marginTop: 8,
          padding: '0 16px 0 16px',
          zIndex: 1
        }}
        ref={ref}
      >
        {!loading && !dbError && (
          <Tree
            className={classes.dbListTree}
            showIcon
            showLine={false}
            treeData={treeData}
            autoExpandParent={autoExpandParent}
            expandedKeys={expandedKeys as any[]}
            onExpand={onExpand}
          />
        )}

        {!dbError && !dbList.length && !loading && (
          <Typography variant="body-lg" mt={32} sx={{ fontStyle: 'italic' }}>
            No user-defined database available
          </Typography>
        )}

        {!!dbError && !dbList.length && !loading && <DbErrorMsg errInfo={dbErrorMsg} />}
      </Box>
    </Flex>
  )
}

export default observer(DatabaseList)
