import React, { useCallback, useEffect, useMemo, useState } from 'react'

import {
  Accordion,
  ActionIcon,
  Card,
  Checkbox,
  createStyles,
  Group,
  Input,
  Loader,
  Space,
  Table,
  Text,
  Grid,
  Center
} from '@mantine/core'
import { IconArrowLeft } from '@tabler/icons-react'
import { useDebounce } from 'ahooks'
import { isObject } from 'lodash'
import { useNavigate } from 'react-router-dom'

import { clusterApi, getTiflashConfig } from '~/api'
import { getAllConfigurations } from '~/api/dashboard'
import { useCurCluster, useCurOrg } from '~/hooks/state'

interface IConfig {
  id: string
  value: string
  kind: string
  defaultValue: string
  valueType: string
}

const useStyles = createStyles({
  cardContainer: {
    height: 'calc(100vh - var(--mantine-header-height, 0px) - 48px)'
  },
  content: {
    padding: 0
  },
  item: {
    border: 0
  }
})

const componentMap: {
  [k: string]: { label: string; configKey: string; defaultKey: string }
} = {
  pd: {
    label: 'PD Configurations',
    configKey: 'pd_config',
    defaultKey: 'pd'
  },
  tidb: {
    label: 'TiDB Configurations',
    configKey: 'tidb_config',
    defaultKey: 'tidb'
  },
  tikv: {
    label: 'TiKV Configurations',
    configKey: 'tikv_config',
    defaultKey: 'tikv'
  },
  tiflash: {
    label: 'TiFlash Configurations',
    configKey: 'tiflash_config',
    defaultKey: 'tiflash'
  }
}

const ConfigurationsPage: React.FC = () => {
  const navigate = useNavigate()
  const [filterValueLower, setFilterValueLower] = useState('')
  const debouncedFilterValue = useDebounce(filterValueLower, {
    wait: 400
  })
  const handleFilterChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      setFilterValueLower(e.target.value.toLowerCase())
    },
    []
  )
  const [noDefault, setNoDefault] = useState(false)
  const { configMap, groups, loading } = useConfigurations(
    debouncedFilterValue,
    noDefault
  )
  const configKinds = useMemo(() => groups.map((g) => g.key), [groups])
  const [bodyAccordionKeys, setBodyAccordionKeys] = useState<string[] | null>(
    null
  )
  const { classes } = useStyles()

  function handleHeadAccordionChange(val: string | null) {
    if (val) {
      // expand all kinds of configurations
      setBodyAccordionKeys(configKinds)
    } else {
      setBodyAccordionKeys([])
    }
  }
  function handleBodyAccordionChange(val: string[]) {
    setBodyAccordionKeys(val)
  }

  return (
    <Card withBorder className={`${classes.cardContainer} flex flex-col`}>
      <Group>
        <ActionIcon variant='subtle' onClick={() => navigate(-1)}>
          <IconArrowLeft size={16} />
        </ActionIcon>
        <Input
          className='grow max-w-3xl'
          placeholder='Search Config Name'
          onChange={handleFilterChange}
        />
        <Checkbox
          className='pt-[6px]'
          label='Only view non-default'
          checked={noDefault}
          onChange={(event) => setNoDefault(event.currentTarget.checked)}
        />
      </Group>

      <Space h={32} />

      <Card.Section>
        <Accordion
          chevronPosition='left'
          onChange={handleHeadAccordionChange}
          defaultValue='table-head'
        >
          <Accordion.Item value='table-head'>
            <Accordion.Control>
              <Grid>
                {['Config', 'Value', 'Default Value'].map((c) => (
                  <Grid.Col span={4}>
                    <Text className='text-left' key={c}>
                      {c}
                    </Text>
                  </Grid.Col>
                ))}
              </Grid>
            </Accordion.Control>
          </Accordion.Item>
        </Accordion>
      </Card.Section>

      {loading ? (
        <Center style={{ height: '90%' }}>
          <Loader variant='bars' size='md' />
        </Center>
      ) : (
        <Card.Section className='pb-4 flex-1 overflow-auto'>
          <Accordion
            classNames={classes}
            value={bodyAccordionKeys === null ? configKinds : bodyAccordionKeys}
            onChange={handleBodyAccordionChange}
            multiple
            chevronPosition='left'
          >
            {groups.map((g) => {
              const configs = configMap[g.key]
              return (
                <Accordion.Item key={g.key} value={g.key}>
                  <Accordion.Control>
                    {g.name} ({g.count})
                  </Accordion.Control>
                  <Accordion.Panel>
                    {configs && (
                      <Table verticalSpacing='sm' highlightOnHover>
                        <tbody>
                          {configs.map((row) => (
                            <tr key={row.id}>
                              <td>
                                <Grid className='pl-[34px]'>
                                  <Grid.Col span={4}>
                                    <Text className='text-left'>{row.id}</Text>
                                  </Grid.Col>
                                  <Grid.Col span={4}>
                                    <Text className='text-left'>
                                      <code className='text-blue-600'>
                                        {row.valueType === 'string'
                                          ? `"${row.value}"`
                                          : row.value}
                                      </code>
                                    </Text>
                                  </Grid.Col>
                                  <Grid.Col span={4}>
                                    <Text className='text-left'>
                                      {row.valueType === 'string'
                                        ? `"${row.defaultValue}"`
                                        : row.defaultValue}
                                    </Text>
                                  </Grid.Col>
                                </Grid>
                              </td>
                            </tr>
                          ))}
                        </tbody>
                      </Table>
                    )}
                  </Accordion.Panel>
                </Accordion.Item>
              )
            })}
          </Accordion>
        </Card.Section>
      )}
    </Card>
  )
}

const useConfigurations = (filter: string, noDefault: boolean) => {
  const [loading, setLoading] = useState(false)
  const [dashboardConfigs, setDashboardConfigs] = useState<any>(null)
  const [defaultConfigs, setDefaultConfigs] = useState<any>({})
  const [tiflashConfigs, setTiflashConfigs] = useState<any[]>([])
  const { curCluster } = useCurCluster()
  const { curOrg } = useCurOrg()

  const { configs, groups } = useMemo(() => {
    const v = filter.trim()
    const newConfigs: IConfig[] = []
    const newGroups: {
      key: string
      name: string
      startIndex: number
      count: number
    }[] = []
    let startIndex = 0
    const defaultConfigMap = Object.entries(defaultConfigs).reduce(
      (prev, cur) => {
        const key = cur[0]
        const values = cur[1] as any
        const map = new Map()
        values.forEach((v: any) => map.set(v.itemName, v))
        prev[key] = map
        return prev
      },
      {} as { [k: string]: Map<string, any> }
    )

    for (const configKind of ['pd', 'tikv', 'tidb']) {
      const cpMap = componentMap[configKind]
      const items = dashboardConfigs?.[cpMap.configKey] ?? []
      const defaultMap = defaultConfigMap[cpMap.defaultKey]
      for (const item of items) {
        const dv = defaultMap?.get(item.id)?.defaultValue
        const defaultValue = String(dv === undefined ? '' : dv).toLowerCase()
        const value = String(item.value).toLowerCase()
        if (v.length > 0) {
          if (
            item.id?.toLowerCase().indexOf(v) === -1 &&
            value.indexOf(v) === -1
          ) {
            continue
          }
        }
        if (noDefault && (!value || value === defaultValue)) {
          continue
        }

        newConfigs.push({
          ...item,
          kind: cpMap.configKey,
          defaultValue,
          value,
          valueType: typeof item.value
        })
      }
      newGroups.push({
        key: cpMap.configKey,
        name: cpMap.label,
        startIndex: startIndex,
        count: newConfigs.length - startIndex
      })
      startIndex = newConfigs.length
    }

    // tiflash configs
    if (!!curCluster?.topology?.tiflash) {
      for (const item of tiflashConfigs) {
        const defaultMap = defaultConfigMap.tiflash
        const dv = defaultMap?.get(item.id)?.defaultValue
        const defaultValue = String(dv === undefined ? '' : dv).toLowerCase()
        const value = String(item.value).toLowerCase()
        if (v.length > 0) {
          if (
            item.id?.toLowerCase().indexOf(v) === -1 &&
            value.indexOf(v) === -1
          ) {
            continue
          }
        }
        if (noDefault && (!value || value === defaultValue)) {
          continue
        }

        newConfigs.push({
          ...item,
          kind: 'tiflash',
          defaultValue,
          value,
          valueType: typeof item.value
        })
      }
      newGroups.push({
        key: 'tiflash',
        name: componentMap.tiflash.label,
        startIndex,
        count: newConfigs.length - startIndex
      })
    }

    return { configs: newConfigs, groups: newGroups }
  }, [
    filter,
    noDefault,
    dashboardConfigs,
    defaultConfigs,
    tiflashConfigs,
    curCluster
  ])
  const configMap = useMemo(
    () =>
      configs.reduce((prev, cur) => {
        if (!prev[cur.kind]) {
          prev[cur.kind] = []
        }
        prev[cur.kind].push(cur)
        return prev
      }, {} as { [k: string]: IConfig[] }),
    [configs]
  )

  useEffect(() => {
    async function fetchConfig() {
      setLoading(true)
      const [res1, res2] = await Promise.all([
        getAllConfigurations(curOrg?.externalID!, curCluster?.id!),
        clusterApi.orgsOidClustersCidDefaultconfGet(
          '',
          curOrg?.id!,
          curCluster?.id!
        )
      ])

      setDashboardConfigs(res1.data.items)
      setDefaultConfigs(res2.data.items)
      setLoading(false)
    }
    async function fetchTiflashConfig() {
      try {
        const res = await getTiflashConfig(curOrg?.externalID!, curCluster?.id!)
        setTiflashConfigs(flatConfig(res.data))
      } catch (e) {}
    }

    fetchConfig()
    if (!!curCluster?.topology?.tiflash) {
      fetchTiflashConfig()
    }
  }, [])

  return { configMap, groups, loading }
}

interface Config {
  [k: string]: Config
}

const flatConfig = (configs: Config): { id: string; value: string }[] => {
  const cfgsArr: { id: string; value: string }[] = []
  const _flatConfig = (prefix: string, cfgs: Config) => {
    Object.entries(cfgs).forEach(([k, v]) => {
      const key = prefix ? `${prefix}.${k}` : k
      if (isObject(v)) {
        _flatConfig(key, v)
      } else {
        cfgsArr.push({ id: key, value: v })
      }
    })
  }
  _flatConfig('', configs)
  return cfgsArr
}

export default ConfigurationsPage
