import {
  Alert,
  Box,
  Button,
  Card,
  Code,
  Divider,
  Group,
  HoverCard,
  Loader,
  Popover,
  Stack,
  Typography
} from '@tidbcloud/uikit'
import { FormErrorMessage, FormRadioGroup } from '@tidbcloud/uikit/biz'
import { IconAlertCircle, IconCheckCircle, IconInfoCircle, IconXCircle } from '@tidbcloud/uikit/icons'
import { useUpdateEffect } from 'ahooks'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useFormContext } from 'react-hook-form'
import { useHistory } from 'react-router-dom'

import { log } from 'common/log'
import { LOG_ERROR_SCENE } from 'common/log/logger'
import { SharedCluster } from 'dbaas/features/ClusterManagement/cluster'
import { useGetCurrentClusterDataIngestionProfile } from 'dbaas/features/Common'
import { useImportStatus } from 'dbaas/features/Import/useImportStatus'
import { DataFormat, DataSourceType, getFormatOptions } from 'dbaas/screens/DataImportV2/constant'
import CSVConfiguration from 'dbaas/screens/DataImportV2/CSVConfiguration'
import { useProgressModal } from 'dbaas/screens/DataImportV2/useProgressModal'
import { useCreateImport, usePrecheckImport, validateImport } from 'dbaas/services/dataflow'
import { AgentTableResult, DataflowImportValidationItem } from 'dbaas/services/dataflow/interface'
import { getErrorMessage } from 'dbaas/services/errorCodes'
import useStores from 'dbaas/stores/useStores'

import { ExpandableList } from '../ImportFromCloudServerless/ExpandableList'

import {
  canStartImportWithPrecheckDataFiles,
  isPrecheckDataFileCountInconclusive,
  isPrecheckResultIncomplete,
  normalizeDedicatedCreateImportBody,
  normalizeDedicatedImportOptions,
  normalizeDedicatedImportSource,
  normalizeDedicatedValidateBody
} from './helper'
import type { ImportFromCloudDedicatedFormData } from './types'

const PRECHECK_SCAN_FILE_LIMIT = 1000
const INCOMPLETE_PRECHECK_IMPORT_GUIDANCE =
  'You may still proceed with the import, but you are responsible for ensuring that all files follow the required naming convention and format. Files that do not comply may cause the import to fail.'

export const Precheck = ({
  cluster,
  onCancel,
  onGoPrev
}: {
  cluster: SharedCluster
  onCancel: () => void
  onGoPrev: () => void
}) => {
  const {
    store: { tenantData, activeProjectId }
  } = useStores()
  const methods = useFormContext<ImportFromCloudDedicatedFormData>()
  const history = useHistory()
  const ingestionProfile = useGetCurrentClusterDataIngestionProfile()
  const precheck = usePrecheckImport({ request: { timeout: 10 * 60 * 1000 } })
  const createImport = useCreateImport({ request: { timeout: 10 * 60 * 1000 } })
  const sourceUrl = methods.watch('source_url')
  const sourceType = methods.watch('data_source_type')
  const isSingleFile = methods.watch('is_single_file')
  const followNamingConventions = methods.watch('use_tidb_dumpling_naming_conventions')
  const dataFormat = methods.watch('data_format')
  const csvFormat = methods.watch('csv_format')
  const targetTableInfos = methods.watch('target_table_infos')
  const { disableImport, importTips } = useImportStatus(tenantData.id, activeProjectId, cluster)
  const [submitError, setSubmitError] = useState('')
  const precheckRequestedRef = useRef(false)

  const progress = useProgressModal({
    error: submitError,
    title: 'Validating',
    onCancel: () => undefined
  })

  useUpdateEffect(() => {
    setSubmitError('')
  }, [dataFormat])

  useEffect(() => {
    const currentValues = methods.getValues()
    const needsAwsExternalId =
      currentValues.data_source_type === DataSourceType.aws && currentValues.bucketAccessMethod === 'manual'

    if (precheckRequestedRef.current) {
      return
    }
    if (needsAwsExternalId && ingestionProfile.isLoading) {
      return
    }

    const source = normalizeDedicatedImportSource(currentValues, {
      awsExternalId: ingestionProfile.data?.awsExternalId
    })
    if (!source) {
      setSubmitError('This storage provider is not supported by the dedicated pre-check flow yet.')
      return
    }

    precheckRequestedRef.current = true
    precheck.mutate({
      orgId: tenantData.id,
      projectId: activeProjectId,
      clusterId: cluster.id,
      data: {
        import_options: normalizeDedicatedImportOptions(currentValues),
        source
      }
    })
  }, [
    activeProjectId,
    cluster.id,
    ingestionProfile.data?.awsExternalId,
    ingestionProfile.isLoading,
    methods,
    precheck,
    tenantData.id
  ])

  const totalDataFilesCount = Number(precheck.data?.total_data_files_count ?? '0')
  const totalTablesCount = Number(precheck.data?.total_tables_count ?? '0')
  const formatOptions = getFormatOptions(cluster.provider, sourceType, isSingleFile ? 'single' : 'multiple').filter(
    (item) => !item.disabled
  )

  const sortedTargetTables = useMemo(() => {
    return [...(precheck.data?.target_tables ?? [])].sort((left, right) => {
      if (left.error_message && !right.error_message) {
        return -1
      }
      if (!left.error_message && right.error_message) {
        return 1
      }
      return 0
    })
  }, [precheck.data?.target_tables])

  const hasBlockingPrecheckError =
    !!precheck.data?.error_message || (precheck.data?.target_tables ?? []).some((item) => !!item.error_message)
  const isScanTimedOut = precheck.data?.status === 'TIMEOUT'
  const isPrecheckIncomplete = isPrecheckResultIncomplete(precheck.data?.is_truncated, precheck.data?.status)
  const isDataFileCountInconclusive = isPrecheckDataFileCountInconclusive(totalDataFilesCount, isPrecheckIncomplete)
  const canStartImportForDataFiles = canStartImportWithPrecheckDataFiles(totalDataFilesCount, isPrecheckIncomplete)
  const needsDataFormatSelection = dataFormat === 'UNKNOWN'

  const onConfirmCSVChange = (nextCsvFormat: ImportFromCloudDedicatedFormData['csv_format']) => {
    methods.setValue('csv_format', nextCsvFormat)
  }

  const onSubmit = async (data: ImportFromCloudDedicatedFormData) => {
    try {
      setSubmitError('')
      progress.start()

      const createBody = normalizeDedicatedCreateImportBody(data)
      const checkItems = [DataflowImportValidationItem.IMPORT_PRECHECK]

      await validateImport(
        tenantData.id,
        activeProjectId,
        cluster.id,
        normalizeDedicatedValidateBody(createBody, checkItems),
        {
          timeout: 10 * 60 * 1000
        }
      )

      await progress.done()
      progress.close()

      const result = await createImport.mutateAsync({
        orgId: tenantData.id,
        projectId: activeProjectId,
        clusterId: cluster.id,
        data: createBody
      })

      history.push(`/clusters/${cluster.id}/imports?id=${result?.id}`)
    } catch (error) {
      progress.pause()
      const message = getErrorMessage(error, true)
      setSubmitError(message)
      log.warning(`Create import from cloud submit error, ${message}`, {
        tags: {
          scene: LOG_ERROR_SCENE.CoreJourney
        }
      })
    }
  }

  return (
    <>
      <Card p={24} data-testid="import-from-cloud-dedicated-step3">
        <Stack component="form" onSubmit={methods.handleSubmit(onSubmit)}>
          <Typography variant="title-lg">Pre-check</Typography>

          {importTips && (
            <Alert color="yellow" icon={<IconAlertCircle />}>
              {importTips}
            </Alert>
          )}

          <Stack gap={4}>
            <Typography variant="body-lg">
              Source URI is <Code>{sourceUrl}</Code>.
            </Typography>
            <Typography variant="body-lg">
              {followNamingConventions && !isSingleFile ? (
                <>Automatic mapping is enabled and files will be resolved by naming conventions.</>
              ) : (
                <>
                  <b>{targetTableInfos.length}</b> custom mapped file{targetTableInfos.length === 1 ? '' : 's'} or
                  pattern{targetTableInfos.length === 1 ? '' : 's'} will be imported.
                </>
              )}
            </Typography>
          </Stack>

          <Card withBorder={false} bg="carbon.2">
            <Group gap={8} wrap="nowrap" align="flex-start">
              {precheck.isLoading && (
                <>
                  <Loader size="xs" />
                  <Typography c="carbon.8" variant="label-md">
                    We are scanning files in your source, please wait...
                  </Typography>
                </>
              )}
              {precheck.isError && (
                <>
                  <IconXCircle size={18} c="red.7" sx={{ flexShrink: 0 }} />
                  <Typography c="carbon.8" variant="label-md">
                    Failed to scan files,{' '}
                    <span dangerouslySetInnerHTML={{ __html: getErrorMessage(precheck.error, true) }} />.
                  </Typography>
                </>
              )}
              {precheck.isSuccess && (
                <>
                  {totalDataFilesCount > 0 ? (
                    <IconCheckCircle size={18} c="green.7" sx={{ flexShrink: 0 }} />
                  ) : isDataFileCountInconclusive ? (
                    <IconInfoCircle size={18} c="peacock.7" sx={{ flexShrink: 0 }} />
                  ) : (
                    <IconXCircle size={18} c="red.7" sx={{ flexShrink: 0 }} />
                  )}
                  <Stack gap={4}>
                    <Typography variant="label-md" c="carbon.8">
                      {isPrecheckIncomplete ? 'Scan incomplete' : 'Scan completed'}
                    </Typography>
                    <Typography variant="body-md">
                      {isDataFileCountInconclusive ? (
                        isScanTimedOut ? (
                          <>No data files were found before the scan timed out. {INCOMPLETE_PRECHECK_IMPORT_GUIDANCE}</>
                        ) : (
                          <>
                            Only the first {PRECHECK_SCAN_FILE_LIMIT} files were scanned, and no data files were found
                            among them. {INCOMPLETE_PRECHECK_IMPORT_GUIDANCE}
                          </>
                        )
                      ) : (
                        <>
                          Found <b>{totalDataFilesCount}</b> data file{totalDataFilesCount === 1 ? '' : 's'} to import
                          into <b>{totalTablesCount}</b> table{totalTablesCount === 1 ? '' : 's'}.
                        </>
                      )}
                    </Typography>
                  </Stack>
                </>
              )}
            </Group>
          </Card>

          {precheck.data?.error_message && (
            <Alert color="red" icon={<IconAlertCircle />}>
              {precheck.data.error_message}
            </Alert>
          )}

          {!followNamingConventions && totalTablesCount > 0 && (
            <Alert color="peacock" icon={<IconInfoCircle />}>
              Data will be imported into existing tables based on your custom mappings. Please make sure all target
              tables exist in the database and are empty.
            </Alert>
          )}

          {isPrecheckIncomplete && !isDataFileCountInconclusive && (
            <Alert color="peacock" icon={<IconInfoCircle />}>
              {isScanTimedOut
                ? `The scan timed out and may have captured only partial results from your source. ${INCOMPLETE_PRECHECK_IMPORT_GUIDANCE}`
                : `We've scanned only the first ${PRECHECK_SCAN_FILE_LIMIT} files in your source. ${INCOMPLETE_PRECHECK_IMPORT_GUIDANCE}`}
            </Alert>
          )}

          {needsDataFormatSelection && (
            <>
              <Divider mx={-24} />

              <FormRadioGroup
                label="Data Format"
                name="data_format"
                rules={{ required: 'Required' }}
                description={
                  <Typography variant="body-md" c="carbon.6">
                    Please select the data format to help us import your data correctly.
                  </Typography>
                }
                data={formatOptions.map((item) => ({
                  label: item.title,
                  value: item.value
                }))}
              />

              {Number(dataFormat) === DataFormat.csv && (
                <Box>
                  <Popover withArrow withinPortal 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>
              )}
            </>
          )}

          {precheck.isSuccess && sortedTargetTables.length > 0 && (
            <>
              <Divider mx={-24} />

              <Typography variant="title-lg">Result</Typography>

              <Card>
                <Card.Section bg="carbon.2" p={16}>
                  <Group justify="space-evenly">
                    <Typography sx={{ flex: 1 }}>Source File</Typography>
                    <Typography sx={{ flex: 1 }}>Target Table</Typography>
                  </Group>
                </Card.Section>
                <Card.Section mah={600} sx={{ overflowY: 'auto' }}>
                  {sortedTargetTables.map((item) => (
                    <PrecheckResultItem
                      item={item}
                      followNamingConventions={followNamingConventions}
                      isSingleFile={isSingleFile}
                      key={`${item.target_table?.schema || '--'}.${item.target_table?.table || '--'}`}
                    />
                  ))}
                </Card.Section>
              </Card>
            </>
          )}

          <Divider mx={-24} />

          <FormErrorMessage message={submitError || getErrorMessage(createImport.error, true)} />

          <Group justify="flex-end">
            <Button variant="default" onClick={onCancel}>
              Cancel
            </Button>
            <Button variant="default" onClick={onGoPrev}>
              Previous
            </Button>
            <Button
              type="submit"
              loading={createImport.isLoading}
              disabled={
                disableImport ||
                !precheck.isSuccess ||
                !canStartImportForDataFiles ||
                hasBlockingPrecheckError ||
                needsDataFormatSelection ||
                !!submitError
              }
            >
              Start Import
            </Button>
          </Group>
        </Stack>
      </Card>

      {progress.content}
    </>
  )
}

function PrecheckResultItem({
  item,
  followNamingConventions,
  isSingleFile
}: {
  item: AgentTableResult
  followNamingConventions: boolean
  isSingleFile: boolean
}) {
  return (
    <Box px={16} py={12} bg={item.error_message ? 'red.1' : 'carbon.0'}>
      <Group justify="space-evenly">
        <Typography sx={{ flex: 1 }}>
          <ExpandableList items={item.matched_data_files || ['No matched data files']} />
        </Typography>

        <Typography sx={{ flex: 1, display: 'flex', alignItems: 'center', gap: 8 }}>
          <span>
            {item.target_table?.schema || '--'}.{item.target_table?.table || '--'}
          </span>

          {item.error_message && (
            <HoverCard>
              <HoverCard.Target>
                <IconXCircle size={18} c="red.7" />
              </HoverCard.Target>
              <HoverCard.Dropdown maw={400}>
                <Typography>{item.error_message}</Typography>
              </HoverCard.Dropdown>
            </HoverCard>
          )}

          {item.matched_schema_file && !item.use_schema_file && (
            <HoverCard>
              <HoverCard.Target>
                <IconAlertCircle size={18} c="yellow.7" />
              </HoverCard.Target>
              <HoverCard.Dropdown maw={400}>
                <Typography>
                  Schema file found but will be ignored because a table with the same name already exists in the
                  database.
                </Typography>
              </HoverCard.Dropdown>
            </HoverCard>
          )}

          {!item.matched_schema_file && followNamingConventions && !isSingleFile && !item.error_message && (
            <HoverCard>
              <HoverCard.Target>
                <IconAlertCircle size={18} c="yellow.7" />
              </HoverCard.Target>
              <HoverCard.Dropdown maw={400}>
                <Typography>
                  No schema file was found for this table. Make sure the target table already exists and is empty.
                </Typography>
              </HoverCard.Dropdown>
            </HoverCard>
          )}
        </Typography>
      </Group>
    </Box>
  )
}
