import {
  Box,
  Button,
  Card,
  Divider,
  Group,
  Loader,
  LoadingOverlay,
  notifier,
  SegmentedControl,
  Stack,
  Typography
} from '@tidbcloud/uikit'
import { FormErrorMessage, FormSelect, FormTextInput } from '@tidbcloud/uikit/biz'
import { useMemoizedFn } from 'ahooks'
import { omit } from 'lodash-es'
import { observer } from 'mobx-react'
import { type ReactNode, useMemo, useState } from 'react'
import { Controller, FormProvider, useForm } from 'react-hook-form'
import { useHistory } from 'react-router-dom'

import { TableSelector } from 'dbaas/components/TableSelector'
import { AccessGuard } from 'dbaas/contexts/access-control'
import { getSharedCluster, SharedCluster } from 'dbaas/features/ClusterManagement/cluster'
import { CloudProviderConst } from 'dbaas/features/Project'
import { ProjectPageShell } from 'dbaas/screens/ConsoleV2/components'
import { useClusterServiceListClusters } from 'dbaas/services/api-clients/v2023/cluster-management/v1beta1'
import { Commonv1beta1ClusterState } from 'dbaas/services/api-clients/v2023/cluster-management/v1beta1/interface'
import { useCalRecoveryGroupFee, useCreateRecoveryGroup, useListRecoveryGroups } from 'dbaas/services/dataflow'
import { type V1beta1CreateRecoveryGroupRequest, V1beta1ResiliencyPolicyType } from 'dbaas/services/dataflow/interface'
import { getErrorMessage } from 'dbaas/services/errorCodes'
import useStores from 'dbaas/stores/useStores'
import { recoveryGroupNameRules } from 'dbaas/utils/validationRules'

import { ROOT_ROUTE } from '../helper'

import { ClusterSelectItem } from './ClusterSelectItem'

interface CreateRecoveryGroupFormData {
  displayName: string
  resiliencyPolicy: V1beta1ResiliencyPolicyType
  primary: string
  secondary: string
  tableFiltersValue: string
}

export const CreateRecoveryGroup = observer(() => {
  const history = useHistory()
  const {
    store: { tenantData, activeProjectId }
  } = useStores()
  const create = useCreateRecoveryGroup()
  const goBack = useMemoizedFn(() => {
    history.push(ROOT_ROUTE)
  })
  const methods = useForm<CreateRecoveryGroupFormData>({
    defaultValues: {
      displayName: '',
      resiliencyPolicy: V1beta1ResiliencyPolicyType.WITHOUT_CONSISTENCY_GUARANTEE,
      primary: '',
      secondary: '',
      tableFiltersValue: JSON.stringify([])
    },
    mode: 'onChange'
  })
  const [assignedDatabaseMethod, setAssignedDatabaseMethod] = useState<'All' | 'Customize'>('All')
  const { setValue, watch, handleSubmit } = methods
  const displayName: string = watch('displayName')
  const primaryClusterId: string = watch('primary')
  const secondaryClusterId: string = watch('secondary')
  const tableFiltersValue: string[] = JSON.parse(watch('tableFiltersValue'))

  const { data: clustersData, isLoading: isLoadingClusters } = useClusterServiceListClusters(
    {
      orgId: tenantData.id,
      projectId: activeProjectId,
      clusterType: 'DEDICATED',
      pageSize: 100
    },
    {
      query: {
        refetchOnWindowFocus: false
      }
    }
  )

  useListRecoveryGroups(
    { org_id: tenantData.id, project_id: activeProjectId },
    {
      query: {
        enabled: !!tenantData.id && !!activeProjectId,
        refetchOnWindowFocus: false,
        onSuccess(data) {
          if (!displayName) {
            setValue('displayName', `recovery-group-${data.recovery_groups?.length ?? 0}`)
          }
        }
      }
    }
  )

  const [primaryClusterOptions, secondaryClusterOptions] = useMemo(() => {
    if (!clustersData) return [[], []]
    const mapper = (i: SharedCluster) => ({
      label: `${i.displayName} (${i.provider}, ${i.regionDisplayName})`,
      name: i.displayName,
      provider: i.provider,
      region: i.region,
      value: i.id,
      status: i.state,
      disabled: i.state !== Commonv1beta1ClusterState.ACTIVE
    })
    const primaryOptions = (clustersData.clusters || [])
      ?.map((c) => getSharedCluster(c))
      .filter((i) => i?.provider === CloudProviderConst.AWS)
      .map(mapper)
    const secondaryOptions = primaryOptions.filter((i) => i.value !== primaryClusterId)
    return [primaryOptions, secondaryOptions]
  }, [clustersData, primaryClusterId])

  const onSelect = useMemoizedFn((nodes: { schema: string; table: string }[]): void => {
    setValue('tableFiltersValue', JSON.stringify(nodes.map((i) => `${i.schema}.*`)))
  })

  const onSubmit = useMemoizedFn(async (data: CreateRecoveryGroupFormData) => {
    if (assignedDatabaseMethod === 'Customize' && !tableFiltersValue.length) {
      notifier.error('You need to select assigned databases first')
      return
    }

    const body: V1beta1CreateRecoveryGroupRequest = {
      setting: {
        ...omit(data, ['secondary', 'tableFiltersValue']),
        secondaries: [data.secondary],
        table_filters: assignedDatabaseMethod === 'All' ? ['*.*'] : tableFiltersValue
      },
      labels: {
        org_id: tenantData.id,
        project_id: activeProjectId
      }
    }

    await create.mutateAsync({ data: body })
    notifier.success('Creating recovery group...')
    history.push(ROOT_ROUTE)
  })

  return (
    <FormProvider {...methods}>
      <ProjectPageShell
        title="Create Recovery Group"
        headerProps={{
          withBack: true,
          onBackClick: goBack
        }}
      >
        <Box
          component="form"
          onSubmit={handleSubmit(onSubmit)}
          sx={{
            display: 'flex',
            height: '100%',
            gap: 24,
            justifyContent: 'center'
          }}
        >
          {/* left container */}
          <Box w={720}>
            <Card p={0}>
              <Stack p={24}>
                <FormItemTitle title="Name your recovery group" />
                <FormTextInput
                  name="displayName"
                  placeholder="Your recovery group name"
                  rules={recoveryGroupNameRules}
                />
              </Stack>

              <Divider />

              <Stack p={24}>
                <FormItemTitle
                  title="Select resiliency level"
                  desc="Currently, only one level is supported and is preselected"
                />

                <FormSelect
                  name="resiliencyPolicy"
                  description="No Redo Log, Target Recovery Point Objective is < 1 minute, Target Recovery Time Objective < 10 minutes"
                  inputWrapperOrder={['input', 'description']}
                  data={[
                    {
                      label: 'No consistency guarantee',
                      value: V1beta1ResiliencyPolicyType.WITHOUT_CONSISTENCY_GUARANTEE
                    }
                  ]}
                />
              </Stack>

              <Divider />

              <Stack p={24}>
                <FormItemTitle title="Choose clusters" desc="Clusters may be located in different regions" />
                <FormSelect
                  name="primary"
                  label="Primary cluster"
                  data={primaryClusterOptions}
                  renderOption={(item: { option: (typeof primaryClusterOptions)[number] }) => (
                    <ClusterSelectItem {...item.option} />
                  )}
                  leftSection={isLoadingClusters ? <Loader size="xs" /> : null}
                  disabled={isLoadingClusters}
                  placeholder="Select your primary cluster"
                  rules={{ required: 'Required' }}
                  onChange={(val) => {
                    if (val === secondaryClusterId) {
                      setValue('secondary', '')
                    }
                  }}
                />
                <FormSelect
                  name="secondary"
                  label="Secondary cluster"
                  data={secondaryClusterOptions}
                  renderOption={(item: { option: (typeof secondaryClusterOptions)[number] }) => (
                    <ClusterSelectItem {...item.option} />
                  )}
                  leftSection={isLoadingClusters ? <Loader size="xs" /> : null}
                  disabled={isLoadingClusters}
                  placeholder="Select your secondary cluster"
                  rules={{ required: 'Required' }}
                  onChange={(val) => {
                    if (val === primaryClusterId) {
                      setValue('primary', '')
                    }
                  }}
                />
              </Stack>

              <Divider />

              <Stack p={24} mb={24}>
                <div>
                  <FormItemTitle
                    title="Replicated databases"
                    desc="You can select multiple databases from the primary cluster to assign to recovery group."
                  />
                  <Controller name="tableFiltersValue" render={({ field }) => <input {...field} hidden />} />
                </div>
                <div>
                  <SegmentedControl
                    data={[
                      {
                        value: 'All',
                        label: 'Assign all databases (current and future)'
                      },
                      { value: 'Customize', label: 'Assign specific databases' }
                    ]}
                    value={assignedDatabaseMethod}
                    onChange={(val) => setAssignedDatabaseMethod(val as any)}
                    w="100%"
                  />
                </div>
                {assignedDatabaseMethod === 'Customize' && (
                  <TableSelector
                    clusterId={primaryClusterId}
                    searchPlaceholder="Search for databases"
                    label="Databases will be assigned"
                    emptyMessage={primaryClusterId ? '' : 'Please select a primary cluster first'}
                    onSelect={onSelect}
                    tableCheckable={false}
                    showEmptyDatabase
                    showDatabaseOnly
                  />
                )}
              </Stack>
            </Card>
          </Box>

          {/* right container */}
          <Box w={408} miw={408}>
            <Card p={24}>
              <Stack>
                <Stack gap={0}>
                  <Typography variant="title-lg">Summary</Typography>
                  <Typography variant="body-md">Your new recovery group summary</Typography>
                </Stack>

                <Stack gap={8}>
                  <SummaryProperty name="Name" value={displayName || '-'} />
                  <SummaryProperty name="Resiliency Policy" value="No consistency guarantee" />
                  <SummaryProperty
                    name="Primary Cluster"
                    value={
                      clustersData?.clusters?.find((i) => i.dedicatedCluster?.clusterId === primaryClusterId)
                        ?.dedicatedCluster?.displayName ?? '-'
                    }
                  />
                  <SummaryProperty
                    name="Secondary Cluster"
                    value={
                      clustersData?.clusters?.find((i) => i.dedicatedCluster?.clusterId === secondaryClusterId)
                        ?.dedicatedCluster?.displayName ?? '-'
                    }
                  />
                  <SummaryProperty
                    name="Assigned Databases"
                    value={
                      assignedDatabaseMethod === 'All'
                        ? 'All'
                        : tableFiltersValue.length
                          ? `${tableFiltersValue.length} database${tableFiltersValue.length === 1 ? '' : 's'}`
                          : '-'
                    }
                  />
                </Stack>
              </Stack>

              <Divider my={24} />

              <PricingDetail
                orgId={tenantData.id}
                projectId={activeProjectId}
                primaryClusterId={primaryClusterId}
                secondaryClusterIds={[secondaryClusterId].filter(Boolean)}
              />

              <Divider my={24} />

              <FormErrorMessage message={getErrorMessage(create.error)} mt={24} />

              <Group justify="flex-end">
                <Button variant="default" onClick={goBack} data-mp-event="Create Recovery Group Cancel Clicked">
                  Cancel
                </Button>
                <AccessGuard id="RecoveryGroupWrite">
                  <Button
                    type="submit"
                    loading={create.isLoading}
                    data-mp-event="Create Recovery Group Submit Clicked"
                    sx={{ flexGrow: 1 }}
                  >
                    Create
                  </Button>
                </AccessGuard>
              </Group>
            </Card>
          </Box>
        </Box>
      </ProjectPageShell>
    </FormProvider>
  )
})

function FormItemTitle({ title, desc, loading }: { title: string; desc?: ReactNode; loading?: boolean }) {
  return (
    <Stack gap={0}>
      <Group gap={8}>
        <Typography variant="title-lg">{title}</Typography>
        {loading && <Loader size="xs" />}
      </Group>
      {desc && <Typography variant="body-lg">{desc}</Typography>}
    </Stack>
  )
}

function PricingDetail({
  orgId,
  projectId,
  primaryClusterId,
  secondaryClusterIds
}: {
  orgId: string
  projectId: string
  primaryClusterId: string
  secondaryClusterIds: string[]
}) {
  const { data: feeData, isLoading } = useCalRecoveryGroupFee(
    {
      org_id: orgId,
      project_id: projectId,
      primary_id: primaryClusterId,
      secondary_ids: JSON.stringify(secondaryClusterIds)
    },
    {
      query: {
        enabled: !!primaryClusterId && !!secondaryClusterIds.length,
        refetchOnWindowFocus: false
      }
    }
  )

  const PricingItems = [
    {
      price: feeData?.access_fee?.toFixed(2) ?? '0.00',
      label: '/hour for recovery group service'
    },
    {
      price: feeData?.data_processing_fee?.toFixed(2) ?? '0.00',
      label: '/GB for data processing'
    }
  ]

  return (
    <Stack gap={8}>
      <LoadingOverlay visible={isLoading} />

      <Stack gap={0}>
        {PricingItems.map((i) => (
          <Box key={i.label}>
            <Typography span c="carbon.9" fw={700} fz={20}>
              ${i.price}
            </Typography>
            <Typography span variant="body-lg">
              {i.label}
            </Typography>
          </Box>
        ))}
      </Stack>

      <Typography variant="body-lg">
        You will be billed on a combination of hourly service and per GB of data processing. Learn more in{' '}
        <a
          href="https://www.pingcap.com/tidb-dedicated-pricing-details/#recovery-group-cost"
          target="_blank"
          rel="noreferrer"
        >
          pricing details
        </a>
        .
      </Typography>
    </Stack>
  )
}

function SummaryProperty({ name, value }: { name: string; value: string }) {
  return (
    <Group gap={24} align="flex-start">
      <Typography variant="label-lg" miw={150}>
        {name}
      </Typography>
      <Typography variant="body-md" maw={162} truncate title={value}>
        {value}
      </Typography>
    </Group>
  )
}
