import {
  Alert,
  Anchor,
  Box,
  Button,
  Code,
  Divider,
  Group,
  LoadingOverlay,
  Modal,
  notifier,
  Radio,
  ScrollArea,
  SimpleGrid,
  Stack,
  TextInput,
  Tooltip,
  Typography
} from '@tidbcloud/uikit'
import { Form, FormSelect, ProTable } from '@tidbcloud/uikit/biz'
import { IconAlertTriangle, IconClock, IconEdit05, IconInfoCircle } from '@tidbcloud/uikit/icons'
import { observer } from 'mobx-react'
import { useEffect, useRef, useState } from 'react'
import { useForm } from 'react-hook-form'
import { useQueryClient } from 'react-query'

import { useOrgProjectClusterIds } from 'dbaas/features/Common'
import { times, toLocalBackupTime, toUTCBackupTime } from 'dbaas/screens/Cluster/Backup/utils'
import {
  getTidbServiceGetBackupSettingQueryKey,
  tidbServiceListTidbBackups,
  useTidbServiceGetBackupSetting,
  useTidbServiceListBackupPolicies,
  useTidbServiceUpdateBackupSetting
} from 'dbaas/services/api-clients/v2025/tidb-management/v1beta2'
import {
  Commonv1beta1ServicePlan,
  Nextgenv1beta2Backup,
  Nextgenv1beta2BackupSettingBackupMode
} from 'dbaas/services/api-clients/v2025/tidb-management/v1beta2/interface'
import { getErrorMessage } from 'dbaas/services/errorCodes'
import useStores from 'dbaas/stores/useStores'
import { getUTCString } from 'dbaas/utils/format'
import { MixpanelEvents } from 'dbaas/utils/tracking/event'

import {
  DEFAULT_DAILY_BACKUP_TIME_UTC,
  DEFAULT_RETENTION_DAYS,
  getAffectedBackups,
  getBackupSettingOverview,
  getBackupSettingSubmitAction,
  hasBackupSettingChanged,
  MILLISECONDS_PER_DAY
} from './Settings.utils'

const AFFECTED_BACKUP_PREVIEW_SIZE = 12
const PREMIUM_PRICING_URL = 'https://www.pingcap.com/tidb-cloud-premium-pricing-details/'
const ESSENTIAL_PRICING_URL = 'https://www.pingcap.com/tidb-cloud-essential-pricing-details/'

const retentionOptions = Array.from({ length: 31 }, (_, index) => {
  const days = index + 3
  return { label: `${days} days`, value: days.toString() }
})

const modeLabels: Record<Nextgenv1beta2BackupSettingBackupMode, string> = {
  STANDARD_BUNDLE: 'Standard Bundle Mode',
  FLEXIBLE_RETENTION: 'Custom Retention Mode'
}

const formatRetentionDays = (days: number) => `${days} ${days === 1 ? 'day' : 'days'}`

interface BackupSettingFormValues {
  mode: Nextgenv1beta2BackupSettingBackupMode
  retentionDays: string
  dailyBackupTime: string
}

interface BackupSettingPremiumProps {
  tidbId: string
  instanceName: string
  servicePlan: Commonv1beta1ServicePlan
  isBYOC: boolean
  closeModal: () => void
}

async function listAffectedBackups({
  tidbId,
  orgId,
  projectId,
  retentionDays,
  previewTime,
  signal
}: {
  tidbId: string
  orgId: string
  projectId: string
  retentionDays: number
  previewTime: Date
  signal?: AbortSignal
}) {
  const backups: Nextgenv1beta2Backup[] = []
  const endTime = new Date(previewTime.valueOf() - retentionDays * MILLISECONDS_PER_DAY).toISOString()
  let pageToken: string | undefined

  do {
    const response = await tidbServiceListTidbBackups(
      tidbId,
      {
        orgId,
        projectId,
        states: ['SUCCEEDED'],
        triggerTypes: ['AUTO'],
        endTime,
        pageSize: 100,
        pageToken
      },
      undefined,
      signal
    )
    backups.push(...(response.backups ?? []))
    pageToken = response.nextPageToken || undefined
  } while (pageToken)

  return getAffectedBackups(backups, retentionDays, previewTime)
}

function _BackupSettingPremium({ tidbId, instanceName, servicePlan, isBYOC, closeModal }: BackupSettingPremiumProps) {
  const { orgId, projectId } = useOrgProjectClusterIds()
  const {
    store: { tenantData, activeProject }
  } = useStores()
  const queryClient = useQueryClient()
  const [confirmationOpened, setConfirmationOpened] = useState(false)
  const [pendingSetting, setPendingSetting] = useState<BackupSettingFormValues | null>(null)
  const [affectedBackups, setAffectedBackups] = useState<Nextgenv1beta2Backup[]>([])
  const [isPreparingConfirmation, setIsPreparingConfirmation] = useState(false)
  const [confirmation, setConfirmation] = useState('')
  const [showAllAffectedBackups, setShowAllAffectedBackups] = useState(false)
  const affectedBackupsRequestRef = useRef<AbortController | null>(null)

  useEffect(
    () => () => {
      affectedBackupsRequestRef.current?.abort()
    },
    []
  )

  const isEssentialV2 = servicePlan === 'Essential_V2'
  const methods = useForm<BackupSettingFormValues>({
    defaultValues: {
      mode: isEssentialV2 ? 'FLEXIBLE_RETENTION' : 'STANDARD_BUNDLE',
      retentionDays: DEFAULT_RETENTION_DAYS.toString(),
      dailyBackupTime: DEFAULT_DAILY_BACKUP_TIME_UTC
    }
  })
  const selectedMode = methods.watch('mode')
  const isFlexibleMode = selectedMode === 'FLEXIBLE_RETENTION'
  const pricingUrl = isEssentialV2 ? ESSENTIAL_PRICING_URL : PREMIUM_PRICING_URL
  const selectedRetentionDays = Number(methods.watch('retentionDays'))
  const selectedDailyBackupTime = methods.watch('dailyBackupTime')
  const retentionSelectOptions =
    selectedRetentionDays >= 0 && selectedRetentionDays < 3
      ? [
          {
            label: formatRetentionDays(selectedRetentionDays),
            value: selectedRetentionDays.toString(),
            disabled: true
          },
          ...retentionOptions
        ]
      : retentionOptions

  const { data: backupSetting, isFetching } = useTidbServiceGetBackupSetting(
    tidbId,
    { orgId, projectId },
    {
      query: {
        refetchOnWindowFocus: false,
        onSuccess(data) {
          methods.reset({
            mode: isEssentialV2 ? 'FLEXIBLE_RETENTION' : (data.mode ?? 'STANDARD_BUNDLE'),
            retentionDays: (data.retentionDays ?? DEFAULT_RETENTION_DAYS).toString(),
            dailyBackupTime: toLocalBackupTime(
              data.dailyBackupTimeUtc ?? DEFAULT_DAILY_BACKUP_TIME_UTC,
              tenantData.timezone
            )
          })
        }
      }
    }
  )

  const { data: backupPoliciesData, isLoading: isLoadingPolicies } = useTidbServiceListBackupPolicies(
    tidbId,
    { orgId, projectId },
    {
      query: {
        refetchOnWindowFocus: false,
        enabled: !isEssentialV2 && !!orgId && !!projectId && !!tidbId && !!backupSetting
      }
    }
  )

  const policy = backupPoliciesData?.backupPolicies?.find(
    (item) => item.name === (backupSetting?.backupPolicy ?? 'TIER1')
  )
  const standardOverview = getBackupSettingOverview({ mode: 'STANDARD_BUNDLE' }, policy)
  const isPendingFlexibleMode = pendingSetting?.mode === 'FLEXIBLE_RETENTION'
  const targetRetentionDays = pendingSetting
    ? isPendingFlexibleMode
      ? Number(pendingSetting.retentionDays)
      : standardOverview.dailyRetentionDays
    : DEFAULT_RETENTION_DAYS

  const updateBackupSetting = useTidbServiceUpdateBackupSetting()
  const currentMode = isEssentialV2 ? 'FLEXIBLE_RETENTION' : (backupSetting?.mode ?? 'STANDARD_BUNDLE')
  const toEffectiveBackupSetting = (setting: BackupSettingFormValues) => ({
    mode: setting.mode,
    retentionDays: Number(setting.retentionDays),
    dailyBackupTimeUtc: toUTCBackupTime(setting.dailyBackupTime, tenantData.timezone)
  })
  const currentSetting = {
    mode: currentMode,
    retentionDays: backupSetting?.retentionDays ?? DEFAULT_RETENTION_DAYS,
    dailyBackupTimeUtc: backupSetting?.dailyBackupTimeUtc ?? DEFAULT_DAILY_BACKUP_TIME_UTC
  }
  const hasSettingChanged = (setting: BackupSettingFormValues) => {
    if (!backupSetting) {
      return false
    }

    return hasBackupSettingChanged(currentSetting, toEffectiveBackupSetting(setting))
  }
  const selectedSetting = {
    mode: selectedMode,
    retentionDays: selectedRetentionDays.toString(),
    dailyBackupTime: selectedDailyBackupTime
  }
  const hasEffectiveChanges = hasSettingChanged(selectedSetting)
  const submitAction = backupSetting
    ? getBackupSettingSubmitAction(currentSetting, toEffectiveBackupSetting(selectedSetting), policy)
    : 'save'
  const requiresAffectedBackupsReview = hasEffectiveChanges && submitAction === 'review-affected-backups'
  const confirmationName = `${tenantData.name}/${activeProject.name}/${instanceName}`
  const isConfirmationMatched = confirmation === confirmationName
  const visibleAffectedBackups = showAllAffectedBackups
    ? affectedBackups
    : affectedBackups.slice(0, AFFECTED_BACKUP_PREVIEW_SIZE)
  const affectedBackupGridColumns = Math.min(affectedBackups.length, 3)

  const overviewData = isFlexibleMode
    ? [
        {
          feature: 'PITR',
          retention: formatRetentionDays(selectedRetentionDays),
          description: `Allows restoration to any specific moment within the ${selectedRetentionDays}-day retention period.`
        },
        {
          feature: 'Daily Backup Snapshot',
          retention: formatRetentionDays(selectedRetentionDays),
          description: `Daily snapshot(${selectedDailyBackupTime} ${getUTCString(
            tenantData.timezone
          )}) restoration available for the ${selectedRetentionDays}-day retention period.`
        }
      ]
    : [
        {
          feature: 'PITR',
          retention: formatRetentionDays(standardOverview.pitrRetentionDays),
          description: `Allows restoration to any specific moment within the ${standardOverview.pitrRetentionDays}-day retention period.`
        },
        {
          feature: 'Hourly Backup Snapshot',
          retention: formatRetentionDays(standardOverview.hourlyRetentionDays!),
          description: `Regular restoration available for each hourly snapshot for ${standardOverview.hourlyRetentionDays} days.`
        },
        {
          feature: 'Daily Backup Snapshot',
          retention: formatRetentionDays(standardOverview.dailyRetentionDays),
          description: `Daily snapshot(${standardOverview.dailyBackupTimeUtc} UTC+0) restoration available for the ${standardOverview.dailyRetentionDays}-day retention period.`
        }
      ]

  const closeSettingsFlow = () => {
    affectedBackupsRequestRef.current?.abort()
    affectedBackupsRequestRef.current = null
    setConfirmationOpened(false)
    closeModal()
  }

  const saveSetting = async (setting: BackupSettingFormValues) => {
    const effectiveSetting = toEffectiveBackupSetting(setting)
    const isFlexibleSetting = setting.mode === 'FLEXIBLE_RETENTION'

    try {
      await updateBackupSetting.mutateAsync({
        tidbId,
        data: isFlexibleSetting
          ? effectiveSetting
          : {
              mode: setting.mode,
              backupPolicy: backupSetting?.backupPolicy ?? 'TIER1'
            },
        params: { orgId, projectId }
      })
      await Promise.all([
        queryClient.invalidateQueries(getTidbServiceGetBackupSettingQueryKey(tidbId, { orgId, projectId })),
        queryClient.invalidateQueries([`/api/v2025/cloud/v1beta2/tidbs/${tidbId}/backups`])
      ])
      notifier.success('Backup settings saved successfully.')
      closeSettingsFlow()
    } catch (error) {
      notifier.error(getErrorMessage(error))
    }
  }

  const submitSetting = async (setting: BackupSettingFormValues) => {
    if (!backupSetting || !hasSettingChanged(setting)) {
      return
    }

    const effectiveSetting = toEffectiveBackupSetting(setting)
    if (getBackupSettingSubmitAction(currentSetting, effectiveSetting, policy) === 'save') {
      await saveSetting(setting)
      return
    }

    affectedBackupsRequestRef.current?.abort()
    const request = new AbortController()
    affectedBackupsRequestRef.current = request
    setIsPreparingConfirmation(true)

    try {
      const affected = await listAffectedBackups({
        tidbId,
        orgId,
        projectId,
        retentionDays: getBackupSettingOverview(effectiveSetting, policy).dailyRetentionDays,
        previewTime: new Date(),
        signal: request.signal
      })

      if (request.signal.aborted) {
        return
      }

      affectedBackupsRequestRef.current = null
      setIsPreparingConfirmation(false)

      if (affected.length === 0) {
        await saveSetting(setting)
        return
      }

      setPendingSetting(setting)
      setAffectedBackups(affected)
      setConfirmation('')
      setShowAllAffectedBackups(false)
      setConfirmationOpened(true)
    } catch (error) {
      if (request.signal.aborted) {
        return
      }

      affectedBackupsRequestRef.current = null
      setIsPreparingConfirmation(false)
      notifier.error(getErrorMessage(error))
    }
  }

  const confirmChange = async () => {
    if (pendingSetting) {
      await saveSetting(pendingSetting)
    }
  }

  return (
    <Box pos="relative" data-testid="premium-backup-settings">
      <LoadingOverlay visible={isFetching || isLoadingPolicies || isPreparingConfirmation} overlayProps={{ blur: 2 }} />
      <Form form={methods} layout="none" withActions={false} onSubmit={submitSetting}>
        <Stack gap={24}>
          {!isEssentialV2 && (
            <Stack gap={8}>
              <Typography variant="label-lg">Auto Backup Mode</Typography>
              <Group grow align="stretch" gap={16}>
                <Radio.Card
                  checked={selectedMode === 'STANDARD_BUNDLE'}
                  onClick={() => methods.setValue('mode', 'STANDARD_BUNDLE', { shouldDirty: true })}
                  p={16}
                  radius="md"
                  style={{
                    borderColor: `var(--mantine-color-${selectedMode === 'STANDARD_BUNDLE' ? 'carbon-9' : 'carbon-4'})`,
                    borderStyle: 'solid',
                    borderWidth: selectedMode === 'STANDARD_BUNDLE' ? 2 : 1
                  }}
                  data-testid="premium-backup-settings-standard-mode"
                  data-selected={selectedMode === 'STANDARD_BUNDLE'}
                  data-mp-event={MixpanelEvents.PremiumBackup.Update.ButtonClicked}
                  data-mp-backup-mode="STANDARD_BUNDLE"
                >
                  <Stack gap={12}>
                    <Group justify="space-between">
                      <Box bg="magenta.1" c="magenta.8" p={7} h={32} w={32} style={{ borderRadius: 4 }}>
                        <IconClock size={18} />
                      </Box>
                      <Radio.Indicator />
                    </Group>
                    <Stack gap={4}>
                      <Typography variant="label-lg">{modeLabels.STANDARD_BUNDLE}</Typography>
                      <Typography variant="body-lg" c="carbon.7">
                        Predefined hourly backup and daily backup and PITR
                      </Typography>
                    </Stack>
                  </Stack>
                </Radio.Card>

                <Radio.Card
                  checked={selectedMode === 'FLEXIBLE_RETENTION'}
                  onClick={() => methods.setValue('mode', 'FLEXIBLE_RETENTION', { shouldDirty: true })}
                  p={16}
                  radius="md"
                  style={{
                    borderColor: `var(--mantine-color-${
                      selectedMode === 'FLEXIBLE_RETENTION' ? 'carbon-9' : 'carbon-4'
                    })`,
                    borderStyle: 'solid',
                    borderWidth: selectedMode === 'FLEXIBLE_RETENTION' ? 2 : 1
                  }}
                  data-testid="premium-backup-settings-flexible-mode"
                  data-selected={selectedMode === 'FLEXIBLE_RETENTION'}
                  data-mp-event={MixpanelEvents.PremiumBackup.Update.ButtonClicked}
                  data-mp-backup-mode="FLEXIBLE_RETENTION"
                >
                  <Stack gap={12}>
                    <Group justify="space-between">
                      <Box bg="peacock.1" c="peacock.8" p={7} h={32} w={32} style={{ borderRadius: 4 }}>
                        <IconEdit05 size={18} />
                      </Box>
                      <Radio.Indicator />
                    </Group>
                    <Stack gap={4}>
                      <Typography variant="label-lg">{modeLabels.FLEXIBLE_RETENTION}</Typography>
                      <Typography variant="body-lg" c="carbon.7">
                        Daily backup and PITR with customizable retention
                      </Typography>
                    </Stack>
                  </Stack>
                </Radio.Card>
              </Group>
            </Stack>
          )}

          {isFlexibleMode && (
            <Group grow align="flex-start" gap={16}>
              <FormSelect
                name="retentionDays"
                label="Backup Retention"
                data={retentionSelectOptions}
                rules={{ required: 'Backup retention is required' }}
              />
              <FormSelect
                name="dailyBackupTime"
                label="Daily Backup Time"
                data={times}
                rules={{ required: 'Daily backup time is required' }}
                rightSectionPointerEvents="all"
                rightSection={
                  <Group gap={6} wrap="nowrap">
                    <Typography variant="body-md" c="carbon.8">
                      {getUTCString(tenantData.timezone)}
                    </Typography>
                    <Tooltip
                      multiline
                      w={320}
                      label={`To ensure consistency across collaborative workflows, all system data timestamps (tasks, schedules, logs, and diagnostics) are displayed in the Organization's timezone (${getUTCString(tenantData.timezone)}).`}
                    >
                      <IconInfoCircle size={16} />
                    </Tooltip>
                  </Group>
                }
                styles={{
                  root: {
                    '& [data-position="right"]': {
                      width: 'fit-content',
                      paddingLeft: 12,
                      paddingRight: 12,
                      borderLeft: '1px solid var(--mantine-color-carbon-4)'
                    }
                  }
                }}
              />
            </Group>
          )}

          <Stack gap={8}>
            <Typography variant="label-lg">Overview</Typography>
            <Box data-testid="premium-backup-settings-overview">
              <ProTable
                mantineTableProps={{ highlightOnHover: false }}
                columns={[
                  {
                    header: 'Feature',
                    Header: () => <Typography ml={8}>Feature</Typography>,
                    accessorFn: (row) => <Typography ml={8}>{row.feature}</Typography>
                  },
                  { header: 'Retention', accessorKey: 'retention', maxSize: 100 },
                  { header: 'Restore Details', accessorKey: 'description' }
                ]}
                data={overviewData}
              />
            </Box>
          </Stack>

          {!isBYOC && (
            <Alert color="peacock" icon={<IconInfoCircle size={16} />}>
              {!isFlexibleMode ? (
                <>
                  Backup price for {modeLabels.STANDARD_BUNDLE} is based on data incremental volume.{' '}
                  <Anchor href={pricingUrl} target="_blank" rel="noreferrer">
                    View Pricing
                  </Anchor>{' '}
                  for more detail.
                </>
              ) : (
                <>
                  Backup price for {modeLabels.FLEXIBLE_RETENTION} is based on Snapshot Size x Retention Duration. Each
                  backup is treated as a standalone object for billing. PITR is temporary free during public preview.{' '}
                  <Anchor href={pricingUrl} target="_blank" rel="noreferrer">
                    View Pricing
                  </Anchor>{' '}
                  for more detail.
                </>
              )}
            </Alert>
          )}

          {requiresAffectedBackupsReview && (
            <Alert
              color="yellow"
              icon={<IconAlertTriangle size={16} />}
              data-testid="premium-backup-settings-retention-warning"
            >
              Setting a shorter retention period may cause existing backups to expire immediately and be deleted. Select
              Next to check for affected backups.
            </Alert>
          )}

          <Divider mx={-24} />
          <Group justify="flex-end">
            <Button
              type="button"
              variant="default"
              onClick={closeModal}
              data-mp-event={MixpanelEvents.PremiumBackup.Update.CancelClicked}
            >
              Cancel
            </Button>
            <Button
              type="submit"
              disabled={!hasEffectiveChanges}
              loading={updateBackupSetting.isLoading || isPreparingConfirmation}
              data-testid="premium-backup-settings-save"
              data-mp-event={MixpanelEvents.PremiumBackup.Update.SubmitClicked}
            >
              {requiresAffectedBackupsReview ? 'Next' : 'Save'}
            </Button>
          </Group>
        </Stack>
      </Form>

      <Modal
        opened={confirmationOpened}
        onClose={() => setConfirmationOpened(false)}
        title="Change Confirmation"
        size="lg"
        centered
        closeOnClickOutside={false}
      >
        <Box pos="relative" data-testid="premium-backup-change-confirmation">
          <Stack gap={16}>
            <Typography>
              Under the new auto backup retention policy, existing backups older than the {targetRetentionDays}-day
              retention period will expire and be permanently deleted. This action is irreversible. Review the affected
              backups below.
            </Typography>

            <Box
              style={{
                border: '1px solid var(--mantine-color-carbon-4)',
                borderRadius: 'var(--mantine-radius-md)',
                overflow: 'hidden'
              }}
              data-testid="premium-backup-affected-list"
            >
              <Box bg="carbon.1" px={12} py={10}>
                <Typography c="carbon.7">
                  The following backups will be permanently deleted: ({affectedBackups.length})
                </Typography>
              </Box>
              <ScrollArea.Autosize mah={300} data-testid="premium-backup-affected-scroll">
                <SimpleGrid cols={affectedBackupGridColumns} spacing={0}>
                  {visibleAffectedBackups.map((backup, index) => (
                    <Box
                      key={backup.id ?? `${backup.displayName}-${index}`}
                      px={12}
                      py={10}
                      style={{
                        borderTop: '1px solid var(--mantine-color-carbon-4)',
                        borderRight: '1px solid var(--mantine-color-carbon-4)',
                        overflowWrap: 'anywhere'
                      }}
                      data-testid="premium-backup-affected-item"
                      data-backup-id={backup.id}
                    >
                      <Typography variant="body-sm">{backup.displayName ?? backup.id}</Typography>
                    </Box>
                  ))}
                </SimpleGrid>
              </ScrollArea.Autosize>
              {!showAllAffectedBackups && affectedBackups.length > AFFECTED_BACKUP_PREVIEW_SIZE && (
                <Button
                  variant="subtle"
                  fullWidth
                  onClick={() => setShowAllAffectedBackups(true)}
                  data-mp-event={MixpanelEvents.PremiumBackup.Update.ButtonClicked}
                >
                  Show More
                </Button>
              )}
            </Box>

            <Stack gap={8}>
              <Typography>
                To prevent accidental data loss, please type the instance name{' '}
                <Code fz={13} fw={600} bg="carbon.3" c="carbon.9" sx={{ whiteSpace: 'pre-wrap' }}>
                  {confirmationName}
                </Code>{' '}
                below to confirm the change:
              </Typography>
              <TextInput
                value={confirmation}
                onChange={(event) => setConfirmation(event.currentTarget.value)}
                data-testid="premium-backup-change-confirmation-input"
              />
            </Stack>

            <Divider mx={-24} />
            <Group justify="flex-end">
              <Button
                variant="default"
                onClick={closeSettingsFlow}
                disabled={updateBackupSetting.isLoading}
                data-mp-event={MixpanelEvents.PremiumBackup.Update.CancelClicked}
              >
                Cancel
              </Button>
              <Button
                variant="default"
                onClick={() => setConfirmationOpened(false)}
                disabled={updateBackupSetting.isLoading}
                data-mp-event={MixpanelEvents.PremiumBackup.Update.BackClicked}
              >
                Previous
              </Button>
              <Button
                color="red"
                variant="light"
                onClick={confirmChange}
                loading={updateBackupSetting.isLoading}
                disabled={!isConfirmationMatched}
                data-testid="premium-backup-change-confirmation-submit"
                data-mp-event={MixpanelEvents.PremiumBackup.Update.DoneClicked}
              >
                I understand, Delete and Change
              </Button>
            </Group>
          </Stack>
        </Box>
      </Modal>
    </Box>
  )
}

export const BackupSettingPremium = observer(_BackupSettingPremium)
