import {
  Alert,
  Anchor,
  Box,
  Button,
  Center,
  Divider,
  Group,
  Modal,
  Stack,
  Switch,
  TextInput,
  Tooltip,
  Typography,
  notifier
} from '@tidbcloud/uikit'
import { FormNumberInput, FormSelect } from '@tidbcloud/uikit/biz'
import { IconAlertTriangle, IconInfoCircle, IconXClose } from '@tidbcloud/uikit/icons'
import { useEffect, useMemo, type ReactNode } from 'react'
import { FormProvider, useForm, useWatch } from 'react-hook-form'

import { ComponentBlock } from 'dbaas/features/ClusterManagement/dedicated'
import { useRequestSupport } from 'dbaas/screens/HelpCenter/RequestSupport/helper'
import {
  useRegionServiceGetByocSharedComponentScaleConstraints,
  useRegionServiceScaleByocSharedComponents
} from 'dbaas/services/api-clients/v2025/tidb-management/v1beta2'
import {
  Nextgenv1beta2ByocSharedComponentScaleConstraint,
  Nextgenv1beta2ByocSharedComponentScaleNodeSpecCandidate,
  Nextgenv1beta2ByocSharedComponentScaleTarget,
  Nextgenv1beta2ByocSharedComponentType
} from 'dbaas/services/api-clients/v2025/tidb-management/v1beta2/interface'
import { getErrorMessage } from 'dbaas/services/errorCodes'

import { PoolNameTag } from './PoolNameTag'

interface VCPUManualScalingModalProps {
  orgId: string
  opened: boolean
  poolName: string
  projectId: string
  resourcePoolId?: string
  onClose: () => void
  onResetLimit?: () => void
  onSuccess?: () => void
}

interface ManualScalingFormValues {
  tikvStorage: number | undefined
  tikvNodes: number | undefined
  tiflashSpec: string
  tiflashStorage: number | undefined
  tiflashNodes: number | undefined
  tiflashEnabled: boolean
}

const storageNodeInputStyles = {
  root: {
    flexGrow: 1
  }
}

const renderNodesSuffix = (value: number | undefined) => `${value === 1 ? ' Node' : ' Nodes'}`
const TIKV_DATA_STORAGE_NAME = 'DATA'
const SILENT_BYOC_QUERY_REQUEST = { isShowToast: false, isHandleErr: false }

const EvaluationItem = ({
  label,
  value,
  tooltip,
  tooltipLabel,
  warning
}: {
  label: string
  value: string | number
  tooltip?: boolean
  tooltipLabel?: ReactNode
  warning?: boolean
}) => {
  return (
    <Stack gap={4}>
      <Group gap={6} align="center">
        <Typography variant="body-md" c="carbon.8">
          {label}
        </Typography>
        {tooltip && (
          <Tooltip label={tooltipLabel} disabled={!tooltipLabel} withArrow multiline maw={360}>
            <Box>
              <IconInfoCircle size={14} color="var(--mantine-color-carbon-6)" />
            </Box>
          </Tooltip>
        )}
      </Group>
      <Typography variant="body-lg" c={warning ? 'red.7' : 'carbon.9'} fz={22}>
        {value}
      </Typography>
    </Stack>
  )
}

const formatSpecLabel = (candidate?: Nextgenv1beta2ByocSharedComponentScaleNodeSpecCandidate) => {
  if (!candidate) return '-'
  return `${candidate.cpu ?? '-'} vCPU, ${candidate.memoryGi ?? '-'} GiB`
}

const getCurrentSpecValue = (component?: Nextgenv1beta2ByocSharedComponentScaleConstraint) => {
  const current = component?.current
  const candidates = component?.nodeSpecConstraints?.candidates ?? []
  const currentCandidate = candidates.find(
    (candidate) => candidate.isCurrent || (candidate.cpu === current?.cpu && candidate.memoryGi === current?.memoryGi)
  )

  return currentCandidate?.name ?? component?.nodeSpecConstraints?.current ?? candidates[0]?.name ?? ''
}

const getCandidate = (component: Nextgenv1beta2ByocSharedComponentScaleConstraint | undefined, value: string) =>
  component?.nodeSpecConstraints?.candidates?.find((candidate) => candidate.name === value)

const getCurrentCpu = (component?: Nextgenv1beta2ByocSharedComponentScaleConstraint) => {
  const value = Number(component?.current?.cpu)
  return Number.isFinite(value) && value > 0 ? value : undefined
}

const toFiniteNumber = (value: number | string | null | undefined) => {
  const parsed = Number(value)
  return Number.isFinite(parsed) ? parsed : undefined
}

const getStorageByName = (component: Nextgenv1beta2ByocSharedComponentScaleConstraint | undefined, name: string) =>
  component?.current?.storages?.find((storage) => storage.storageName === name)

const getStorageText = (component?: Nextgenv1beta2ByocSharedComponentScaleConstraint, storageName?: string) => {
  const sizeGi = (storageName ? getStorageByName(component, storageName) : component?.current?.storages?.[0])?.sizeGi
  return sizeGi ? `${sizeGi} GiB` : '-'
}

const getCurrentStorageSize = (component?: Nextgenv1beta2ByocSharedComponentScaleConstraint, storageName?: string) =>
  (storageName ? getStorageByName(component, storageName) : component?.current?.storages?.[0])?.sizeGi ?? ''

const getCurrentStorageSizeNumber = (
  component?: Nextgenv1beta2ByocSharedComponentScaleConstraint,
  storageName?: string
) => {
  const value = Number(getCurrentStorageSize(component, storageName))
  return Number.isFinite(value) && value > 0 ? value : undefined
}

const getCandidateStorageSizeNumber = (
  component: Nextgenv1beta2ByocSharedComponentScaleConstraint | undefined,
  specValue: string
) => {
  const candidate = getCandidate(component, specValue)
  const value = Number(candidate?.storageConstraints?.[0]?.currentSizeGi)
  return Number.isFinite(value) && value > 0 ? value : undefined
}

const getTargetStorageSizeNumber = (
  component: Nextgenv1beta2ByocSharedComponentScaleConstraint | undefined,
  specValue: string
) => getCurrentStorageSizeNumber(component) ?? getCandidateStorageSizeNumber(component, specValue)

const getCurrentNodeCount = (component?: Nextgenv1beta2ByocSharedComponentScaleConstraint) =>
  Number(component?.current?.nodeCount ?? component?.nodeCountConstraints?.current ?? 0)

const isPositiveInteger = (value: number | undefined) => Number.isInteger(value) && Number(value) >= 0

const getComponentVcpu = (
  component: Nextgenv1beta2ByocSharedComponentScaleConstraint | undefined,
  cpu?: number | string | null,
  nodeCount?: number | string | null
) => {
  if (!component) return 0

  const targetCpu = cpu ?? component.current?.cpu
  const targetNodeCount = nodeCount ?? component.current?.nodeCount ?? component.nodeCountConstraints?.current
  const parsedCpu = toFiniteNumber(targetCpu)
  const parsedNodeCount = toFiniteNumber(targetNodeCount)

  return parsedCpu !== undefined && parsedNodeCount !== undefined ? parsedCpu * parsedNodeCount : 0
}

const buildTarget = (
  component: Nextgenv1beta2ByocSharedComponentScaleConstraint | undefined,
  specValue: string,
  nodeCount: number | undefined,
  storageSizeGi?: number,
  options?: {
    includeSpec?: boolean
    storageName?: string
  }
): Nextgenv1beta2ByocSharedComponentScaleTarget | undefined => {
  if (!component) return undefined

  const candidate = getCandidate(component, specValue)
  const target: Nextgenv1beta2ByocSharedComponentScaleTarget = {
    nodeCount: nodeCount === undefined ? null : String(nodeCount),
    storages: component.current?.storages
      ?.filter((storage) => !options?.storageName || storage.storageName === options.storageName)
      .map((storage, index) => ({
        storageName: storage.storageName,
        sizeGi:
          (options?.storageName ? storage.storageName === options.storageName : index === 0) &&
          storageSizeGi !== undefined
            ? String(storageSizeGi)
            : (storage.sizeGi ?? null)
      }))
  }

  if (options?.includeSpec !== false) {
    target.cpu = candidate?.cpu ?? component.current?.cpu ?? null
    target.memoryGi = candidate?.memoryGi ?? component.current?.memoryGi ?? null
  }

  return target
}

const buildNodeCountOnlyTarget = (
  component: Nextgenv1beta2ByocSharedComponentScaleConstraint | undefined,
  nodeCount: number
): Nextgenv1beta2ByocSharedComponentScaleTarget | undefined => {
  if (!component) return undefined

  return {
    nodeCount: String(nodeCount)
  }
}

const getCurrentSpecLabel = (component?: Nextgenv1beta2ByocSharedComponentScaleConstraint) => {
  const candidate = getCandidate(component, getCurrentSpecValue(component))
  if (candidate) return formatSpecLabel(candidate)
  if (component?.current?.cpu || component?.current?.memoryGi) {
    return `${component.current.cpu ?? '-'} vCPU, ${component.current.memoryGi ?? '-'} GiB`
  }
  return '-'
}

export const VCPUManualScalingModal = ({
  orgId,
  opened,
  poolName,
  projectId,
  resourcePoolId,
  onClose,
  onResetLimit,
  onSuccess
}: VCPUManualScalingModalProps) => {
  const form = useForm<ManualScalingFormValues>({
    defaultValues: {
      tikvStorage: undefined,
      tikvNodes: undefined,
      tiflashSpec: '',
      tiflashStorage: undefined,
      tiflashNodes: undefined,
      tiflashEnabled: false
    }
  })
  const formValues = useWatch({ control: form.control })
  const tiflashEnabled = !!formValues.tiflashEnabled
  const scaleSharedComponents = useRegionServiceScaleByocSharedComponents()
  const { open: openSupport } = useRequestSupport()

  const {
    data: constraints,
    isFetching,
    refetch
  } = useRegionServiceGetByocSharedComponentScaleConstraints(
    resourcePoolId ?? '',
    { orgId, projectId },
    {
      query: {
        enabled: opened && !!resourcePoolId && !!orgId && !!projectId,
        refetchOnWindowFocus: false
      },
      request: SILENT_BYOC_QUERY_REQUEST
    }
  )

  const tikvComponent = useMemo(
    () =>
      constraints?.components?.find(
        (component) => component.componentType === Nextgenv1beta2ByocSharedComponentType.BYOC_SHARED_COMPONENT_TYPE_TIKV
      ),
    [constraints?.components]
  )
  const tiflashComputeComponent = useMemo(
    () =>
      constraints?.components?.find(
        (component) =>
          component.componentType === Nextgenv1beta2ByocSharedComponentType.BYOC_SHARED_COMPONENT_TYPE_TIFLASH_COMPUTE
      ),
    [constraints?.components]
  )
  const tiflashDisplayComponent = tiflashComputeComponent

  useEffect(() => {
    if (!opened || !constraints) return

    const currentTiflashNodes = getCurrentNodeCount(tiflashDisplayComponent)
    const currentTiflashSpec = getCurrentSpecValue(tiflashDisplayComponent)
    form.reset({
      tikvStorage: getCurrentStorageSizeNumber(tikvComponent, TIKV_DATA_STORAGE_NAME),
      tikvNodes: getCurrentNodeCount(tikvComponent),
      tiflashSpec: currentTiflashSpec,
      tiflashStorage: getTargetStorageSizeNumber(tiflashDisplayComponent, currentTiflashSpec),
      tiflashNodes: currentTiflashNodes,
      tiflashEnabled: currentTiflashNodes > 0
    })
  }, [constraints, form, opened, tiflashDisplayComponent, tikvComponent])

  useEffect(() => {
    if (!opened || !constraints || !tiflashEnabled) return

    const targetStorage = getTargetStorageSizeNumber(tiflashDisplayComponent, formValues.tiflashSpec ?? '')
    if (!targetStorage) return

    const currentStorage = Number(formValues.tiflashStorage)
    if (!Number.isFinite(currentStorage) || currentStorage < targetStorage) {
      form.setValue('tiflashStorage', targetStorage, { shouldValidate: true })
    }
  }, [
    constraints,
    form,
    formValues.tiflashSpec,
    formValues.tiflashStorage,
    opened,
    tiflashDisplayComponent,
    tiflashEnabled
  ])

  const currentTiflashCpu = getCurrentCpu(tiflashDisplayComponent)
  const tiflashSpecOptions =
    tiflashDisplayComponent?.nodeSpecConstraints?.candidates
      ?.filter((candidate) => {
        const candidateCpu = Number(candidate.cpu)
        return !currentTiflashCpu || (Number.isFinite(candidateCpu) && candidateCpu >= currentTiflashCpu)
      })
      .map((candidate) => ({
        label: formatSpecLabel(candidate),
        value: candidate.name ?? ''
      })) ?? []

  const isReady = !!constraints && !isFetching
  const selectedTiflashCandidate = getCandidate(tiflashDisplayComponent, formValues.tiflashSpec ?? '')
  const minimumTiflashNodeCount = Math.max(1, getCurrentNodeCount(tiflashDisplayComponent))
  const tikvNodeInvalid =
    isReady &&
    (!isPositiveInteger(formValues.tikvNodes) || Number(formValues.tikvNodes) < getCurrentNodeCount(tikvComponent))
  const currentTikvStorage = getCurrentStorageSizeNumber(tikvComponent, TIKV_DATA_STORAGE_NAME)
  const tikvStorageInvalid =
    isReady &&
    (!currentTikvStorage ||
      !isPositiveInteger(formValues.tikvStorage) ||
      Number(formValues.tikvStorage) < currentTikvStorage)
  const tiflashNodeInvalid =
    isReady &&
    tiflashEnabled &&
    (!isPositiveInteger(formValues.tiflashNodes) || Number(formValues.tiflashNodes) < minimumTiflashNodeCount)
  const currentTiflashStorage = getTargetStorageSizeNumber(tiflashDisplayComponent, formValues.tiflashSpec ?? '')
  const tiflashStorageInvalid =
    isReady &&
    tiflashEnabled &&
    (!currentTiflashStorage ||
      !isPositiveInteger(formValues.tiflashStorage) ||
      Number(formValues.tiflashStorage) < currentTiflashStorage)
  const tiflashSpecInvalid =
    isReady &&
    tiflashEnabled &&
    (!selectedTiflashCandidate ||
      (!!currentTiflashCpu &&
        (!Number.isFinite(Number(selectedTiflashCandidate.cpu)) ||
          Number(selectedTiflashCandidate.cpu) < currentTiflashCpu)))
  const saveDisabled =
    !resourcePoolId ||
    !isReady ||
    tikvNodeInvalid ||
    tikvStorageInvalid ||
    tiflashNodeInvalid ||
    tiflashStorageInvalid ||
    tiflashSpecInvalid

  const localPreview = useMemo(() => {
    const currentVcpusUsed = toFiniteNumber(constraints?.preview?.currentVcpusUsed)
    const maxVcpusLimit = toFiniteNumber(constraints?.preview?.maxVcpusLimit ?? constraints?.vcpusLimit)
    const selectedTiflashCpu = toFiniteNumber(selectedTiflashCandidate?.cpu)

    const currentSharedVcpus = getComponentVcpu(tikvComponent) + getComponentVcpu(tiflashDisplayComponent)
    const targetSharedVcpus =
      getComponentVcpu(tikvComponent, tikvComponent?.current?.cpu, formValues.tikvNodes) +
      (tiflashEnabled ? getComponentVcpu(tiflashDisplayComponent, selectedTiflashCpu, formValues.tiflashNodes) : 0)
    const scalingVcpusUsed =
      currentVcpusUsed !== undefined ? currentVcpusUsed + targetSharedVcpus - currentSharedVcpus : undefined
    const allowed =
      scalingVcpusUsed !== undefined && maxVcpusLimit !== undefined ? scalingVcpusUsed <= maxVcpusLimit : undefined

    return {
      allowed,
      currentVcpusUsed: constraints?.preview?.currentVcpusUsed ?? '-',
      maxVcpusLimit: constraints?.preview?.maxVcpusLimit ?? constraints?.vcpusLimit ?? '-',
      scalingVcpusUsed:
        scalingVcpusUsed === undefined ? (constraints?.preview?.scalingVcpusUsed ?? '-') : scalingVcpusUsed
    }
  }, [
    constraints?.preview?.currentVcpusUsed,
    constraints?.preview?.maxVcpusLimit,
    constraints?.preview?.scalingVcpusUsed,
    constraints?.vcpusLimit,
    formValues.tiflashNodes,
    formValues.tikvNodes,
    selectedTiflashCandidate?.cpu,
    tiflashDisplayComponent,
    tiflashEnabled,
    tikvComponent
  ])

  const handleSave = form.handleSubmit(async (data) => {
    if (!resourcePoolId || saveDisabled) return

    try {
      await scaleSharedComponents.mutateAsync({
        resourcePoolId,
        data: {
          tikv: buildTarget(tikvComponent, '', data.tikvNodes, data.tikvStorage, {
            includeSpec: false,
            storageName: TIKV_DATA_STORAGE_NAME
          }),
          tiflashCompute: data.tiflashEnabled
            ? buildTarget(tiflashComputeComponent, data.tiflashSpec, data.tiflashNodes, data.tiflashStorage)
            : buildNodeCountOnlyTarget(tiflashComputeComponent, 0)
        },
        params: {
          orgId,
          projectId
        }
      })
      notifier.success('Basic capacity settings updated successfully.')
      refetch()
      onSuccess?.()
      onClose()
    } catch (error) {
      notifier.error(getErrorMessage(error, true))
    }
  })

  return (
    <Modal
      opened={opened}
      onClose={onClose}
      centered
      size={890}
      padding="xl"
      title={
        <Group gap="sm">
          <PoolNameTag poolName={poolName} />
          <Typography variant="title-lg">Basic Capacity Settings</Typography>
        </Group>
      }
    >
      <FormProvider {...form}>
        <Stack gap={0}>
          <Stack gap="lg" pb="xl">
            <Alert icon={<IconInfoCircle size={16} />} color="peacock">
              Only scale-out operations are supported here. If you have additional nodes and size requirements, or
              Scale-in requirement, please contact TiDB Cloud{' '}
              <Anchor component="button" type="button" onClick={() => openSupport()}>
                support
              </Anchor>
              .
            </Alert>

            <ComponentBlock
              title="TiKV"
              description="The storage node that stores the online transactional processing (OLTP) data."
            >
              <ComponentBlock.Item label="vCPU, RAM">
                <TextInput size="sm" value={getCurrentSpecLabel(tikvComponent)} disabled />
              </ComponentBlock.Item>
              <ComponentBlock.Item label="Storage × Nodes">
                <Group gap={8} align="flex-start">
                  <FormNumberInput
                    size="sm"
                    name="tikvStorage"
                    disabled={!isReady || !currentTikvStorage}
                    min={currentTikvStorage}
                    step={10}
                    rules={{
                      required: 'Please enter a storage size.',
                      min: {
                        value: currentTikvStorage ?? 0,
                        message: `Cannot be less than current storage (${getStorageText(tikvComponent, TIKV_DATA_STORAGE_NAME)}).`
                      }
                    }}
                    suffix=" GiB"
                    sx={storageNodeInputStyles}
                  />
                  <Center h={32}>
                    <IconXClose size={16} color="var(--mantine-color-carbon-6)" />
                  </Center>
                  <FormNumberInput
                    size="sm"
                    name="tikvNodes"
                    disabled={!isReady}
                    min={getCurrentNodeCount(tikvComponent)}
                    step={3}
                    rules={{
                      required: 'Please enter node count.',
                      min: {
                        value: getCurrentNodeCount(tikvComponent),
                        message: `Cannot be less than current nodes (${getCurrentNodeCount(tikvComponent)}).`
                      },
                      validate: (value) => (Number(value) % 3 === 0 ? true : 'Node count must be in increments of 3.')
                    }}
                    suffix={renderNodesSuffix(formValues.tikvNodes)}
                    sx={storageNodeInputStyles}
                  />
                </Group>
              </ComponentBlock.Item>
            </ComponentBlock>

            <ComponentBlock
              title={
                <>
                  TiFlash{' '}
                  <Box component="span" fw="initial" c="carbon.7">
                    (Optional)
                  </Box>
                </>
              }
              description="The analytical storage node that replicates data from TiKV in real-time and supports real-time analytical workloads."
              rightSection={
                <Switch
                  checked={tiflashEnabled}
                  onChange={(event) => {
                    const enabled = event.currentTarget.checked
                    form.setValue('tiflashEnabled', enabled, { shouldValidate: true })
                    const currentNodes = Number(form.getValues('tiflashNodes'))
                    if (enabled && (!Number.isFinite(currentNodes) || currentNodes < minimumTiflashNodeCount)) {
                      form.setValue('tiflashNodes', minimumTiflashNodeCount, { shouldValidate: true })
                    }
                  }}
                />
              }
            >
              {tiflashEnabled && (
                <>
                  <ComponentBlock.Item label="vCPU, RAM">
                    <FormSelect
                      size="sm"
                      name="tiflashSpec"
                      data={tiflashSpecOptions}
                      disabled={!isReady}
                      rules={{
                        required: 'Please select a TiFlash spec.'
                      }}
                    />
                  </ComponentBlock.Item>
                  <ComponentBlock.Item label="Storage × Nodes">
                    <Group gap={8} align="flex-start">
                      <FormNumberInput
                        size="sm"
                        name="tiflashStorage"
                        disabled={!isReady || !currentTiflashStorage}
                        min={currentTiflashStorage}
                        step={10}
                        rules={{
                          required: 'Please enter a storage size.',
                          min: {
                            value: currentTiflashStorage ?? 0,
                            message: `Cannot be less than current storage (${currentTiflashStorage ?? '-'} GiB).`
                          }
                        }}
                        suffix=" GiB"
                        sx={storageNodeInputStyles}
                      />
                      <Center h={32}>
                        <IconXClose size={16} color="var(--mantine-color-carbon-6)" />
                      </Center>
                      <FormNumberInput
                        size="sm"
                        name="tiflashNodes"
                        disabled={!isReady}
                        min={minimumTiflashNodeCount}
                        step={1}
                        rules={{
                          required: 'Please enter node count.',
                          min: {
                            value: minimumTiflashNodeCount,
                            message: `Cannot be less than ${minimumTiflashNodeCount} node${minimumTiflashNodeCount === 1 ? '' : 's'}.`
                          }
                        }}
                        suffix={renderNodesSuffix(formValues.tiflashNodes)}
                        sx={storageNodeInputStyles}
                      />
                    </Group>
                  </ComponentBlock.Item>
                </>
              )}
            </ComponentBlock>

            <Group
              justify="space-between"
              p="lg"
              sx={(theme) => ({
                border: `1px solid ${theme.colors.carbon[4]}`,
                borderRadius: theme.radius.md
              })}
            >
              <EvaluationItem label="Current vCPU Usage" value={localPreview.currentVcpusUsed} />
              <EvaluationItem
                label="Scaling vCPU Preview"
                value={localPreview.scalingVcpusUsed}
                tooltip
                tooltipLabel="Review the estimated vCPU usage before applying changes."
              />
              <EvaluationItem label="Max vCPU Limit" value={localPreview.maxVcpusLimit} />
              <EvaluationItem
                label="Scale Evaluation"
                value={localPreview.allowed === false ? 'Exceeds limit' : 'Allowed'}
                tooltip
                tooltipLabel="Indicates whether the scaling plan stays within the max vCPU limit."
                warning={localPreview.allowed === false}
              />
            </Group>

            {localPreview.allowed === false && (
              <Alert icon={<IconAlertTriangle size={16} />} color="yellow">
                {constraints?.preview?.blockingReasons?.join(' ') || (
                  <>
                    This scaling plan exceeds the maximum vCPU limit. Please adjust the scaling nodes value above or go
                    to{' '}
                    <Anchor component="button" type="button" onClick={onResetLimit}>
                      reset the max limit
                    </Anchor>
                    .
                  </>
                )}
              </Alert>
            )}
          </Stack>

          <Divider mx="-xl" />
          <Group justify="flex-end" pt="xl">
            <Button variant="default" onClick={onClose}>
              Cancel
            </Button>
            <Button onClick={handleSave} loading={scaleSharedComponents.isLoading} disabled={saveDisabled}>
              Save
            </Button>
          </Group>
        </Stack>
      </FormProvider>
    </Modal>
  )
}
