import {
  ActionIcon,
  Box,
  Button,
  Card,
  Divider,
  Group,
  HoverCard,
  Loader,
  SegmentedControl,
  Stack,
  Typography
} from '@tidbcloud/uikit'
import {
  type TreeDataNode,
  FormCheckboxGroup,
  FormNumberInput,
  FormRadioGroup,
  FormSelect,
  FormTextInput,
  TransferTree
} from '@tidbcloud/uikit/biz'
import { IconDatabase01, IconInfoCircle, IconLayoutGrid02, IconRefreshCw01 } from '@tidbcloud/uikit/icons'
import { useUnmount } from 'ahooks'
import { capitalize, omit, pick, uniqBy } from 'lodash-es'
import { observer } from 'mobx-react'
import { useMemo, useRef, useState } from 'react'
import { Controller, FormProvider, useForm } from 'react-hook-form'
import { useQuery } from 'react-query'

import { FormErrorMessage } from 'dbaas/components/Form'
import { DmImportMode } from 'dbaas/features/DataMigration'
import { usePhysicalModeSelectable } from 'dbaas/features/DataMigration/helpers'
import type { FormData } from 'dbaas/screens/DataMigration/CreateDataMigrationDedicated'
import { getSourceTables, usePrecheckJob } from 'dbaas/services/dataflow'
import { CommonTable, CreateDMJobBodyBody, DmSource } from 'dbaas/services/dataflow/interface'
import { getErrorMessage } from 'dbaas/services/errorCodes'
import useStores from 'dbaas/stores/useStores'

import { flatTree2Keys } from './utils'

export interface Step2FormFields {
  full_instance_migrate: boolean
  migration_mode: ('full' | 'incremental')[]
  mode: 'all' | 'full' | 'incremental'
  sources: DmSource[]
  precheckId: string
  selectObjectMethod: 'all' | 'customize'
  selectedObjectKeys: string[]
  start_position_method: 'gtid' | 'binlog' | 'current'
  binlog_name?: string
  binlog_pos?: number
  binlog_gtid?: string
  import_mode?: DmImportMode
}

const getMode = (isFull: boolean, isIncremental: boolean): Step2FormFields['mode'] => {
  if (isFull && isIncremental) {
    return 'all'
  } else if (isFull) {
    return 'full'
  } else {
    return 'incremental'
  }
}

const Step2 = ({
  onCancel,
  onGoPrev,
  onFinish,
  formData
}: {
  formData: FormData
  onFinish: (data?: any, next?: boolean) => void
  onCancel: () => void
  onGoPrev: () => void
}) => {
  const {
    store: { tenantData, activeProjectId, currentCluster }
  } = useStores()
  const method = useForm<Step2FormFields>({
    defaultValues: {
      migration_mode: formData.migration_mode ?? ['full', 'incremental'],
      mode: formData.mode ?? 'all',
      full_instance_migrate: formData.full_instance_migrate ?? true,
      start_position_method: formData.start_position_method ?? 'current',
      binlog_name: formData.binlog_name ?? '',
      binlog_pos: formData.binlog_pos ?? 0,
      binlog_gtid: formData.binlog_gtid ?? '',
      import_mode: formData.import_mode ?? DmImportMode.Logical
    }
  })
  const { control, handleSubmit, setValue, watch } = method
  const migrationMode = watch('migration_mode')
  const isFull = migrationMode.includes('full')
  const isIncremental = migrationMode.includes('incremental')
  const isAll = migrationMode.length === 2
  const startPositionMethod = watch('start_position_method')
  const [selectObjectMethod, setSelectObjectMethod] = useState<Step2FormFields['selectObjectMethod']>(
    formData.selectObjectMethod ?? 'all'
  )
  const precheck = usePrecheckJob()
  const [errorMessage, setErrorMessage] = useState('')

  const [selectedObjectKeys, setSelectedObjectKeys] = useState<string[]>(formData.selectedObjectKeys ?? [])
  const [rules, setRules] = useState<{
    do_dbs?: string[]
    ignore_dbs?: string[]
    do_tables?: Array<CommonTable & { key: string }>
    ignore_tables?: Array<CommonTable & { key: string }>
  }>({
    do_dbs: formData.sources?.[0]?.ba_rules?.do_dbs ?? [],
    ignore_dbs: formData.sources?.[0]?.ba_rules?.ignore_dbs ?? [],
    do_tables:
      formData.sources?.[0]?.ba_rules?.do_tables?.map((i) => ({ ...i, key: [i.schema, i.table].join(',') })) ?? [],
    ignore_tables:
      formData.sources?.[0]?.ba_rules?.ignore_tables?.map((i) => ({ ...i, key: [i.schema, i.table].join(',') })) ?? []
  })

  const selectedSourceTables = useRef<string[]>([])
  const selectedTargetTables = useRef<string[]>([])
  const targetTables = useRef<string[]>(formData.selectedObjectKeys ?? [])
  const fullDbs = useRef<Set<string>>(new Set())

  const [canSelectPhysicalMode, physicalModeTooltip] = usePhysicalModeSelectable(
    tenantData.id,
    activeProjectId,
    currentCluster!
  )

  const updateRules = () => {
    setRules((prev) => {
      const tableSet = new Set(targetTables.current.filter((i) => i.includes(',')))
      const do_dbs: (typeof rules)['do_dbs'] = targetTables.current
        .filter((i) => !i.includes(','))
        .filter((i) => {
          const db = data?.schemas?.find((s) => s.schema_name === i)
          if (db) {
            return (
              fullDbs.current.has(db.schema_name!) &&
              db.tables?.every((t) => tableSet.has([t.schema_name, t.table_name].join(',')))
            )
          }
          return false
        })

      const do_tables: (typeof rules)['do_tables'] = uniqBy(
        targetTables.current
          .filter((i) => i.includes(','))
          .filter((i) => !do_dbs.includes(i.split(',')?.[0]))
          .map((i) => {
            const [schema, table] = i.split(',')
            return { schema, table, key: i }
          }),
        'key'
      )

      if (do_dbs.length) {
        do_tables.push(...do_dbs.map((db) => ({ schema: db, table: '~.*', key: db })))
      }

      return {
        ...prev,
        do_tables
      }
    })
  }

  const {
    data,
    isFetching: isLoadingSourceTable,
    refetch,
    remove
  } = useQuery(
    '/source-tables',
    () =>
      getSourceTables(
        tenantData.id,
        activeProjectId,
        currentCluster?.id!,
        {
          conn_method: formData.connect_method,
          endpoint_service_name: formData.service_endpoint || undefined,
          host: formData.host || undefined,
          port: formData.port,
          user: formData.username,
          password: formData.password,
          ssl_ca: formData.ssl_ca || undefined,
          ssl_cert: formData.ssl_cert || undefined,
          ssl_key: formData.ssl_key || undefined,
          type: formData.data_source
        },
        { timeout: 60 * 1000 }
      ),
    {
      enabled: Boolean(formData.connect_method),
      refetchOnWindowFocus: false,
      refetchOnReconnect: false
    }
  )

  const dataSource = useMemo(() => {
    if (!data) return []
    return (
      data.schemas?.map((i) => ({
        title: i.schema_name,
        key: i.schema_name!,
        icon: <Box component={IconDatabase01} size={14} sx={{ position: 'relative', top: 2, marginRight: 4 }} />,
        isLeaf: false,
        children:
          i.tables?.map((j) => ({
            title: j.table_name,
            key: j.schema_name + ',' + j.table_name!,
            icon: <Box component={IconLayoutGrid02} size={14} sx={{ position: 'relative', top: 2, marginRight: 4 }} />,
            isLeaf: true
          })) ?? []
      })) ?? ([] as TreeDataNode[])
    )
  }, [data])

  const onSubmit = async (data: Step2FormFields) => {
    if (!data.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
    }

    try {
      const isPrivateLink = formData.connect_method?.toLowerCase().includes('private') ?? false

      const payload: CreateDMJobBodyBody = {
        name: formData.name,
        sources: [
          {
            ba_rules: data.full_instance_migrate
              ? undefined
              : {
                  ...rules,
                  do_dbs: rules.do_dbs ?? [],
                  do_tables: rules.do_tables?.map((i) => omit(i, 'key')) ?? [],
                  ignore_tables: rules.ignore_tables?.map((i) => omit(i, 'key')) ?? []
                },
            conn_profile: {
              type: formData.data_source,
              host: isPrivateLink ? undefined : formData.host || undefined,
              port: formData.port,
              user: formData.username,
              password: formData.password,
              conn_method: formData.connect_method,
              endpoint_service_name: isPrivateLink ? formData.service_endpoint || undefined : undefined,
              ssl_ca: formData.ssl_ca || undefined,
              ssl_cert: formData.ssl_cert || undefined,
              ssl_key: formData.ssl_key || undefined
            },
            ...pick(data, ['binlog_pos', 'binlog_name', 'binlog_gtid'])
          }
        ],
        cluster_user: formData.cluster_username,
        cluster_password: formData.cluster_password,
        mode: data.mode,
        full_instance_migrate: data.full_instance_migrate,
        import_mode: data.import_mode!
      }
      const res = await precheck.mutateAsync({
        orgId: tenantData.id,
        projectId: activeProjectId,
        clusterId: currentCluster?.id!,
        data: payload
      })

      onFinish({
        ...data,
        sources: payload.sources,
        precheckId: res.id,
        selectObjectMethod,
        selectedObjectKeys
      } as Step2FormFields)
    } catch (e) {
      setErrorMessage(getErrorMessage(e))
    }
  }

  useUnmount(() => {
    remove()
  })

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

            <Group align="flex-start">
              <Typography variant="label-lg" miw={240}>
                Migration process
              </Typography>
              <FormCheckboxGroup
                name="migration_mode"
                rules={{
                  required: 'Required',
                  validate: (values) => {
                    if (values.length === 0) {
                      return 'Select at least one migration strategy'
                    }
                  }
                }}
                data={[
                  { value: 'full', label: 'Existing data migration' },
                  { value: 'incremental', label: 'Incremental data migration' }
                ]}
                onChange={(vals) => {
                  const isFull = vals.includes('full')
                  const isInc = vals.includes('incremental')
                  setValue('mode', getMode(isFull, isInc))
                  if (vals.length === 1) {
                    setValue('import_mode', DmImportMode.Logical)
                  }
                }}
                styles={{
                  root: {
                    '.mantine-Group-root': { paddingTop: 0 }
                  }
                }}
              />

              <Controller name="mode" control={control} render={({ field }) => <input hidden {...field} />} />
            </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>

              <HoverCard disabled={isAll} shadow="md" withArrow>
                <HoverCard.Target>
                  <Box>
                    <FormRadioGroup
                      name="import_mode"
                      data={[
                        {
                          value: DmImportMode.Physical,
                          label: 'Physical',
                          disabled: !canSelectPhysicalMode || !isAll,
                          tooltip: physicalModeTooltip
                        },
                        { value: DmImportMode.Logical, label: 'Logical' }
                      ]}
                      styles={{ root: { '--mantine-spacing-xs': '4px' } }}
                    />
                  </Box>
                </HoverCard.Target>
                <HoverCard.Dropdown maw={400}>
                  When selecting only existing data migration or incremental data migration, only logical mode is
                  supported.
                </HoverCard.Dropdown>
              </HoverCard>
            </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 time when the incremental migration job starts', 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_pos"
                        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 className="wd-mb-4">
              <SegmentedControl
                size="md"
                value={selectObjectMethod}
                onChange={(val) => {
                  setSelectObjectMethod(val as any)
                  setValue('full_instance_migrate', val === 'all')
                  if (val === 'all') {
                    setSelectedObjectKeys([])
                  }
                }}
                w={240}
                data={(['all', 'customize'] as const).map((i) => ({ label: capitalize(i), value: i }))}
              />

              <Controller
                name="full_instance_migrate"
                control={control}
                //@ts-ignore
                render={({ field }) => <input hidden {...field} />}
              />
            </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>
                    {isLoadingSourceTable ? (
                      <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>
                }
                onCheckLeft={(selected: string[], info) => {
                  selectedSourceTables.current = selected
                  if (info.node.isLeaf) {
                    return
                  }
                  if (info.checked) {
                    fullDbs.current.add(info.node.key as string)
                  } else {
                    fullDbs.current.delete(info.node.key as string)
                  }
                }}
                onCheckRight={(selected: string[]) => {
                  selectedTargetTables.current = selected as string[]
                }}
                onMoveRight={() => {
                  targetTables.current = [...new Set([...targetTables.current, ...selectedSourceTables.current])]
                  updateRules()
                }}
                onMoveLeft={() => {
                  targetTables.current = [
                    ...new Set([...targetTables.current].filter((i) => !selectedTargetTables.current.includes(i)))
                  ]
                  updateRules()
                  selectedTargetTables.current = []
                }}
              />
            )}
          </Stack>

          <Divider />

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

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

            <Button variant="outline" type="button" onClick={onGoPrev} data-mp-event="mp-previous-1691461016965">
              Previous
            </Button>

            <Button type="submit" loading={precheck.isLoading} data-mp-event="mp-next-1691461016966">
              Next
            </Button>
          </Group>
        </form>
      </Card>
    </FormProvider>
  )
}

export const DedicatedDMStep2 = observer(Step2)
