import {
  ActionIcon,
  Box,
  Button,
  Card,
  Divider,
  Group,
  HoverCard,
  Loader,
  SegmentedControl,
  Stack,
  Typography
} from '@tidbcloud/uikit'
import { FormNumberInput, FormRadioGroup, FormSelect, FormTextInput, TransferTree } from '@tidbcloud/uikit/biz'
import { IconDatabase01, IconInfoCircle, IconLayoutGrid02, IconRefreshCw01 } from '@tidbcloud/uikit/icons'
import { capitalize } from 'lodash-es'
import { observer } from 'mobx-react'
import { useMemo, useState } from 'react'
import { useFormContext } from 'react-hook-form'
import { useQuery } from 'react-query'

import { FormErrorMessage } from 'dbaas/components/Form'
import type { PremiumDMFormData } from 'dbaas/screens/DataMigration/CreateDataMigrationPremium.types'
import { buildPremiumConnProfile } from 'dbaas/screens/DataMigration/steps/Step1/helper'
import type { PremiumStep1FormData } from 'dbaas/screens/DataMigration/steps/Step1/PremiumDMStep1'
import { flatTree2Keys } from 'dbaas/screens/DataMigration/steps/Step2/utils'
import { useSharedTiDBContext } from 'dbaas/screens/NextGen/tidb-mgmt-container/shared/tidb-context'
import { getPremiumMigrationSourceTables, useCreatePremiumMigrationPrecheck } from 'dbaas/services/dataflow'
import {
  PremiumDMTaskImportModeENUM,
  V1beta2PremiumDMSource,
  V1beta2PremiumDMSourceType,
  V1beta2PremiumDMTaskMode
} from 'dbaas/services/dataflow/interface'
import { getErrorMessage } from 'dbaas/services/errorCodes'

export interface PremiumStep2FormData {
  full_instance_migrate: boolean
  migration_mode: V1beta2PremiumDMTaskMode
  sources: V1beta2PremiumDMSource[]
  precheckId: string
  selectObjectMethod: 'all' | 'customize'
  selectedObjectKeys: string[]
  start_position_method: 'gtid' | 'binlog' | 'current'
  binlog_name?: string
  binlog_position?: number
  binlog_gtid?: string
  import_mode: PremiumDMTaskImportModeENUM
}

const PremiumDMTypeOptions = [
  { value: V1beta2PremiumDMTaskMode.ALL, label: 'Full + Incremental' },
  { value: V1beta2PremiumDMTaskMode.INCREMENTAL, label: 'Incremental only' }
]

const Step2 = ({
  onCancel,
  onFinish,
  onGoPrev
}: {
  onFinish: () => void
  onGoPrev: () => void
  onCancel: () => void
}) => {
  const tidb = useSharedTiDBContext()
  const { handleSubmit, getValues, setValue, watch } = useFormContext<PremiumDMFormData>()
  const formData = getValues() as PremiumStep1FormData & PremiumStep2FormData
  const migrationMode = watch('migration_mode')
  const isFull = migrationMode === V1beta2PremiumDMTaskMode.ALL
  const isIncremental = migrationMode === V1beta2PremiumDMTaskMode.INCREMENTAL
  const startPositionMethod = watch('start_position_method')
  const selectObjectMethod = watch('selectObjectMethod') ?? 'all'
  const setSelectObjectMethod = (value: PremiumStep2FormData['selectObjectMethod']) => {
    setValue('selectObjectMethod', value)
  }
  const selectedObjectKeys = watch('selectedObjectKeys') ?? []
  const setSelectedObjectKeys = (keys: string[]) => {
    setValue('selectedObjectKeys', keys)
  }

  const [errorMessage, setErrorMessage] = useState('')
  const precheck = useCreatePremiumMigrationPrecheck()

  const {
    data: sourceTables,
    isFetching: isLoadingTables,
    error: tablesError,
    refetch
  } = useQuery(
    '/source-tables',
    () =>
      getPremiumMigrationSourceTables(
        tidb.tidbId!,
        { conn_profile: buildPremiumConnProfile(getValues()) },
        { timeout: 60 * 1000 }
      ),
    {
      enabled: Boolean(formData.connect_method),
      refetchOnWindowFocus: false,
      refetchOnReconnect: false,
      select(data) {
        return data.tables
      }
    }
  )

  const schemaGroups = useMemo(() => {
    if (!sourceTables?.length) {
      return [] as Array<{
        schema_name: string
        tables: Array<{ schema_name: string; table_name: string }>
      }>
    }

    const grouped = new Map<string, Set<string>>()
    sourceTables.forEach(({ schema, table }) => {
      if (!schema) return
      const tableSet = grouped.get(schema) ?? new Set<string>()
      if (table) {
        tableSet.add(table)
      }
      grouped.set(schema, tableSet)
    })

    return Array.from(grouped.entries())
      .map(([schemaName, tables]) => ({
        schema_name: schemaName,
        tables: Array.from(tables)
          .sort((a, b) => a.localeCompare(b))
          .map((tableName) => ({ schema_name: schemaName, table_name: tableName }))
      }))
      .sort((a, b) => a.schema_name.localeCompare(b.schema_name))
  }, [sourceTables])

  const dataSource = useMemo(() => {
    return schemaGroups.map((schema) => ({
      title: schema.schema_name,
      key: schema.schema_name,
      icon: <IconDatabase01 size={14} sx={{ position: 'relative', top: 2, marginRight: 4 }} />,
      isLeaf: false,
      children:
        schema.tables?.map((table) => ({
          title: table.table_name,
          key: `${table.schema_name},${table.table_name}`,
          icon: <IconLayoutGrid02 size={14} sx={{ position: 'relative', top: 2, marginRight: 4 }} />,
          isLeaf: true
        })) ?? []
    }))
  }, [schemaGroups])

  const schemaTableCount = useMemo(() => {
    return new Map(schemaGroups.map((schema) => [schema.schema_name, schema.tables.length]))
  }, [schemaGroups])

  const onSubmit = async (values: PremiumStep2FormData) => {
    setErrorMessage('')
    if (!values.full_instance_migrate && selectedObjectKeys.length === 0) {
      setErrorMessage(
        'No selected objects. You have to pick some in the source database and move them to the right side.'
      )
      return
    }

    const formValues = getValues() as PremiumStep1FormData & PremiumStep2FormData
    const connProfile = buildPremiumConnProfile(formValues)
    const sourceType = formValues.data_source as V1beta2PremiumDMSourceType
    const source: V1beta2PremiumDMSource = {
      conn_profile: connProfile,
      source_type: sourceType,
      premium_migration_rules: []
    }

    if (values.start_position_method === 'binlog' && values.binlog_name) {
      source.binlog_name = values.binlog_name
      source.binlog_position = values.binlog_position || 0
    } else if (values.start_position_method === 'gtid' && values.binlog_gtid) {
      source.binlog_gtid = values.binlog_gtid
    }

    if (values.selectObjectMethod === 'customize' && selectedObjectKeys.length > 0) {
      const schemaSelections = new Map<string, { hasSchema: boolean; tables: Set<string> }>()
      selectedObjectKeys.forEach((key) => {
        const [schema, table] = key.split(',')
        if (!schema) return
        const selection = schemaSelections.get(schema) ?? { hasSchema: false, tables: new Set<string>() }
        if (table) {
          selection.tables.add(table)
        } else {
          selection.hasSchema = true
        }
        schemaSelections.set(schema, selection)
      })

      const rules: V1beta2PremiumDMSource['premium_migration_rules'] = []
      schemaSelections.forEach((selection, schema) => {
        const totalTables = schemaTableCount.get(schema) ?? 0
        const allTablesSelected = totalTables > 0 && selection.tables.size === totalTables
        if (allTablesSelected || (selection.hasSchema && selection.tables.size === 0)) {
          rules.push({
            source_table: {
              schema_pattern: schema,
              table_pattern: '*'
            },
            target_table: {
              schema,
              table: undefined
            }
          })
          return
        }

        selection.tables.forEach((table) => {
          rules.push({
            source_table: {
              schema_pattern: schema,
              table_pattern: table
            },
            target_table: {
              schema,
              table
            }
          })
        })
      })
      source.premium_migration_rules = rules
    }

    setValue('sources', [source])

    try {
      const res = await precheck.mutateAsync({
        tidbId: tidb.tidbId!,
        data: {
          display_name: formValues.name,
          sources: [source],
          import_mode: values.import_mode,
          target: {
            user: formValues.cluster_username,
            password: formValues.cluster_password
          },
          mode: values.migration_mode
        }
      })

      setValue('precheckId', res.precheck_id ?? '')
      onFinish()
    } catch (error) {
      setErrorMessage(getErrorMessage(error, true) || 'Failed to create migration precheck')
    }
  }

  return (
    <Card withBorder shadow="none" p={0}>
      <form onSubmit={handleSubmit((values) => onSubmit(values as PremiumStep2FormData))}>
        <Stack m={24}>
          <Typography variant="headline-sm">Migration Type</Typography>

          <Group align="flex-start">
            <Typography variant="label-lg" miw={240}>
              Migration process
            </Typography>
            <FormRadioGroup
              name="migration_mode"
              data={PremiumDMTypeOptions}
              styles={{ root: { '--mantine-spacing-xs': '4px' } }}
            />
          </Group>

          <Group align="flex-start">
            <Group gap={4} miw={240}>
              <Typography variant="label-lg">Existing data migration mode</Typography>
              <HoverCard shadow="md" withArrow>
                <HoverCard.Target>
                  <IconInfoCircle size={14} />
                </HoverCard.Target>
                <HoverCard.Dropdown maw={400}>
                  <Typography variant="body-lg">
                    Physical migration mode is faster but requires the target tables that need data migration to be
                    empty. Logical mode is slower but allows migration into tables with existing data.
                  </Typography>
                </HoverCard.Dropdown>
              </HoverCard>
            </Group>

            <FormRadioGroup
              name="import_mode"
              data={[
                {
                  value: PremiumDMTaskImportModeENUM.IMPORTMODE_PHYSICAL,
                  label: 'Physical'
                },
                { value: PremiumDMTaskImportModeENUM.IMPORTMODE_LOGICAL, label: 'Logical' }
              ]}
              styles={{ root: { '--mantine-spacing-xs': '4px' } }}
            />
          </Group>

          {isIncremental && !isFull && (
            <Stack maw={500}>
              <Group gap={8}>
                <Typography variant="title-lg">Start Position</Typography>
                <HoverCard shadow="md" withArrow>
                  <HoverCard.Target>
                    <IconInfoCircle size={14} />
                  </HoverCard.Target>
                  <HoverCard.Dropdown>
                    <Typography variant="body-lg" maw={300}>
                      Support migration of incremental data generated after the DM job starts, or from user-specified
                      binlog position or GTID. Refer to{' '}
                      <a
                        href="https://docs.pingcap.com/tidbcloud/migrate-incremental-data-from-mysql-using-data-migration#step-3-choose-migration-job-type"
                        target="_blank"
                        rel="noreferrer"
                      >
                        documentation
                      </a>{' '}
                      for details.
                    </Typography>
                  </HoverCard.Dropdown>
                </HoverCard>
              </Group>
              <Stack>
                <FormSelect
                  name="start_position_method"
                  size="xs"
                  rules={{ required: 'Required' }}
                  placeholder="Choose when the incremental migration job starts"
                  data={[
                    {
                      label: 'The earliest existing binlog position currently available on the source',
                      value: 'current'
                    },
                    { label: 'Specify GTID', value: 'gtid' },
                    { label: 'Specify binlog file name and position', value: 'binlog' }
                  ]}
                  miw={330}
                />
                {startPositionMethod === 'binlog' && (
                  <>
                    <FormTextInput
                      name="binlog_name"
                      rules={{ required: 'Required' }}
                      size="xs"
                      label="Binlog File Name"
                      placeholder="Enter the binlog file name"
                    />
                    <FormNumberInput
                      name="binlog_position"
                      size="xs"
                      label="Binlog Position"
                      placeholder="Enter the binlog position"
                    />
                  </>
                )}
                {startPositionMethod === 'gtid' && (
                  <FormTextInput
                    name="binlog_gtid"
                    size="xs"
                    label="GTID"
                    placeholder="Enter the GTID"
                    rules={{ required: 'Required' }}
                  />
                )}
              </Stack>
            </Stack>
          )}
        </Stack>

        <Divider />

        <Stack m={24}>
          <Typography variant="headline-sm">Select Objects to Migrate</Typography>

          <div>
            <SegmentedControl
              size="sm"
              value={selectObjectMethod}
              onChange={(val) => {
                setSelectObjectMethod(val as PremiumStep2FormData['selectObjectMethod'])
                setValue('full_instance_migrate', val === 'all')
                if (val === 'all') {
                  setSelectedObjectKeys([])
                }
              }}
              miw={240}
              data={(['all', 'customize'] as const).map((i) => ({ label: capitalize(i), value: i }))}
            />
          </div>

          {selectObjectMethod === 'customize' && (
            <>
              <TransferTree
                className="wd-h-[400px]"
                dataSource={dataSource}
                onChange={(tree) => setSelectedObjectKeys(flatTree2Keys(tree))}
                treeProps={{ showIcon: true, virtual: true }}
                initialTargetKeys={formData.selectedObjectKeys}
                sourceHeader={
                  <Group justify="space-between" p={16} sx={(theme) => ({ backgroundColor: theme.colors.carbon[3] })}>
                    <b>Source Database</b>
                    {isLoadingTables ? (
                      <Loader size="xs" />
                    ) : (
                      <ActionIcon variant="default" onClick={() => refetch()}>
                        <IconRefreshCw01 size={14} />
                      </ActionIcon>
                    )}
                  </Group>
                }
                targetHeader={
                  <Group justify="space-between" p={16} sx={(theme) => ({ backgroundColor: theme.colors.carbon[3] })}>
                    <Typography fw={700} lh="28px">
                      Selected Objects
                    </Typography>
                  </Group>
                }
              />
              {!!tablesError && <FormErrorMessage message={getErrorMessage(tablesError)} />}
            </>
          )}
        </Stack>

        <Divider />

        {errorMessage && (
          <Box m={24}>
            <FormErrorMessage message={errorMessage} />
          </Box>
        )}

        <Group justify="flex-end" m={24}>
          <Button variant="default" type="button" onClick={onCancel}>
            Cancel
          </Button>

          <Button variant="outline" type="button" onClick={onGoPrev}>
            Previous
          </Button>

          <Button type="submit" loading={precheck.isLoading}>
            Next
          </Button>
        </Group>
      </form>
    </Card>
  )
}

export const PremiumDMStep2 = observer(Step2)
