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 { useMount } from 'ahooks'
import { useMemo, useState } from 'react'
import { useFormContext } from 'react-hook-form'
import { useHistory } from 'react-router-dom'

import { SharedCluster } from 'dbaas/features/ClusterManagement/cluster'
import { useOrgProjectClusterIds } from 'dbaas/features/Common'
import { DataFormat, DataFormatDisplayName } from 'dbaas/features/Import'
import { LightningFileNamingExample } from 'dbaas/features/Import/LightningFileNamingExample'
import CSVConfiguration from 'dbaas/screens/DataImportV2/CSVConfiguration'
import {
  useImportServiceCreateImport,
  useImportServicePrecheck,
  useImportServiceValidateImport
} from 'dbaas/services/api-clients/legacy/serverless'
import { CSVFormat, ImportFileTypeEnum, TableResult } from 'dbaas/services/api-clients/legacy/serverless/interface'
import { getErrorMessage } from 'dbaas/services/errorCodes'

import { ExpandableList } from './ExpandableList'
import { normalizeImportOptions, normalizeImportSource } from './helper'
import { ImportFromCloudFormData } from './types'

export const PreCheck = ({
  onCancel,
  onGoPrev,
  cluster
}: {
  onCancel: () => void
  onGoPrev: () => void
  cluster: SharedCluster
}) => {
  const methods = useFormContext<ImportFromCloudFormData>()
  const history = useHistory()
  const precheck = useImportServicePrecheck({ request: { timeout: 10 * 60 * 1000 } })
  const validate = useImportServiceValidateImport({ request: { timeout: 10 * 60 * 1000 } })
  const createImport = useImportServiceCreateImport({ request: { timeout: 10 * 60 * 1000 } })
  const { orgId, projectId } = useOrgProjectClusterIds()
  const sourceUri = methods.watch('source_uri')
  const dataFormat = methods.watch('data_format')
  const followNamingConvention = methods.watch('use_tidb_dumpling_naming_conventions')
  const customMappings = methods.watch('target_table_infos')
  const csvFormat = methods.watch('csv_format')

  const onConfirmCSVChange = (csvFormat: CSVFormat) => {
    methods.setValue('csv_format', csvFormat)
  }

  const onSubmit = async (data: ImportFromCloudFormData) => {
    const result = await createImport.mutateAsync({
      orgId,
      projectId,
      clusterId: cluster.id,
      data: {
        import_options: normalizeImportOptions(data),
        source: normalizeImportSource(data)!
      }
    })

    history.push(`/clusters/${cluster.id}/imports?id=${result?.id}`)
  }

  useMount(() => {
    const data = methods.getValues()
    precheck.mutate({
      orgId,
      projectId,
      clusterId: cluster.id,
      data: {
        import_options: normalizeImportOptions(data),
        source: normalizeImportSource(data)!
      }
    })
  })

  const totalDataFilesCount = Number(precheck.data?.total_data_files_count ?? '0')
  const totalTablesCount = Number(precheck.data?.total_tables_count ?? '0')

  // Categorize different precheck scenarios
  const { newTablesWithSchema, existingTablesWithSchema, tablesWithoutSchema } = useMemo(() => {
    const tables = precheck.data?.target_tables ?? []

    // When not following naming convention, all tables are expected to not have schema files
    // and should import to existing tables (which is the intended behavior)
    if (!followNamingConvention) {
      return {
        newTablesWithSchema: [],
        existingTablesWithSchema: [],
        tablesWithoutSchema: [] // Don't categorize as "missing schema" since it's expected
      }
    }

    // Case 1: Tables with matching schema files that will use the schema (new tables will be created)
    const newTablesWithSchema = tables.filter((i) => i.matched_schema_file && i.use_schema_file)

    // Case 2: Tables with matching schema files but won't use the schema (target table exists, import to existing table)
    const existingTablesWithSchema = tables.filter((i) => i.matched_schema_file && !i.use_schema_file)

    // Case 3: Tables without matching schema files (target table must exist)
    const tablesWithoutSchema = tables.filter((i) => !i.matched_schema_file)

    return {
      newTablesWithSchema,
      existingTablesWithSchema,
      tablesWithoutSchema
    }
  }, [precheck.data, followNamingConvention])

  const [shouldShowDataFormatSelection, setShouldShowDataFormatSelection] = useState(false)

  // Sort target tables: error_message first, then alert cases, then normal cases
  const sortedTargetTables = useMemo(() => {
    const tables = precheck.data?.target_tables ?? []

    return [...tables].sort((a, b) => {
      // Priority 1: Items with error_message come first
      if (a.error_message && !b.error_message) return -1
      if (!a.error_message && b.error_message) return 1

      // When not following naming convention, schema-related sorting is not relevant
      if (!followNamingConvention) {
        return 0 // Keep original order
      }

      // Priority 2: Items without schema files (only when following naming convention)
      if (!a.matched_schema_file && b.matched_schema_file) return -1
      if (a.matched_schema_file && !b.matched_schema_file) return 1

      // Priority 3: Items with schema files but won't use them (only when following naming convention)
      if (a.matched_schema_file && !a.use_schema_file && b.matched_schema_file && b.use_schema_file) return -1
      if (a.matched_schema_file && a.use_schema_file && b.matched_schema_file && !b.use_schema_file) return 1

      // Keep original order for items with same priority
      return 0
    })
  }, [precheck.data?.target_tables, followNamingConvention])

  const disabled = useMemo(() => {
    const hasPrecheckError =
      !!precheck.data?.error_message || precheck.data?.target_tables?.some((i) => !!i.error_message)

    const emptyPrecheckResults = totalDataFilesCount === 0 || totalTablesCount === 0

    return hasPrecheckError || (emptyPrecheckResults && precheck.data?.status === 'COMPLETED')
  }, [precheck.data, totalDataFilesCount, totalTablesCount])

  useMount(() => {
    const dataFormat = methods.getValues('data_format')
    if (dataFormat === 'UNKNOWN') {
      setShouldShowDataFormatSelection(true)
    }
  })

  return (
    <Card p={24}>
      <Stack component="form" onSubmit={methods.handleSubmit(onSubmit)}>
        <Typography variant="title-lg">Pre-check</Typography>

        <Stack>
          <Stack gap={4}>
            <Typography variant="body-lg">
              Source URI is <Code>{sourceUri}</Code>.
            </Typography>
            <Typography variant="body-lg">
              {followNamingConvention ? (
                <>
                  All files named after <LightningFileNamingExample dataFormat={dataFormat as ImportFileTypeEnum} />{' '}
                  will be imported.
                </>
              ) : (
                <>
                  <b>{customMappings.length}</b> custom mapped file{customMappings.length === 1 ? '' : 's'} or pattern
                  {customMappings.length === 1 ? '' : 's'} will be imported to their corresponding target tables.
                </>
              )}
            </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} color="red" 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 }} />
                  ) : (
                    <IconXCircle size={18} c="red.7" sx={{ flexShrink: 0 }} />
                  )}
                  <Stack gap={4}>
                    <Typography variant="label-md" c="carbon.8">
                      Scan completed
                    </Typography>
                    <Typography variant="body-md">
                      Found <b>{totalDataFilesCount}</b> data file{totalDataFilesCount === 1 ? '' : 's'} to import into{' '}
                      <b>{totalTablesCount}</b> table{totalTablesCount === 1 ? '' : 's'}.
                      {totalDataFilesCount === 0
                        ? followNamingConvention
                          ? ' Make sure you have selected the correct data format.'
                          : ' Make sure you have entered the correct file pattern.'
                        : ''}
                    </Typography>

                    {followNamingConvention && (
                      // When following naming convention, show detailed categorization
                      <>
                        {/* Case 1: Create new tables using schema files */}
                        {newTablesWithSchema.length > 0 && (
                          <Typography variant="body-md">
                            <b>{newTablesWithSchema.length}</b> new table
                            {newTablesWithSchema.length === 1 ? '' : 's'} will be created based on schema files found in
                            your source.
                          </Typography>
                        )}

                        {/* Case 2: Import to existing tables with matching schema files */}
                        {existingTablesWithSchema.length > 0 && (
                          <Typography variant="body-md">
                            <b>{existingTablesWithSchema.length}</b> table
                            {existingTablesWithSchema.length === 1 ? '' : 's'} with matching schema files. The schema
                            files in your source will be ignored because tables with the same names already exist in the
                            database.
                          </Typography>
                        )}

                        {/* Case 3: Import to existing tables without schema files */}
                        {tablesWithoutSchema.length > 0 && (
                          <Typography variant="body-md">
                            <b>{tablesWithoutSchema.length}</b> table
                            {tablesWithoutSchema.length === 1 ? '' : 's'} without schema files. No matching schema files
                            were found in your source.
                          </Typography>
                        )}
                      </>
                    )}
                  </Stack>
                </>
              )}
            </Group>
          </Card>

          {/* Alert for different scenarios */}
          {!followNamingConvention && 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>
          )}

          {followNamingConvention && (existingTablesWithSchema.length > 0 || tablesWithoutSchema.length > 0) && (
            <Alert color="peacock" icon={<IconInfoCircle />}>
              {existingTablesWithSchema.length > 0 && tablesWithoutSchema.length === 0 && (
                <>
                  Schema files found in your source will be ignored because tables with the same names already exist.
                  Please make sure all target tables are empty.
                </>
              )}
              {tablesWithoutSchema.length > 0 && existingTablesWithSchema.length === 0 && (
                <>No schema files found for some tables. Please make sure all target tables are created and empty.</>
              )}
              {existingTablesWithSchema.length > 0 && tablesWithoutSchema.length > 0 && (
                <>
                  Some tables already exist (schema files will be ignored) and some tables have no schema files. Please
                  make sure all target tables are created and empty.
                </>
              )}
            </Alert>
          )}

          {precheck.data?.status === 'TRUNCATED' && (
            <Alert color="peacock" icon={<IconInfoCircle />}>
              We've scanned only the first 1000 files in your source. You may still proceed with the import, but please
              ensure all files follow the required naming convention and format to avoid potential issues during the
              import process.
            </Alert>
          )}

          {precheck.data?.status === 'TIMEOUT' && (
            <Alert color="peacock" icon={<IconInfoCircle />}>
              The scan timed out and may have only captured partial results from your source. You may still proceed with
              the import, but please ensure all files follow the required naming convention and format to avoid
              potential issues during the import process.
            </Alert>
          )}
        </Stack>

        {precheck.isSuccess && Number(totalDataFilesCount) > 0 && (
          <>
            <Typography variant="title-lg">Result</Typography>

            {shouldShowDataFormatSelection && (
              <>
                <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={[
                    {
                      label: DataFormatDisplayName[DataFormat.csv],
                      value: ImportFileTypeEnum.CSV
                    },
                    {
                      label: DataFormatDisplayName[DataFormat.parquet],
                      value: ImportFileTypeEnum.PARQUET
                    },
                    {
                      label: DataFormatDisplayName[DataFormat.sql_file],
                      value: ImportFileTypeEnum.SQL
                    }
                  ]}
                />
                {dataFormat === ImportFileTypeEnum.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>
                )}
              </>
            )}

            <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((i) => (
                  <PrecheckResultItem
                    item={i}
                    followNamingConvention={followNamingConvention}
                    key={i.target_table?.database! + i.target_table?.table!}
                  />
                ))}
              </Card.Section>
            </Card>
          </>
        )}

        <Divider mx={-24} />

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

        <Group justify="flex-end">
          <Button variant="default" onClick={onCancel}>
            Cancel
          </Button>
          <Button variant="default" onClick={onGoPrev}>
            Previous
          </Button>
          <Button type="submit" disabled={disabled} loading={createImport.isLoading || validate.isLoading}>
            Start Import
          </Button>
        </Group>
      </Stack>
    </Card>
  )
}

function PrecheckResultItem({ item, followNamingConvention }: { item: TableResult; followNamingConvention: 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?.database}.{item.target_table?.table}
          </span>

          {/* When table exists and is not empty or both missing schema and table */}
          {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>
          )}

          {/* Only show schema-related warnings when following naming convention */}
          {followNamingConvention && (
            <>
              {/* Case 1: New table created using schema file, no need for warning */}
              {/* Case 3: Schema file not found and table exists, no need for warning; if table does not exist, error_message will be displayed */}
              {/* Case 2: Schema file found but won't be used - existing table takes priority */}
              {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>
              )}
            </>
          )}
        </Typography>
      </Group>
    </Box>
  )
}
