import {
  NumberInput,
  Select,
  Textarea,
  TextInput,
  Badge,
  Box,
  Flex,
  Typography,
  HoverCard,
  Switch,
  Input,
  Accordion,
  Alert,
  FlexProps,
  NumberInputProps
} from '@tidbcloud/uikit'
import { CodeBlock } from '@tidbcloud/uikit/biz'
import { useDebouncedValue } from '@tidbcloud/uikit/hooks'
import { IconChevronRightFill, IconInfoCircle } from '@tidbcloud/uikit/icons'
import DOMPurify from 'dompurify'
import { isNil } from 'lodash-es'
import React, { useEffect, useState } from 'react'

import { useHelpCenterContext } from 'dbaas/screens/HelpCenter/context'
import { useRequestSupport } from 'dbaas/screens/HelpCenter/RequestSupport/helper'
import { useGetEndpointBaseUrl } from 'dbaas/services/dataservice'
import useStores from 'dbaas/stores/useStores'
import { eventTracking } from 'dbaas/utils/tracking'

import SystemEndpointCodeExample from '../Chat2queryEndpoint/CodeExample'
import BatchOperTips from '../common/BatchOperTips'
import { useDataApiContext } from '../context'
import CodeExample from '../EditPointActionList/CodeExample'
import { useTestEndpoint, isPathValid } from '../helpers'
import { EndPoint } from '../types'

interface PropertiesLayoutProps extends FlexProps {
  label: string | React.ReactNode
  tooltip?: string | React.ReactNode
  dataGuide?: string
}

const PropertiesLayout: React.FC<React.PropsWithChildren<PropertiesLayoutProps>> = ({
  label,
  tooltip,
  children,
  dataGuide,
  ...rest
}) => {
  return (
    <Flex align="center" justify="space-between" mt={8} {...rest} data-guide={dataGuide}>
      <Flex align="center">
        <Typography variant="body-sm" fw={600}>
          {label}
        </Typography>
        {tooltip && (
          <HoverCard width={320} withArrow position="top" shadow="md">
            <HoverCard.Target>
              <Box component={IconInfoCircle} size={14} c="carbon.8" ml={2} sx={{ cursor: 'pointer' }}></Box>
            </HoverCard.Target>
            <HoverCard.Dropdown>
              <Typography variant="body-sm">{tooltip}</Typography>
            </HoverCard.Dropdown>
          </HoverCard>
        )}
      </Flex>
      {children}
    </Flex>
  )
}

export const usePathErrMsg = () => {
  const { editEndpointRef, isPathErr, isPathConflict, autoSaveErrorMsg } = useDataApiContext()

  if (isPathErr) {
    const path = editEndpointRef.current?.endpoint || ''
    if (path.indexOf('{page}') !== -1 || path.indexOf('{page_size}') !== -1) {
      return (
        <Box>
          <b>page</b> and <b>page_size</b> are build-in paging variables in the system, please replace the name of
          parameters. Refer to the documentation [
          <a
            href="https://docs.pingcap.com/tidbcloud/data-service-manage-endpoint#advanced-properties"
            target="_blank"
            rel="noreferrer"
          >
            Pagination for Endpoint
          </a>
          ]
        </Box>
      )
    }
    return 'Path must start with a forward slash (/), contain only letters, numbers, underscores, and not end with a forward slash (/)'
  }

  if (isPathConflict) {
    return autoSaveErrorMsg
  }

  return ''
}

const Properties = ({ resizing }: { resizing: boolean }) => {
  const {
    orgId,
    projectId,
    openedEndpList,
    setOpenedEndpList,
    editEndpoint,
    editEndpointRef,
    setEditEndpoint,
    appList,
    setAppList,
    appListRef,
    setIsPathErr,
    setIsPathConflict,
    pathForHelpCenter,
    setPathForHelpCenter,
    layout: { rightPanel }
  } = useDataApiContext()
  const { editAppInHelpCenter } = useHelpCenterContext()
  const [codeExampleOpened, setCodeExampleOpened] = useState(false)
  const [debouncedEndpoint] = useDebouncedValue(editEndpoint?.endpoint, 1000)
  const {
    store: { currentCluster }
  } = useStores()
  const { saveEndpoint } = useTestEndpoint()
  const [path, setPath] = useState(editEndpoint?.endpoint || '')
  const [tag, setTag] = useState(editEndpoint?.tag || '')
  const { open: openSupport } = useRequestSupport()
  const isSystemEndpoint = editEndpoint?.type === 'system-data'
  const pathErrMsg = usePathErrMsg()

  useEffect(() => {
    setIsPathErr(false)
    setIsPathConflict(false)
    if (editEndpoint) {
      setPath(editEndpoint.endpoint || '')
    }
  }, [editEndpoint?.id])

  // synchronization path in helpcenter and local
  useEffect(() => {
    if (pathForHelpCenter === '') return
    !isNil(editAppInHelpCenter) && setPath(pathForHelpCenter)
  }, [pathForHelpCenter])

  useEffect(() => {
    setTag(editEndpoint?.tag || '')
  }, [editEndpoint?.tag])

  const { data: baseUrl } = useGetEndpointBaseUrl(
    orgId,
    projectId,
    `${editEndpoint?.dataappp_id}`,
    `${editEndpoint?.id}`,
    {
      cluster_id: editEndpoint?.cluster_id || ''
    },
    {
      query: {
        enabled:
          !!editEndpoint &&
          editEndpoint.id !== -1 &&
          editEndpoint?.cluster_id !== '0' &&
          !['chat2data', 'chat2eda'].includes(editEndpoint?.type ?? ''),
        refetchOnWindowFocus: false,
        refetchOnMount: false
      }
    }
  )

  useEffect(() => {
    if (!editEndpointRef.current?.settings) {
      return
    }

    if (debouncedEndpoint === editEndpointRef.current?.endpoint) {
      return
    }

    saveEndpoint()
  }, [debouncedEndpoint])

  const updateBatch2Endpoint = (e: React.ChangeEvent<HTMLInputElement>) => {
    const isBatch = e.currentTarget.checked
    const limit = editEndpointRef.current?.settings?.row_limit || 100

    const endp = {
      ...editEndpoint,
      batch: isBatch,
      settings: {
        ...editEndpointRef.current?.settings,
        row_limit: isBatch ? (limit > 100 ? 100 : limit) : limit
      }
    } as EndPoint

    setEditEndpoint(endp)
    editEndpointRef.current = endp
  }

  const RequestMethodTips = () => {
    const items = [
      { method: 'GET', single: true, dataMethod: 'query', label: 'SELECT' },
      { method: 'POST', label: 'INSERT' },
      { method: 'PUT', label: 'UPDATE' },
      { method: 'DELETE', label: 'DELETE' }
    ]
    return (
      <>
        {items.map((item, index) => (
          <Box mt={4} key={index}>
            <Typography variant="body-sm" fw={600} mr={4} component="span">
              {item.method}:
            </Typography>
            <Typography variant="body-sm" component="span">
              use this method to {item.dataMethod || item.label.toLocaleLowerCase()} data, such as a
              {item.single ? '' : 'n'}
            </Typography>
            <Badge
              component="span"
              size="sm"
              radius="sm"
              styles={(theme) => ({
                root: {
                  margin: '0 4px',
                  backgroundColor: theme.colors.carbon[5],
                  color: theme.colors.carbon[8]
                }
              })}
            >
              {item.label}
            </Badge>
            <Typography variant="body-sm" component="span">
              statement.
            </Typography>
          </Box>
        ))}
      </>
    )
  }

  const settingChanged = (key: string, val?: string | number) => {
    const endp = {
      ...editEndpointRef.current,
      settings: {
        ...editEndpointRef.current?.settings,
        [key]: val
      }
    } as EndPoint
    setEditEndpoint(endp)
    editEndpointRef.current = endp
  }

  const basicProperties = (
    <Box p="0 16px 8px">
      <Box m="8px 0 4px" data-testid="endpoint-path-input" data-guide="property-path">
        <TextInput
          label={
            <Typography variant="body-sm" fw={600} mb={4} display="inline">
              Path
            </Typography>
          }
          disabled={isSystemEndpoint}
          placeholder="For example: /v1/data"
          value={path}
          maxLength={64}
          error={pathErrMsg}
          styles={() => ({
            input: {
              height: 28,
              minHeight: 28,
              fontSize: 12
            }
          })}
          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
            setIsPathErr(false)
            setIsPathConflict(false)
            const val = e.target.value.trim()
            if (val && !/^[a-zA-Z0-9\_\/{}]*$/.test(val)) {
              e.preventDefault()
            } else {
              if (!val || !isPathValid(val)) {
                setIsPathErr(true)
              } else {
                setIsPathErr(false)
              }
              const endp = {
                ...editEndpointRef.current,
                endpoint: val
              } as EndPoint

              setPath(e.target.value)
              setPathForHelpCenter(e.target.value)
              setEditEndpoint(endp)
              editEndpointRef.current = endp
            }
          }}
          onBlur={() => {
            if (
              (editEndpoint?.name === 'untitled endpoint' || editEndpoint?.name === '/untitled_endpoint') &&
              /^\/[a-zA-Z0-9\_\/{}]+$/.test(editEndpoint?.endpoint || '')
            ) {
              const val = {
                ...editEndpointRef.current,
                name: path.replaceAll('{', '').replaceAll('}', ''),
                has_modified: true
              } as EndPoint

              const list = appList.slice()
              const index = list.findIndex((item) => `${item.id}` === `${editEndpoint?.dataappp_id}`)
              if (index === -1) {
                return
              }
              const endIndex = (list[index].endpoints || []).findIndex((item) => `${item.id}` === `${editEndpoint?.id}`)
              if (endIndex === -1) {
                return
              }

              list[index].endpoints[endIndex] = val
              setEditEndpoint(val)
              editEndpointRef.current = val
              setAppList(list)
              appListRef.current = list

              // update tabs
              const openList = openedEndpList.slice()
              const oIndex = openList.findIndex((item) => `${item.id}` === `${editEndpoint?.id}`)
              if (oIndex !== -1) {
                openList[oIndex] = val
                setOpenedEndpList(openList)
              }
            }
          }}
        />
      </Box>
      <Flex align="center" justify="space-between" m="14px 0 4px">
        <Typography variant="body-sm" fw={600}>
          Endpoint URL
        </Typography>

        <Typography
          variant="action-sm"
          component="a"
          data-mp-event="DataService code example clicked"
          data-mp-source="properties"
          onClick={() => setCodeExampleOpened(true)}
        >
          Show Code Example
        </Typography>
      </Flex>

      {editEndpoint?.type === 'system-data' ? (
        <SystemEndpointCodeExample default={codeExampleOpened} onClose={() => setCodeExampleOpened(false)} />
      ) : (
        <CodeExample
          open={codeExampleOpened}
          onClose={() => setCodeExampleOpened(false)}
          endpoint={editEndpoint as EndPoint}
          source="Properties"
        />
      )}

      {!baseUrl?.data ? (
        <TextInput disabled size="sm" />
      ) : (
        <CodeBlock
          onCopyClick={() => {
            eventTracking('DataApp endpoint url copy button clicked')
          }}
          sx={{
            '& pre': {
              whiteSpace: 'normal'
            }
          }}
        >{`${baseUrl?.data || ''}${editEndpoint?.endpoint}`}</CodeBlock>
      )}

      <PropertiesLayout
        dataGuide="property-method"
        label={<Typography truncate>Request Method</Typography>}
        tooltip={<RequestMethodTips />}
        m="14px 0 4px"
      >
        <Select
          placeholder="Select"
          size="xs"
          data={['GET', 'POST', 'PUT', 'DELETE']}
          styles={() => ({
            input: {
              width: 120,
              height: 28,
              minHeight: 28,
              lineHeight: '28px'
            }
          })}
          disabled={isSystemEndpoint}
          value={editEndpoint?.method}
          onChange={(e: EndPoint['method']) => {
            const defaultValues = {
              cache_enabled: 0,
              cache_ttl: 0,
              enable_pagination: 0
            }
            const endp = {
              ...editEndpointRef.current,
              method: e,
              has_modified: true,
              settings: {
                ...editEndpointRef.current?.settings,
                ...defaultValues
              }
            } as EndPoint
            ;(e === 'GET' || e === 'DELETE') && (endp.batch = false)
            setEditEndpoint(endp)
            editEndpointRef.current = endp

            const list = appList.slice()
            const app = list.find((item) => `${item.id}` === `${editEndpoint?.dataappp_id}`)
            if (app) {
              const endp = (app.endpoints || []).find((item) => `${item.id}` === `${editEndpoint?.id}`)
              if (endp) {
                endp.method = e
                endp.has_modified = true
                endp.settings = {
                  ...endp.settings,
                  ...defaultValues
                }
              }
            }
            setAppList(list)
            appListRef.current = list

            const openList = openedEndpList.slice()
            const openEndpoint = openList.find((item) => `${item.id}` === `${editEndpoint?.id}`)
            if (openEndpoint) {
              openEndpoint.method = e
              openEndpoint.has_modified = true
              openEndpoint.settings = {
                ...openEndpoint.settings,
                ...defaultValues
              }
              setOpenedEndpList(openList)
            }
          }}
        />
      </PropertiesLayout>

      <Box>
        <Typography variant="body-sm" fw={600} m="14px 0 4px">
          Description
        </Typography>
        <Textarea
          placeholder="Input description, max 2000 characters"
          minRows={2}
          maxLength={2000}
          disabled={isSystemEndpoint}
          value={decodeURIComponent(editEndpoint?.description || '')}
          onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
            const val = {
              ...editEndpointRef.current,
              description: encodeURIComponent(e.target.value)
            } as EndPoint

            setEditEndpoint(val)
            editEndpointRef.current = val
          }}
          styles={() => ({
            input: {
              lineHeight: '20px'
            }
          })}
        />
      </Box>
    </Box>
  )
  let maxRowsProps: NumberInputProps = {
    hideControls: true,
    disabled: editEndpoint?.batch,
    styles: () => ({
      input: {
        width: 120,
        height: 28,
        minHeight: 28,
        lineHeight: '28px'
      }
    })
  }

  if (editEndpoint?.batch === false) {
    maxRowsProps = {
      ...maxRowsProps,
      ...{
        defaultValue: 50,
        min: 1,
        max: 2000,
        error:
          !editEndpointRef.current?.settings?.row_limit ||
          editEndpointRef.current?.settings?.row_limit > (editEndpointRef.current?.batch ? 100 : 2000) ||
          editEndpointRef.current?.settings?.row_limit < 0,
        value: editEndpointRef.current?.settings?.row_limit,
        onChange: (val) => settingChanged('row_limit', val),
        onBlur: () => {
          if (!editEndpointRef.current?.settings?.row_limit) {
            settingChanged('row_limit', 50)
          }
        }
      }
    }
  }

  const advancedProperties = (
    <Box p="0 16px 8px">
      <PropertiesLayout
        label={
          <>
            Timeout
            <Typography component="span" c="carbon.7">
              (ms)
            </Typography>
          </>
        }
        tooltip="The value must be in the range of 1-60000"
        m="14px 0 4px"
      >
        <NumberInput
          hideControls
          defaultValue={60000}
          min={1}
          max={60000}
          value={editEndpoint?.settings?.timeout}
          styles={() => ({
            input: {
              width: 120,
              height: 28,
              minHeight: 28,
              lineHeight: '28px'
            }
          })}
          error={
            !editEndpointRef.current?.settings?.timeout ||
            editEndpointRef.current?.settings.timeout > 60000 ||
            editEndpointRef.current?.settings.timeout < 0
          }
          onChange={(val) => settingChanged('timeout', val)}
          onBlur={() => {
            if (!editEndpointRef.current?.settings?.timeout) {
              settingChanged('timeout', 60000)
            }
          }}
        />
      </PropertiesLayout>
      <PropertiesLayout
        label="Max Rows"
        tooltip={
          editEndpoint?.batch
            ? 'Max Rows cannot be configured for batch operations.'
            : 'Max rows limits the number of rows to be returned in a single response. The value must be 1-2000.'
        }
        m="14px 0 4px"
      >
        <NumberInput {...maxRowsProps} />
      </PropertiesLayout>
      <PropertiesLayout
        label="Tag"
        tooltip="Tags are used to group endpoints. If no tag is specified, they will be grouped under a default tag."
        m="8px 0 4px"
      >
        <Input
          sx={{ width: 120 }}
          styles={() => ({
            input: {
              width: 120,
              height: 28,
              minHeight: 28,
              lineHeight: '28px'
            }
          })}
          value={DOMPurify.sanitize(tag || '')}
          disabled={isSystemEndpoint}
          onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
            const endp = {
              ...editEndpointRef.current,
              tag: e.currentTarget.value
            } as EndPoint
            setEditEndpoint(endp)
            editEndpointRef.current = endp
            setTag(e.currentTarget.value)
          }}
        />
      </PropertiesLayout>

      {editEndpoint?.method !== 'GET' && editEndpoint?.method !== 'DELETE' && (
        <PropertiesLayout label="Batch Operation" tooltip={<BatchOperTips />}>
          <Switch checked={editEndpoint?.batch} disabled={isSystemEndpoint} onChange={updateBatch2Endpoint} />
        </PropertiesLayout>
      )}

      {editEndpoint?.method === 'GET' && (
        <PropertiesLayout
          label="Pagination"
          tooltip={
            <Box>
              <Typography variant="body-sm">
                <b>Page</b>: Specify the page number to retrieve a subset of data. For example, if the page size is set
                to 10, page = 1 retrieves the first 10 rows, and page = 2 retrieves the next 10 rows. Note: When
                pagination is enabled, the page value defaults to 1.
              </Typography>
              <Typography variant="body-sm" mt={8}>
                <b>Page_Size</b>: Specify the number of rows to be returned to each page. This value can not exceed the
                maximum rows configuration. Note: When pagination is enabled, the page_size defaults to the max rows
                property configured."
              </Typography>
            </Box>
          }
        >
          <Switch
            checked={!!editEndpoint?.settings?.enable_pagination}
            onChange={(e) => settingChanged('enable_pagination', +e.currentTarget.checked)}
          />
        </PropertiesLayout>
      )}

      {editEndpoint?.method === 'GET' && (
        <PropertiesLayout label="Cache Response" tooltip="You can enable it to cache your endpoint's response">
          <Switch
            checked={!!editEndpoint?.settings?.cache_enabled}
            onChange={(e) => {
              const endp = {
                ...editEndpointRef.current,
                settings: {
                  ...editEndpointRef.current?.settings,
                  cache_enabled: +e.currentTarget.checked,
                  cache_ttl: e.currentTarget.checked ? 100 : 0
                }
              } as EndPoint
              setEditEndpoint(endp)
              editEndpointRef.current = endp
            }}
          />
        </PropertiesLayout>
      )}

      {editEndpoint?.method === 'GET' && !!editEndpoint?.settings?.cache_enabled && (
        <PropertiesLayout
          label={
            <>
              Time-to-live
              <Typography component="span" c="carbon.7">
                (s)
              </Typography>
            </>
          }
          tooltip="The value must be in the range of 30-600"
        >
          <NumberInput
            hideControls
            defaultValue={100}
            min={30}
            max={600}
            styles={() => ({
              input: {
                width: 120,
                height: 28,
                minHeight: 28,
                lineHeight: '28px'
              }
            })}
            value={editEndpointRef.current?.settings?.cache_ttl}
            onChange={(val) => settingChanged('cache_ttl', val)}
            onBlur={() => {
              if (!editEndpointRef.current?.settings?.cache_ttl) {
                settingChanged('cache_ttl', 100)
              }
            }}
          />
        </PropertiesLayout>
      )}
    </Box>
  )

  return (
    <Box
      sx={{
        width: resizing ? '100%' : rightPanel.width,
        overflowY: 'auto',
        overflowX: 'hidden',
        height: '100%'
      }}
    >
      <Box
        sx={{
          width: resizing ? '100%' : rightPanel.width
        }}
      >
        <Accordion
          mt={4}
          defaultValue="basic"
          chevronPosition="right"
          chevron={<IconChevronRightFill />}
          styles={(theme) => ({
            control: {
              padding: '8px 16px',
              backgroundColor: theme.colors.carbon[2],
              fontSize: 12,
              '&:hover': {
                backgroundColor: theme.colors.carbon[2]
              }
            },
            label: {
              paddingTop: 0,
              paddingBottom: 0
            },
            content: {
              padding: 0
            },
            chevron: {
              '&[data-rotate]': {
                transform: 'rotate(90deg)'
              }
            }
          })}
        >
          <Accordion.Item value="basic">
            <Accordion.Control>
              <Typography variant="title-sm">Basic</Typography>
            </Accordion.Control>
            <Accordion.Panel>{basicProperties}</Accordion.Panel>
          </Accordion.Item>
        </Accordion>
        <Accordion
          defaultValue=""
          chevronPosition="right"
          chevron={<IconChevronRightFill />}
          styles={(theme) => ({
            control: {
              padding: '8px 16px',
              backgroundColor: theme.colors.carbon[2],
              fontSize: 12,
              '&:hover': {
                backgroundColor: theme.colors.carbon[2]
              }
            },
            label: {
              paddingTop: 0,
              paddingBottom: 0
            },
            content: {
              padding: 0
            },
            chevron: {
              '&[data-rotate]': {
                transform: 'rotate(90deg)'
              }
            }
          })}
        >
          <Accordion.Item value="advanced">
            <Accordion.Control>
              <Typography variant="title-sm">Advanced</Typography>
            </Accordion.Control>
            <Accordion.Panel>{advancedProperties}</Accordion.Panel>
          </Accordion.Item>
        </Accordion>
        <Alert color="peacock" m={16}>
          <Typography variant="body-sm" component="span">
            Request quotas are set at 100 requests per minute (rpm) per API key, with a customizable maximum of 1000
            rpm. For an increase in quota beyond 1000 rpm, please
          </Typography>
          <Typography
            variant="action-sm"
            ml={4}
            component="span"
            fw={400}
            sx={{ cursor: 'pointer' }}
            onClick={() => {
              openSupport({
                orgId,
                cluster: currentCluster || undefined
              })
            }}
          >
            contact us
          </Typography>
          .
        </Alert>
      </Box>
    </Box>
  )
}

export default Properties
