import {
  ActionIcon,
  Alert,
  Anchor,
  Badge,
  Box,
  Button,
  Card,
  Group,
  HoverCard,
  Input,
  List,
  LoadingOverlay,
  Menu,
  Modal,
  Popover,
  ScrollArea,
  Stack,
  Typography
} from '@tidbcloud/uikit'
import { FormErrorMessage, FormRadioGroup, FormSelect, FormTextInput, Tree, TreeDataNode } from '@tidbcloud/uikit/biz'
import {
  IconAlertCircle,
  IconAlertTriangle,
  IconDatabase01,
  IconInfoCircle,
  IconRefreshCw02,
  IconTable,
  IconTrash01
} from '@tidbcloud/uikit/icons'
import { useMemoizedFn } from 'ahooks'
import { omit, pick } from 'lodash-es'
import { observer } from 'mobx-react'
import { Fragment, useMemo, useRef, useState } from 'react'
import { Controller, FormProvider, useForm } from 'react-hook-form'
import { Link, useHistory, useParams } from 'react-router-dom'

import { log } from 'common/log'
import { LOG_ERROR_SCENE } from 'common/log/logger'
import {
  AzureBlobStorageTemplateLinkForDedicatedImport,
  FileExtensions,
  getBucketURIExample
} from 'dbaas/features/Import'
import { useCloudImportName } from 'dbaas/features/Import/useCloudImportName'
import { useImportStatus } from 'dbaas/features/Import/useImportStatus'
import { validateBucketURI } from 'dbaas/features/Import/validator'
import { CloudProviderConst } from 'dbaas/features/Project'
import ImportFromCloudDedicated from 'dbaas/screens/Cluster/ImportV4/ImportFromCloudDedicated'
import { ImportFromCloud as ImportFromCloudForServerless } from 'dbaas/screens/Cluster/ImportV4/ImportFromCloudServerless'
import {
  DataFormat,
  DataSourceType,
  DefaultCSVFormat,
  getDataSourceTypeOptions,
  getFormatOptions
} from 'dbaas/screens/DataImportV2/constant'
import { Mapping, MappingFormData } from 'dbaas/screens/DataImportV2/CreateImport/Mapping'
import { Parameter } from 'dbaas/screens/DataImportV2/CreateImport/Parameter'
import CSVConfiguration from 'dbaas/screens/DataImportV2/CSVConfiguration'
import { useProgressModal } from 'dbaas/screens/DataImportV2/useProgressModal'
import {
  getS3BucketRegion,
  useCreateImport,
  useGetClusterTables,
  useSearchImportFiles,
  useValidateImport,
  validateImport
} from 'dbaas/services/dataflow'
import {
  CommonTable,
  CreateImportBody,
  DataflowCustomCSVFormat,
  DataflowImportsSearchImportFilesResp,
  DataflowImportsSearchImportFilesRespTableMatch,
  DataflowImportTargetTableInfo,
  DataflowImportValidationItem,
  ValidateImportBody
} from 'dbaas/services/dataflow/interface'
import { getErrorMessage } from 'dbaas/services/errorCodes'
import useStores from 'dbaas/stores/useStores'
import { eventTracking, MixpanelEvents } from 'dbaas/utils/tracking'

import { FormAzureSasTokenInput } from './AzureBlobAccess'
import { FormGCSServiceAccountIdReadonly } from './GCSBucketAccess'
import PrivateLinkCheck from './PrivateLinkCheck'
import { S3BucketAccess } from './S3BucketAccess'

interface FormData extends Omit<Partial<CreateImportBody>, 'data_format'> {
  source_url: string
  region: string
  data_format: string
  data_source_type: number
  conn_method?: string
  endpoint_service_name?: string
  bucketAccessMethod?: 'manual' | 'access_key'
  tempDb?: string
  tempTable?: string
  file_count_mode?: 'single' | 'multiple'
  included_schema_files?: '0' | '1'
}

type ImportTarget = 'singleTable' | 'multiTable' | 'withSchema'

// TODO: remove this backend is fixed, leave it for test in non-prod env
// Update: backend does not have plan to support single file option in near future
const showSingleFileOption = false // window.location.host !== 'tidbcloud.com'

const fileCountModeOptions = [
  {
    label: 'One file',
    value: 'single' as const,
    show: showSingleFileOption
  },
  {
    label: 'Multiple files',
    value: 'multiple' as const,
    show: true
  }
]
  .filter((i) => i.show)
  .map((i) => omit(i, 'show'))

const azureConnectionMethodOptions = [
  {
    label: 'Public',
    value: 'direct'
  },
  {
    label: 'Private Link',
    value: 'private-link'
  }
]

const CreateImport = observer(() => {
  const {
    store: { tenantData, activeProjectId, currentCluster }
  } = useStores()
  const { id } = useParams<{ id: string }>()
  const history = useHistory()
  const cluster = currentCluster!
  const isServerless = cluster?.computed?.isServerless
  const isAzure = cluster?.provider === CloudProviderConst.AZURE
  const isGCS = cluster?.provider === CloudProviderConst.GCP
  const isS3 = cluster?.provider === CloudProviderConst.AWS

  const abortController = useRef<AbortController>()
  const [modal, setModal] = useState<'none' | 'mappings' | 'warn' | 'parameter' | 'roleArn'>('none')
  const [checkedTreeNodes, setCheckedTreeNodes] = useState<{ checked: string[]; halfChecked: string[] }>({
    checked: [],
    halfChecked: []
  })

  const [loading, setLoading] = useState(false)
  const [validateError, setValidateError] = useState('')
  const [privateLinkError, setPrivateLinkError] = useState('')
  const [privateLinkOpen, setPrivateLinkOpen] = useState(false)
  const [privateLinkKey, setPrivateLinkKey] = useState(0)
  const [searchQuery, setSearchQuery] = useState('')
  const [csvFormat, setCsvFormat] = useState(() =>
    pick(DefaultCSVFormat, ['separator', 'delimiter', 'backslash_escape', 'header'])
  )
  const [view, setView] = useState<'step1' | 'step2'>('step1')

  const dataSourceTypeOptions = getDataSourceTypeOptions(cluster?.provider, isServerless)
  const defaultSourceType: number = (() => {
    return dataSourceTypeOptions.find((i) => i.value !== DataSourceType.local && !i.disabled)?.value!
  })()

  const methods = useForm<Partial<FormData>>({
    mode: 'onChange',
    defaultValues: {
      source_url: '',
      region: new URLSearchParams(window.location.search).get('region') || cluster?.region,
      data_format: DataFormat.csv + '',
      data_source_type: defaultSourceType,
      aws_role_arn: '',
      aws_access_key_id: '',
      aws_secret_access_key: '',
      azblob_sas_token: '',
      conn_method: 'direct',
      endpoint_service_name: '',

      // only for frontend
      bucketAccessMethod: 'manual',
      tempDb: '',
      tempTable: '',
      file_count_mode: fileCountModeOptions[0].value,
      included_schema_files: '0'
    }
  })

  const { control, handleSubmit, setValue, watch, getValues } = methods
  const sourceType = Number(watch('data_source_type'))
  const sourceUrl = watch('source_url')
  const dataFormat = Number(watch('data_format'))
  const connMethod = watch('conn_method')
  const endpointServiceName = watch('endpoint_service_name')
  const selectedDb = watch('tempDb')
  const selectedTable = watch('tempTable')
  const fileCountMode = watch('file_count_mode')
  const includedSchemaFiles = watch('included_schema_files') === '1'
  const isPrivateLink = isAzure && connMethod === 'private-link'
  const bucketUriName = useMemo(() => {
    return fileCountMode === 'single' ? `File URI` : `Folder URI`
  }, [fileCountMode])
  const importTarget: ImportTarget = (() => {
    if (fileCountMode === 'single') {
      return 'singleTable'
    }

    if (includedSchemaFiles) {
      return 'withSchema'
    }

    if (fileCountMode === 'multiple') {
      return 'multiTable'
    }

    return 'singleTable'
  })()
  const cloudImportName = useCloudImportName()

  const [tableMappings, setTableMappings] = useState<DataflowImportTargetTableInfo[]>([])

  const checkedTables: CommonTable[] = (() => {
    if (importTarget === 'singleTable') {
      return [{ schema: selectedDb, table: selectedTable }]
    }
    return checkedTreeNodes.checked
      .filter((i) => i.split('.').length === 2)
      .map((i) => ({
        schema: i.split('.')[0],
        table: i.split('.')[1]
      }))
  })()

  const formatOptions = getFormatOptions(cluster?.provider, sourceType!, fileCountMode!).filter((i) => !i.disabled)

  const { importTips, disableImport } = useImportStatus(tenantData.id, activeProjectId, cluster)
  const {
    data: clusterTableData,
    isFetching: isLoadingTables,
    refetch: refetchTable
  } = useGetClusterTables(tenantData.id, activeProjectId, cluster?.id, {
    query: {
      refetchOnWindowFocus: false,
      enabled: Boolean(cluster?.id)
    }
  })

  // 10-mins timeout
  const searchImportFiles = useSearchImportFiles({ request: { timeout: 10 * 60 * 1000 } })
  const createImport = useCreateImport({ request: { timeout: 10 * 60 * 1000 } })
  const validateMutation = useValidateImport({ request: { timeout: 10 * 60 * 1000 } })

  const progress = useProgressModal({
    error: validateError,
    title: `Validating`,
    onCancel: () => abortController.current?.abort()
  })

  const targetTableTreeData: TreeDataNode[] = useMemo(() => {
    return (
      clusterTableData?.schemas
        ?.filter((i) => (i.tables?.length ?? 0) > 0)
        .map((i) => ({
          key: i.schema_name!,
          title: (
            <Group gap={4} sx={{ position: 'relative', top: 4 }}>
              <Box component={IconDatabase01} size={14} c="carbon.8" />
              <Typography variant="body-md">{i.schema_name}</Typography>
            </Group>
          ),
          isLeaf: false,
          children: i.tables?.map((k) => ({
            title: (
              <Group gap={4} sx={{ position: 'relative', top: 4 }}>
                <Box component={IconTable} size={14} c="carbon.8" />
                <Typography variant="body-md">{k.table_name}</Typography>
              </Group>
            ),

            key: `${i.schema_name}.${k.table_name}`,
            isLeaf: true
          }))
        })) ?? []
    )
  }, [clusterTableData])
  const filteredTargetTableTreeData: TreeDataNode[] = useMemo(() => {
    if (!searchQuery) {
      return targetTableTreeData
    }
    const filter = (c: any) => c.key.toString().includes(searchQuery)
    return targetTableTreeData
      .filter((i) => i.children && i.children.some(filter))
      .map((i) => ({
        ...i,
        children: i.children?.filter(filter)
      }))
  }, [targetTableTreeData, searchQuery])

  const checkedTableTreeData = useMemo(() => {
    const TrashIcon = (props: any) => (
      <Box
        component={IconTrash01}
        size={14}
        c="carbon.8"
        sx={{ position: 'relative', top: 4, opacity: 0, cursor: 'pointer' }}
        {...props}
      />
    )

    const handleUncheckDbOrTable = (key: string) => {
      setCheckedTreeNodes((prev) => {
        let checked = [...prev.checked]
        let halfChecked = [...prev.halfChecked]
        const isDb = !key.includes('.')
        if (isDb) {
          checked = checked.filter((item) => item !== key && !item.startsWith(key + '.'))
          halfChecked = halfChecked.filter((item) => item !== key)
        } else {
          checked = checked.filter((i) => i !== key)

          const [db] = key.split('.')
          checked = checked.filter((i) => i !== db)
          const otherTablesInDb = checked.filter((item) => item.startsWith(db + '.'))

          if (otherTablesInDb.length > 0) {
            if (!halfChecked.includes(db)) {
              halfChecked.push(db)
            }
          } else {
            if (halfChecked.includes(db)) {
              halfChecked = halfChecked.filter((i) => i !== db)
            }
          }
        }

        return {
          checked,
          halfChecked
        }
      })
    }
    return targetTableTreeData
      .filter(
        (i) =>
          checkedTreeNodes.checked.includes(i.key as string) || checkedTreeNodes.halfChecked.includes(i.key as string)
      )
      .map((i) => ({
        ...i,
        title: (
          <Group justify="space-between">
            <Fragment>
              {i.title as any}
              <TrashIcon onClick={() => handleUncheckDbOrTable(i.key as string)} />
            </Fragment>
          </Group>
        ),
        children: i.children
          ?.filter((k) => checkedTreeNodes.checked.includes(k.key as string))
          .map((child) => ({
            ...child,
            title: (
              <Group justify="space-between">
                <Group gap={4}>
                  <Fragment>
                    {child.title as any}
                    {searchImportFiles.data?.target_tables?.find(
                      (t) => `${t.target_table?.schema}.${t.target_table?.table}` === child.key && t.warning_message
                    ) && (
                      <Box component={IconAlertTriangle} c="yellow" size={14} sx={{ position: 'relative', top: 4 }} />
                    )}
                  </Fragment>
                </Group>
                <TrashIcon onClick={() => handleUncheckDbOrTable(child.key as string)} />
              </Group>
            )
          }))
      }))
  }, [targetTableTreeData, checkedTreeNodes, searchImportFiles.data])

  const [expandKeys, setExpandKeys] = useState<string[]>([])

  const onConfirmCSVChange = useMemoizedFn(async (data: DataflowCustomCSVFormat) => {
    setCsvFormat((prev) => ({ ...prev, ...data }))
  })

  const closeModal = useMemoizedFn(() => {
    setModal('none')
  })

  const onResetMapping = useMemoizedFn(() => {
    setTableMappings([])
  })

  const onConfirmMapping = useMemoizedFn((data: MappingFormData) => {
    const mappings = data.target_table_infos?.filter((i) => i.target_table?.schema && i.target_table.table) ?? []
    const nextValue = [...mappings]
    setTableMappings(nextValue)
    closeModal()
  })

  const autoDetectRegion = useMemoizedFn(async () => {
    if (!isS3 || !sourceUrl) return
    if (validateBucketURI(sourceUrl, { sourceType: sourceType!, directory: fileCountMode !== 'single' })) {
      return
    }
    try {
      setLoading(true)
      const data = await getS3BucketRegion({ s3_url: sourceUrl })
      if (data?.region) {
        setValue('region', data.region)
      }
    } finally {
      setLoading(false)
    }
  })

  const validateConnection = useMemoizedFn(async () => {
    const data = getValues()
    await validateMutation.mutateAsync(
      {
        orgId: tenantData.id,
        projectId: activeProjectId,
        clusterId: id,
        data: {
          source_url: data.source_url?.trim()!,
          region: data.region!.trim(),
          data_source_type: data.data_source_type!,
          data_format: Number(data.data_format),
          aws_role_arn: data.aws_role_arn || undefined,
          aws_access_key_id: data.aws_access_key_id || undefined,
          aws_secret_access_key: data.aws_secret_access_key || undefined,
          azblob_sas_token: data.azblob_sas_token || undefined,
          check_items: [DataflowImportValidationItem.SOURCE_ACCESS_CHECK]
        }
      },
      {
        onError: (error) => {
          eventTracking(MixpanelEvents.DedicatedImport.TestConnect.RequestFailed, {
            error: getErrorMessage(error, true)
          })
        }
      }
    )
    setView('step2')
  })

  const testBucketURIConnection = useMemoizedFn(async () => {
    if (!(await methods.trigger())) {
      return
    }
    setPrivateLinkError('')
    if (isPrivateLink) {
      setPrivateLinkKey((prev) => prev + 1)
      setPrivateLinkOpen(true)
      return
    }
    await validateConnection()
  })

  const onCancel = useMemoizedFn(() => {
    history.push(`/clusters/${cluster.id}/imports`)
  })

  const onSubmit = useMemoizedFn(async (data: Partial<FormData>) => {
    if (!(await methods.trigger())) {
      return
    }

    try {
      const normalizedData = normalizeCreateImportBody(
        data,
        checkedTables,
        tableMappings,
        searchImportFiles.data?.target_tables!,
        importTarget
      )

      searchImportFiles.reset()
      await searchImportFilesInBucket(normalizedData)
    } finally {
      setLoading(false)
    }
  })

  const searchImportFilesInBucket = useMemoizedFn(async (data: Partial<CreateImportBody>) => {
    try {
      setValidateError('')

      eventTracking('Create Import Validate Started')
      progress.start()
      abortController.current = new AbortController()
      const res = await searchImportFiles.mutateAsync({
        orgId: tenantData.id,
        projectId: activeProjectId,
        clusterId: id,
        data: {
          target_table_infos: data.target_table_infos!,
          source_uri: data.source_url?.trim()!,
          data_format: data.data_format!,
          data_source_type: data.data_source_type!,
          aws_role_arn: data.aws_role_arn || undefined,
          aws_access_key_id: data.aws_access_key_id || undefined,
          aws_secret_access_key: data.aws_secret_access_key || undefined,
          azblob_sas_token: data.azblob_sas_token || undefined
        }
      })
      eventTracking('Create Import Validate Finished')

      if (res.warning_message) {
        setModal('warn')
      } else {
        await handleCreateImport(res)
      }
    } catch (e) {
      const msg = getErrorMessage(e, true)
      if (msg !== 'Request cancelled') {
        setValidateError(msg)
      }

      progress.pause()
      eventTracking('Create Import Validate Failed', {
        error: msg
      })
      throw e
    }
  })

  const handleCreateImport = useMemoizedFn(async (res?: DataflowImportsSearchImportFilesResp) => {
    setLoading(true)
    const formValues = getValues()
    const data: CreateImportBody = (() => {
      const body = normalizeCreateImportBody(
        formValues,
        checkedTables,
        tableMappings,
        res?.target_tables ?? searchImportFiles.data?.target_tables!,
        importTarget
      )

      if (dataFormat === DataFormat.csv) {
        body.csv_format = csvFormat
      }

      return body
    })()

    try {
      const body: ValidateImportBody = normalizeValidateBody(data, [
        importTarget !== 'withSchema'
          ? DataflowImportValidationItem.IMPORT_DATA_ONLY_CHECK
          : DataflowImportValidationItem.IMPORT_PRECHECK
      ])

      await validateImport(
        tenantData.id,
        activeProjectId,
        id,
        {
          ...body
        },
        { timeout: 10 * 60 * 1000 }
      )

      await progress.done()
      progress.close()

      const res = await createImport.mutateAsync({
        orgId: tenantData.id,
        projectId: activeProjectId,
        clusterId: id,
        data
      })
      history.push(`/clusters/${id}/imports?id=${res?.id}`)
    } catch (e) {
      progress.pause()
      const msg = getErrorMessage(e, true)
      setValidateError(msg)
      log.warning(`Create import from cloud submit error, ${msg}`, {
        tags: {
          scene: LOG_ERROR_SCENE.CoreJourney
        }
      })
    } finally {
      setLoading(false)
    }
  })

  return (
    <FormProvider {...methods}>
      <Box component="form" onSubmit={handleSubmit(onSubmit)} maw={720}>
        {importTips.length > 0 && (
          <Alert color="yellow" mb={16}>
            {importTips}
          </Alert>
        )}

        <Controller name="region" control={control} render={({ field }) => <input {...field} hidden />} />

        {view === 'step1' && (
          <Card shadow="none" withBorder p={24} mb={24} w={720}>
            <Typography variant="title-lg" mb={16}>
              Source
            </Typography>

            <FormRadioGroup
              name="file_count_mode"
              label="Import File Count"
              mb={16}
              data={fileCountModeOptions}
              onChange={() => methods.clearErrors()}
              hidden={!showSingleFileOption}
            />

            {fileCountMode === 'multiple' && (
              <FormRadioGroup
                label="Included Schema Files"
                name="included_schema_files"
                data={[
                  {
                    label: 'Yes',
                    value: '1'
                  },
                  {
                    label: 'No',
                    value: '0'
                  }
                ]}
                mb={16}
              />
            )}

            <FormRadioGroup
              name="data_format"
              label="Data Format"
              data={formatOptions.map((i) => ({
                label: i.title,
                value: i.value + ''
              }))}
              onChange={(value: DataFormat | string) => {
                eventTracking('Import Data Format Change', {
                  format: formatOptions.find((i) => i.value === Number(value))?.title
                })
              }}
              mb={16}
            />

            <Controller name="data_source_type" control={control} render={({ field }) => <input {...field} hidden />} />

            {isAzure && (
              <FormRadioGroup
                name="conn_method"
                label="Connectivity Method"
                data={azureConnectionMethodOptions}
                mb={16}
                onChange={(value: string) => {
                  if (value !== 'private-link') {
                    setValue('endpoint_service_name', '')
                    methods.clearErrors('endpoint_service_name')
                  }
                }}
              />
            )}

            {dataFormat === DataFormat.csv && (
              <Box mb={16}>
                <Popover position="bottom-start" withArrow shadow="md">
                  <Popover.Target>
                    <Button type="button" variant="default" size="xs">
                      Edit CSV Configuration
                    </Button>
                  </Popover.Target>
                  <Popover.Dropdown>
                    <CSVConfiguration defaultValue={csvFormat} onChange={onConfirmCSVChange} />
                  </Popover.Dropdown>
                </Popover>
              </Box>
            )}

            {isPrivateLink && (
              <FormTextInput
                name="endpoint_service_name"
                rules={{
                  required: 'Required',
                  pattern: {
                    value:
                      /^\/subscriptions\/[a-fA-F0-9-]+\/resourceGroups\/[a-zA-Z0-9_.-]+\/providers\/Microsoft\.Storage\/storageAccounts\/[a-zA-Z0-9-]+$/,
                    message: 'Invalid format'
                  }
                }}
                label={
                  <Group gap={8}>
                    <Typography variant="label-lg">Azure Blob Storage Resource ID</Typography>

                    <HoverCard>
                      <HoverCard.Target>
                        <IconInfoCircle />
                      </HoverCard.Target>
                      <HoverCard.Dropdown maw={460}>
                        Follow the steps below to learn how to find the Resource ID:
                        <Box component={List} px={8} py={8}>
                          <List.Item>Azure Portal → Storage Account Overview → JSON View.</List.Item>
                          <List.Item sx={{ wordBreak: 'break-all' }}>
                            {`Copy the value of the id property (format /subscriptions/<...>/resourceGroups/<...>/providers/Microsoft.Storage/storageAccounts/<account_name>).`}
                          </List.Item>
                        </Box>
                      </HoverCard.Dropdown>
                    </HoverCard>
                  </Group>
                }
                inputWrapperOrder={['label', 'input', 'description', 'error']}
                placeholder="Enter the Azure Blob Storage Resource ID"
                maw={800}
                mb={16}
              />
            )}

            <FormTextInput
              name="source_url"
              rules={{
                validate: (val) =>
                  validateBucketURI(val, {
                    sourceType: sourceType!,
                    directory: fileCountMode !== 'single',
                    format: dataFormat
                  })
              }}
              label={
                <Group gap={8}>
                  <Typography variant="label-lg">{bucketUriName}</Typography>

                  {fileCountMode === 'multiple' && (
                    <HoverCard withArrow shadow="md" position="top" width={500}>
                      <HoverCard.Target>
                        <Box component={IconInfoCircle} size={16} c="carbon.7" sx={{ cursor: 'pointer' }} />
                      </HoverCard.Target>

                      <HoverCard.Dropdown>
                        <Stack gap={8}>
                          <Typography variant="body-md">
                            Enter the {bucketUriName} in {cloudImportName} where the source files are stored. The
                            program will search for matching files in this folder. See{' '}
                            <a
                              href="https://docs.pingcap.com/tidbcloud/naming-conventions-for-data-import"
                              target="_blank"
                              rel="noreferrer"
                            >
                              matching rules
                            </a>{' '}
                            to learn how files will be matched.
                          </Typography>
                        </Stack>
                      </HoverCard.Dropdown>
                    </HoverCard>
                  )}
                </Group>
              }
              placeholder={getBucketURIExample(sourceType, fileCountMode!, dataFormat)}
              onFocus={() => {
                eventTracking('Import Bucket URL focus')
              }}
              onBlur={autoDetectRegion}
              inputWrapperOrder={['label', 'input', 'description', 'error']}
              description={
                <span>
                  {fileCountMode === 'multiple'
                    ? 'Use folder URI instead of file URI. The path should end in ‘/’.'
                    : ''}
                </span>
              }
              maw={800}
              mb={16}
            />

            {isS3 && (
              <S3BucketAccess
                authTypeFormName="bucketAccessMethod"
                roleArnFormName="aws_role_arn"
                accessKeyFormName="aws_access_key_id"
                accessKeySecretFormName="aws_secret_access_key"
                bucketUri={sourceUrl ?? ''}
                bucketUriName={bucketUriName}
                cloudformationTemplateUrl="https://tidbcloud-prod-public-cloudformation-templates.s3.us-west-2.amazonaws.com/import-role.json"
                cloudformationStackName={`TiDBCloud-Import-${Date.now()}`}
                roleArnDocLink="https://docs.pingcap.com/tidbcloud/dedicated-external-storage/#configure-amazon-s3-access-using-a-role-arn"
                accessKeyDocLink="https://docs.pingcap.com/tidbcloud/dedicated-external-storage/#configure-amazon-s3-access-using-an-aws-access-key"
              />
            )}

            {isGCS && (
              <FormGCSServiceAccountIdReadonly docUrl="https://docs.pingcap.com/tidbcloud/dedicated-external-storage#configure-gcs-access" />
            )}

            {isAzure && (
              <FormAzureSasTokenInput
                name="azblob_sas_token"
                label="SAS Token"
                rules={{ required: 'Required' }}
                templateLink={AzureBlobStorageTemplateLinkForDedicatedImport}
                docLink="https://docs.pingcap.com/tidbcloud/dedicated-external-storage#configure-azure-blob-storage-access"
              />
            )}

            {privateLinkError && <FormErrorMessage message={privateLinkError} mt={16} />}

            <FormErrorMessage message={getErrorMessage(validateMutation.error, true)} mt={16} />

            {importTarget === 'withSchema' && (
              <Alert icon={<IconInfoCircle />} title="Note" mt={16} color="peacock">
                The Folder URI you entered above should include the schema SQL files to create target tables. Otherwise,
                no data will be imported. And the schema SQL file should follow the format of{' '}
                <b>{`{db_name}.{table_name}-schema.sql`}</b>. For more detail, please refer to this{' '}
                <a
                  href="https://docs.pingcap.com/tidbcloud/naming-conventions-for-data-import#naming-conventions-for-schema-files"
                  target="_blank"
                  rel="noreferrer"
                >
                  document
                </a>
                .
              </Alert>
            )}

            <div>
              <Button
                type="button"
                loading={validateMutation.isLoading}
                disabled={!sourceUrl}
                mt={16}
                onClick={testBucketURIConnection}
                data-mp-event={MixpanelEvents.DedicatedImport.TestConnect.ButtonClicked}
              >
                Connect
              </Button>
            </div>
          </Card>
        )}

        {view === 'step2' && (
          <Card shadow="none" withBorder p={24} mb={24} w={720}>
            <Group gap={8} mb={8}>
              <Typography variant="title-lg">Source</Typography>
              <Anchor color="peacock" size="xs" p={0} onClick={() => setView('step1')}>
                Edit
              </Anchor>
            </Group>

            <Group>
              <Badge color="carbon" variant="outline" sx={{ textTransform: 'uppercase' }} c="carbon.7" fw={500}>
                {FileExtensions[dataFormat].slice(1)}
              </Badge>
              <Typography variant="body-lg">{sourceUrl}</Typography>
            </Group>
          </Card>
        )}

        {view === 'step2' && (
          <Card shadow="none" withBorder mb={24} p={24} w={720}>
            <Stack>
              <Stack gap={4}>
                <Typography variant="title-lg">Destination</Typography>
              </Stack>

              {importTarget === 'singleTable' && (
                <Stack>
                  <FormSelect
                    label="Database"
                    name="tempDb"
                    data={
                      clusterTableData?.schemas?.map((i) => ({ label: i.schema_name!, value: i.schema_name! })) ?? []
                    }
                    placeholder="Select a database"
                    rules={{ required: 'Required' }}
                  />

                  <FormSelect
                    label="Table"
                    name="tempTable"
                    data={
                      clusterTableData?.schemas
                        ?.find((i) => i.schema_name === selectedDb)
                        ?.tables?.map((i) => ({
                          label: i.table_name!,
                          value: i.table_name!
                        })) ?? []
                    }
                    placeholder="Select a table"
                    disabled={!selectedDb}
                    rules={{ required: 'Required' }}
                  />
                </Stack>
              )}

              {importTarget === 'multiTable' && (
                <Stack>
                  <Group justify="space-between" wrap="nowrap">
                    <Typography variant="body-lg">
                      In the left window, select the databases and tables you want to import. Selected items are
                      displayed on the right side.
                    </Typography>

                    <Menu>
                      <Menu.Target>
                        <Button variant="default" size="xs" data-mp-event="mp-advancedsettings-1691461015197">
                          Advanced Settings
                        </Button>
                      </Menu.Target>

                      <Menu.Dropdown>
                        <Menu.Item onClick={() => setModal('mappings')} data-mp-event="Mapping Settings Clicked">
                          Mapping Settings
                        </Menu.Item>
                      </Menu.Dropdown>
                    </Menu>
                  </Group>

                  <Group wrap="nowrap" grow align="flex-start">
                    <Card withBorder shadow="unset" h={360}>
                      <Group>
                        <ActionIcon variant="default" onClick={() => refetchTable()}>
                          <IconRefreshCw02 size={14} />
                        </ActionIcon>

                        <Input
                          placeholder="Search for table"
                          size="xs"
                          sx={{ flexGrow: 1 }}
                          onChange={(e: any) => setSearchQuery(e.target.value)}
                        />
                      </Group>

                      {clusterTableData?.schemas?.some((i) => (i.tables?.length ?? 0) > 0) ? (
                        <ScrollArea h={298}>
                          <Box py={12}>
                            <Tree
                              treeData={filteredTargetTableTreeData}
                              checkable
                              autoExpandParent
                              checkedKeys={checkedTreeNodes}
                              onCheck={(_, info) => {
                                setCheckedTreeNodes({
                                  checked: info.checkedNodes.map((i) => i.key) as string[],
                                  halfChecked: info.halfCheckedKeys as string[]
                                })

                                setExpandKeys(info.halfCheckedKeys as string[])
                              }}
                            />
                          </Box>
                        </ScrollArea>
                      ) : (
                        <Stack align="center" mt={100}>
                          <Typography variant="body-lg" ta="center" maw={270}>
                            No table in this cluster. Please create a table, then refresh this page.
                          </Typography>
                        </Stack>
                      )}

                      <LoadingOverlay
                        visible={isLoadingTables}
                        overlayProps={{ blur: 2 }}
                        loaderProps={{ color: 'carbon.6' }}
                      />
                    </Card>

                    <Card withBorder shadow="unset" h={360} p={0}>
                      <Box component="header" px={24} py={16} bg="carbon.3">
                        <Typography variant="label-lg">Tables will be imported</Typography>
                      </Box>

                      <ScrollArea h={298}>
                        <Box
                          p={16}
                          sx={(theme) => ({
                            '& .tc-uikit-tree-treenode': {
                              borderRadius: 8
                            },
                            '& .tc-uikit-tree-node-content-wrapper': {
                              flex: 1,
                              paddingRight: 16
                            },
                            '& .tc-uikit-tree-treenode:hover': {
                              backgroundColor: theme.colors.gray[1],

                              '&:hover': { svg: { opacity: 1 } }
                            }
                          })}
                        >
                          <Tree
                            treeData={checkedTableTreeData}
                            expandedKeys={expandKeys}
                            onExpand={(keys) => {
                              setExpandKeys(keys as string[])
                            }}
                          />
                        </Box>
                      </ScrollArea>
                    </Card>
                  </Group>

                  {isServerless && (
                    <Typography variant="body-md">
                      <Link
                        to={`/clusters/${currentCluster?.id}/chat2query`}
                        target="_blank"
                        data-mp-event="Click here to create table in import page"
                      >
                        Click here to create table
                      </Link>
                    </Typography>
                  )}
                </Stack>
              )}

              {importTarget === 'withSchema' && (
                <Typography variant="body-lg">Both schema and data are imported from {cloudImportName}.</Typography>
              )}
            </Stack>
          </Card>
        )}

        {view === 'step2' && (
          <Stack>
            {searchImportFiles.data?.warning_message && (
              <Alert icon={<IconAlertCircle size={14} />} color="yellow">
                <div dangerouslySetInnerHTML={{ __html: searchImportFiles.data?.warning_message }} />
              </Alert>
            )}

            <FormErrorMessage
              message={validateError || getErrorMessage(createImport.error, true)}
              sx={{ wordBreak: 'break-all' }}
            />

            <Group justify="flex-start">
              <Button
                type="submit"
                loading={loading}
                disabled={disableImport}
                data-mp-event="Create Import Submit Clicked"
              >
                Start Import
              </Button>

              <Button type="button" variant="default" data-mp-event="Create Import Cancel Clicked" onClick={onCancel}>
                Cancel
              </Button>
            </Group>
          </Stack>
        )}
      </Box>

      {progress.content}

      <PrivateLinkCheck
        key={privateLinkKey}
        opened={privateLinkOpen}
        onClose={() => {
          setPrivateLinkOpen(false)
          setPrivateLinkKey((prev) => prev + 1)
        }}
        onAvailable={validateConnection}
        resourceId={endpointServiceName?.trim() || ''}
        onError={setPrivateLinkError}
      />

      <Modal centered title="Mapping Settings" opened={modal === 'mappings'} onClose={closeModal} size={800}>
        <Mapping
          tableData={
            clusterTableData?.schemas
              ?.filter(
                (i) =>
                  checkedTreeNodes.checked.includes(i.schema_name as string) ||
                  checkedTreeNodes.halfChecked.includes(i.schema_name as string)
              )
              .map((i) => ({
                ...i,
                tables: i.tables?.filter((t) =>
                  checkedTreeNodes.checked.includes(`${t.schema_name}.${t.table_name}` as string)
                )
              })) ?? []
          }
          onCancel={closeModal}
          onReset={onResetMapping}
          onConfirm={onConfirmMapping}
          defaultValues={{ target_table_infos: tableMappings }}
          sourceType={sourceType!}
          dataFormat={dataFormat}
        />
      </Modal>

      <Modal centered title="Parameter Settings" opened={modal === 'parameter'} onClose={closeModal} size={'80%'}>
        <Parameter />
      </Modal>

      <Modal
        centered
        title="Warning"
        opened={modal === 'warn'}
        onClose={closeModal}
        size={'80%'}
        withCloseButton={false}
        closeOnClickOutside={false}
      >
        <Stack>
          {searchImportFiles.data?.warning_message && (
            <Alert icon={<IconAlertCircle size={14} />} color="yellow">
              <div dangerouslySetInnerHTML={{ __html: searchImportFiles.data?.warning_message }} />
            </Alert>
          )}

          {validateError && <FormErrorMessage message={validateError} />}

          <Group justify="flex-end">
            <Button variant="default" onClick={closeModal} data-mp-event="mp-cancel-1691461015199">
              Cancel
            </Button>

            <Button
              onClick={() => handleCreateImport()}
              loading={loading || createImport.isLoading}
              data-mp-event="Ignore Warning and Import When Create Import Clicked"
            >
              Ignore warning and Import
            </Button>
          </Group>
        </Stack>
      </Modal>
    </FormProvider>
  )
})

function normalizeCreateImportBody(
  data: Partial<FormData>,
  checkedTables: CommonTable[],
  userConfiguredTableMappings: DataflowImportTargetTableInfo[],
  serverFoundMappings: DataflowImportsSearchImportFilesRespTableMatch[],
  importTarge: ImportTarget
): CreateImportBody {
  const isAzurePrivateLink = data.data_source_type === DataSourceType.azure && data.conn_method === 'private-link'
  return {
    source_url: data.source_url?.trim(),
    region: data.region!,
    data_source_type: data.data_source_type!,
    data_format: Number(data.data_format),
    aws_role_arn: data.bucketAccessMethod === 'manual' ? data.aws_role_arn?.trim() : undefined,
    aws_access_key_id: data.bucketAccessMethod === 'access_key' ? data.aws_access_key_id?.trim() : undefined,
    aws_secret_access_key: data.bucketAccessMethod === 'access_key' ? data.aws_secret_access_key?.trim() : undefined,
    azblob_sas_token: data.azblob_sas_token?.trim() || undefined,
    conn_method: isAzurePrivateLink ? data.conn_method : undefined,
    endpoint_service_name: isAzurePrivateLink ? data.endpoint_service_name?.trim() : undefined,
    target_table_infos:
      importTarge !== 'withSchema'
        ? checkedTables.map((i) => {
            const item = userConfiguredTableMappings?.find(
              (t) => t.target_table?.schema === i.schema && t.target_table?.table === i.table
            )
            const item2 = serverFoundMappings?.find(
              (t) => t.target_table?.schema === i.schema && t.target_table?.table === i.table
            )
            return {
              target_table: i,
              custom_file: item?.custom_file ?? '',
              matched_uri: item2?.source_uri
            }
          })
        : undefined
  }
}

function normalizeValidateBody(
  data: Partial<CreateImportBody>,
  checkItems: ValidateImportBody['check_items']
): ValidateImportBody {
  return {
    source_url: data.source_url?.trim()!,
    region: data.region!,
    data_source_type: data.data_source_type!,
    data_format: Number(data.data_format!),
    aws_role_arn: data.aws_role_arn || undefined,
    aws_access_key_id: data.aws_access_key_id || undefined,
    aws_secret_access_key: data.aws_secret_access_key || undefined,
    azblob_sas_token: data.azblob_sas_token || undefined,
    target_table_infos: data.target_table_infos,
    csv_format: data.csv_format,
    check_items: checkItems
  }
}

export default function ImportFromCloud() {
  const {
    store: { currentCluster }
  } = useStores()

  if (currentCluster && currentCluster.computed.isServerless) {
    return <ImportFromCloudForServerless cluster={currentCluster} />
  }

  if (currentCluster && currentCluster.computed.isDedicated) {
    return <ImportFromCloudDedicated cluster={currentCluster} />
  }

  return <CreateImport />
}
