import TimeRangePicker from '@tidb-cloud-uikit/components/TimeRangePicker'
import { Alert, Box, Card, Group, SegmentedControl, Select, Stack, Typography } from '@tidbcloud/uikit'
import { fromTimeRangeValue, toTimeRangeValue } from '@tidbcloud/uikit/biz'
import { CodeBlock, LabelTooltip } from '@tidbcloud/uikit/biz'
import dayjs from 'dayjs'
import React, { useMemo, useState } from 'react'
import { useQuery } from 'react-query'

import { HeroComponent } from 'dbaas/components/Measurement'
import MetricsPanel from 'dbaas/components/MetricsPanel'
import { eventTracking } from 'dbaas/utils/tracking'

import { Statistic } from './components/Statistic'
import { metricsConfig } from './metrics-config'
import { useCalibrateResourceContext } from './utils/context'
import { FEATURE_NAME, QUICK_RANGES, TABS, WORKLOAD_SELECT_OPTIONS } from './utils/helpers'
import { useCalibrateResourceState } from './utils/url-state'

const TimeRangeSelect: React.FC<React.PropsWithChildren<unknown>> = () => {
  const { timeRange, setTimeRange } = useCalibrateResourceState()

  return (
    <TimeRangePicker
      value={timeRange}
      onChange={(v) => {
        eventTracking(`${FEATURE_NAME} Time Range Filter Clicked`)
        setTimeRange(v)
      }}
      quickRanges={QUICK_RANGES}
      minDateTime={() => dayjs().subtract(1, 'day').toDate()}
      maxDateTime={() => dayjs().toDate()}
    />
  )
}

const EstimateCapacityNotice: React.FC<React.PropsWithChildren<unknown>> = () => {
  return (
    <Alert color="peacock">
      Request Unit (RU) is a unified abstraction unit in TiDB for system resources, which is relevant to resource
      consumption.
      <br />
      Please notice the "estimated capacity" refers to a result that is hardware specs or past statistics, and may
      deviate from actual capacity.
      <br />
      <br />
      <details>
        <summary style={{ cursor: 'pointer' }}>Change the Resource Allocation</summary>
        <Typography sx={{ lineHeight: 2 }}>To change the resource allocation for resource group:</Typography>
        <CodeBlock
          my={8}
          language="sql"
        >{`ALTER RESOURCE GROUP <resource group name> RU_PER_SEC=<#ru> [BURSTABLE];`}</CodeBlock>
        <Typography sx={{ lineHeight: 2 }}>
          For detail information, please refer to{' '}
          <a href={`https://docs.pingcap.com/tidb/dev/tidb-resource-control`} target="_blank" rel="noreferrer">
            user manual
          </a>
          .
        </Typography>
      </details>
    </Alert>
  )
}

const CapacityWarning: React.FC<React.PropsWithChildren<{ totalRU?: number; estimatedRU?: number }>> = ({
  totalRU,
  estimatedRU
}) => {
  if (totalRU === undefined || estimatedRU === undefined) {
    return null
  }
  if (totalRU > estimatedRU) {
    return (
      <Alert color="yellow">
        The total RU of all customized resource groups exceeds the "estimated capacity". The RU allocated to some
        resource groups could not be satisfied.
      </Alert>
    )
  }
  return null
}

const useTotalRU = () => {
  const ctx = useCalibrateResourceContext()
  const { data: groups, isLoading } = useQuery(
    ['calibrate_resource', 'resource_groups', ctx.cfg.clusterId],
    () => ctx.ds.getResourceGroupsInfo(),
    {
      refetchOnWindowFocus: false
    }
  )

  const totalRU = useMemo(() => {
    if (!groups) {
      return undefined
    }
    return groups
      .filter((item) => item.name !== 'default')
      .reduce((acc, cur) => {
        const ru = Number(cur.ru_per_sec)
        if (!isNaN(ru)) {
          return acc + ru
        }
        return acc
      }, 0)
  }, [groups])

  return { totalRU, isLoading }
}

const HardwareTab: React.FC<React.PropsWithChildren<unknown>> = () => {
  const { workloadType, setWorkloadType } = useCalibrateResourceState()
  const ctx = useCalibrateResourceContext()
  const {
    data,
    error,
    isLoading: loadingEstimate
  } = useQuery(
    ['calibrate_resource', 'hardware', workloadType, ctx.cfg.clusterId],
    () => ctx.ds.getCalibrateByHardware({ workloadType }),
    {
      refetchOnWindowFocus: false,
      retry: false
      // ctx.ds.getCalibrateByHardware() is not a simply API call function, it contains some complex computation logic,
      // although the API call success, the computation failure will cause useQuery to retry, to call API again,
      // that not we want.
      // The better way is extracting the computation logic out of ctx.ds.getCalibrateByHardware(), or use `useEffect` replace `useQuery`,
      // but currently the simplest way is to set retry to false,
      // we may refine it later.
    }
  )

  const { totalRU, isLoading: loadingTotalRU } = useTotalRU()

  const workloadTypeSelect = (
    <Select
      w={200}
      data={WORKLOAD_SELECT_OPTIONS}
      value={workloadType}
      onChange={(v) => {
        eventTracking(`${FEATURE_NAME} Workload Type Filter Clicked`)
        setWorkloadType(v ?? '')
      }}
    />
  )

  return (
    <Stack>
      <Group gap={4}>
        {workloadTypeSelect}
        <Box pt={4}>
          <LabelTooltip
            label={
              <Typography>
                Select a workload type which is similar with your actual workload.
                <br />
                <br />
                - oltp_read_write: applies to workloads with even data read and write. It is estimated based on a
                workload model similar to sysbench oltp_read_write
                <br />
                <br />
                - oltp_read_only: applies to workloads with heavy data read. It is estimated based on a workload model
                similar to sysbench oltp_read_only
                <br />
                <br />
                - oltp_write_only: applies to workloads with heavy data write. It is estimated based on a workload model
                similar to sysbench oltp_write_only
                <br />
                <br />- tpcc: applies to workloads with heavy data write. It is estimated based on a workload model
                similar to TPC-C
              </Typography>
            }
          />
        </Box>
      </Group>
      <Group>
        <Card miw="20%">
          <Statistic
            label="Estimated Capacity"
            value={data?.estimated_capacity}
            unit="RUs/sec"
            loading={loadingEstimate}
          />
        </Card>
        <Card miw="20%">
          <Statistic label="Total RU of user resource groups" value={totalRU} loading={loadingTotalRU} />
        </Card>
      </Group>
      <CapacityWarning totalRU={totalRU} estimatedRU={data?.estimated_capacity} />
      {!!error && (
        <Alert color="red">
          <span dangerouslySetInnerHTML={{ __html: (error as Error).message }} />
        </Alert>
      )}
    </Stack>
  )
}

const WorkloadTab: React.FC<React.PropsWithChildren<unknown>> = () => {
  const { timeRange } = useCalibrateResourceState()
  const timeRangeValue = useMemo(() => toTimeRangeValue(timeRange), [timeRange])
  const ctx = useCalibrateResourceContext()
  const {
    data,
    error,
    isLoading: loadingEstimate
  } = useQuery(
    ['calibrate_resource', 'workload', timeRangeValue[0], timeRangeValue[1], ctx.cfg.clusterId],
    () => ctx.ds.getCalibrateByActual({ startTime: timeRangeValue[0], endTime: timeRangeValue[1] }),
    {
      refetchOnWindowFocus: false,
      retry: false
      // ctx.ds.getCalibrateByActual() is not a simply API call function, it contains some complex computation logic,
      // although the API call success, the computation failure will cause useQuery to retry, to call API again,
      // that not we want.
      // The better way is extracting the computation logic out of ctx.ds.getCalibrateByActual(), or use `useEffect` replace `useQuery`,
      // but currently the simplest way is to set retry to false,
      // we may refine it later.
    }
  )

  const { totalRU, isLoading: loadingTotalRU } = useTotalRU()

  return (
    <Stack>
      <Group gap={4}>
        <TimeRangeSelect />
        <Box pt={4}>
          <LabelTooltip
            label={
              <Typography>
                Select the time window with classic workload in the past, with which TiDB can come a better estimation
                of RU capacity.
                <br />
                <br />
                Time window length: 10 mins ~ 24 hours
              </Typography>
            }
          />
        </Box>
      </Group>
      <Group>
        <Card miw="20%">
          <Statistic
            label="Estimated Capacity"
            value={data?.estimated_capacity}
            unit="RUs/sec"
            loading={loadingEstimate}
          />
        </Card>
        <Card miw="20%">
          <Statistic label="Total RU of user resource groups" value={totalRU} loading={loadingTotalRU} />
        </Card>
      </Group>
      <CapacityWarning totalRU={totalRU} estimatedRU={data?.estimated_capacity} />
      {!!error && (
        <Alert color="red">
          <span dangerouslySetInnerHTML={{ __html: (error as Error).message }} />
        </Alert>
      )}
    </Stack>
  )
}

const Page: React.FC<React.PropsWithChildren<unknown>> = () => {
  const { by, setBy, timeRange, setTimeRange } = useCalibrateResourceState()
  const timeRangeValue = useMemo(() => toTimeRangeValue(timeRange), [timeRange])
  const ctx = useCalibrateResourceContext()
  const [loaded, setLoaded] = useState(false)

  return (
    <Stack gap={36}>
      <Stack>
        <Typography variant="title-md">Estimate Capacity</Typography>
        <EstimateCapacityNotice />
        <SegmentedControl w={400} data={TABS} value={by} onChange={setBy} />
        {by === 'hardware' ? <HardwareTab /> : <WorkloadTab />}
      </Stack>
      <Stack>
        <Typography variant="title-md">Metrics</Typography>
        <TimeRangeSelect />
        <MetricsPanel
          metricSourceURI={ctx.cfg.metricsSourceURI}
          metrics={metricsConfig}
          timeRangeValue={timeRangeValue}
          metricRequestOptions={ctx.cfg.metricRequestOptions}
          onTimeRangeChange={(v) => setTimeRange(fromTimeRangeValue(v))}
          onLoading={(loading) => {
            if (!loading) {
              setLoaded(true)
            }
          }}
        />
      </Stack>
      {loaded && <HeroComponent />}
    </Stack>
  )
}

export default Page
