import React, { useState, useMemo, useEffect } from 'react'

import {
  PromDataAccessor,
  PromQueryGroup,
  TimeSeriesChart,
  TimeSeriesChartProps
} from '@diag-ui/chart'
import { SyncTooltip } from '@diag-ui/chart/src/chart/sync_tooltip'
import {
  Group,
  Grid,
  Button,
  Text,
  Collapse,
  Card,
  Loader,
  Flex
} from '@mantine/core'
import { IconZoomOut, IconZoomReset, IconZoomIn } from '@tabler/icons-react'
import dayjs from 'dayjs'
import { useParams } from 'react-router-dom'

import { grafanaQueryRange } from '~/api'
import { useCurOrg, useCurCluster } from '~/hooks/state'
import { replaceQuery } from '~/pages/reports/shared/utils/promql'

import {
  TiDBMetrics,
  TiKVMetrics,
  PDMetrics,
  MetricsProps
} from './metricsConfig'

interface MetricsChartProps {
  startTime: number
  completeTime: number
  actionComponent: string
}

const MetricsMap: Record<string, MetricsProps[]> = {
  tidb: TiDBMetrics,
  tikv: TiKVMetrics,
  pd: PDMetrics
}

const SEVEN_DAYS = 7 * 24 * 60 * 60
const ONE_HOUR = 60 * 60
const ONE_MINUTE = 60
const ZOOM_OUT_RATE = 0.5
const ZOOM_IN_RATE = 0.2
const MIN_TIME_RANGE = 5 * 60

const MetricsChart = ({
  startTime,
  completeTime,
  actionComponent
}: MetricsChartProps) => {
  const { orgId, cid } = useParams<'orgId' | 'cid'>()
  const { curOrg } = useCurOrg()
  const { curCluster } = useCurCluster()
  const { isCloud, orgType } = curOrg!

  // If completeTime is not set, use current time as endTime
  const endTime = completeTime ? completeTime : dayjs().unix()

  const {
    timeRange,
    eventAnnotations,
    isLoadingMetrics,
    exccedMaxTS,
    smallThanMinTS,
    setLoadingMetrics,
    handleZoomOut,
    handleZoomIn,
    handleReset
  } = useEventTS({ startTime: startTime, endTime: endTime })

  const metricsConfig = MetricsMap[actionComponent]

  return (
    <>
      <Flex my={40} direction='column'>
        <Group mb={10}>
          <Button
            leftIcon={<IconZoomOut size={16} />}
            onClick={handleZoomOut}
            className='font-light'
            disabled={exccedMaxTS}
          >
            Zoom Out
          </Button>

          <Button
            leftIcon={<IconZoomIn size={16} />}
            onClick={handleZoomIn}
            className='font-light'
            disabled={smallThanMinTS}
          >
            Zoom in
          </Button>

          <Button
            leftIcon={<IconZoomReset size={16} />}
            onClick={handleReset}
            className='font-light'
          >
            Reset
          </Button>
          {isLoadingMetrics && <Loader size='xs' />}
        </Group>
        {exccedMaxTS && (
          <Text c='dimmed'>Can only view recent two weeks metrics.</Text>
        )}
      </Flex>

      <PromDataAccessor
        params={{ start_time: timeRange[0], end_time: timeRange[1] }}
        fetch={(query, tp) => {
          return grafanaQueryRange({
            query: replaceQuery(query, cid!, orgType, curCluster?.deployType),
            orgId: orgId!,
            clusterId: cid!,
            start: tp.start_time,
            end: tp.end_time,
            step: tp.step.toString() + 's',
            isCloud
          }).then((res) => {
            return res.data
          })
        }}
        onBatchLoadingChange={(loading) => {
          setLoadingMetrics(loading)
        }}
      >
        <SyncTooltip>
          {metricsConfig.map((metrics, index) => (
            <Collapse key={index} in={true}>
              <Text fz='lg' mb='lg'>
                {metrics.category}
              </Text>
              <Grid justify='space-between' my='lg' gutter='xl'>
                {metrics.metrics_list.map((metric, index) => (
                  <Grid.Col span={6} key={index}>
                    <Card shadow='sm' p='lg' radius='md' withBorder>
                      <Text fz='md' mb='lg' align='center'>
                        {metric.title}
                      </Text>
                      <TimeSeriesChart
                        nullValue={metric.nullValue}
                        annotations={eventAnnotations}
                        height={300}
                      >
                        <PromQueryGroup
                          queries={metric.queries}
                          unit={metric.unit}
                        />
                      </TimeSeriesChart>
                    </Card>
                  </Grid.Col>
                ))}
              </Grid>
            </Collapse>
          ))}
        </SyncTooltip>
      </PromDataAccessor>
    </>
  )
}

interface useEventTSProps {
  startTime: number
  endTime: number
}

const useEventTS = ({ startTime, endTime }: useEventTSProps) => {
  const initialTS = useMemo(
    () => [startTime - ONE_HOUR, Math.min(dayjs().unix(), endTime + ONE_HOUR)],
    [startTime, endTime]
  )

  const maxTS = useMemo(
    () => [startTime - SEVEN_DAYS, endTime + SEVEN_DAYS],
    [startTime, endTime]
  )

  const minTS = useMemo(
    () => [startTime - MIN_TIME_RANGE, endTime + MIN_TIME_RANGE],
    [startTime, endTime]
  )

  const [timeRange, setTimeRange] = useState<number[]>(initialTS)
  const [isLoadingMetrics, setLoadingMetrics] = useState(true)

  const exccedMaxTS = useMemo(() => {
    return timeRange[0] === maxTS[0] && timeRange[1] === maxTS[1]
  }, [timeRange, maxTS])

  const smallThanMinTS = useMemo(() => {
    return timeRange[0] === minTS[0] && timeRange[1] === minTS[1]
  }, [timeRange, minTS])

  const eventAnnotations: TimeSeriesChartProps['annotations'] = useMemo(() => {
    // if start and end time is less than 2s, show a line
    if (endTime - startTime < 2) {
      return [
        {
          type: 'line',
          start: [startTime * 1000, 'min'],
          end: [startTime * 1000, 'max'],
          style: {
            lineDash: [4, 4]
          },
          top: true
        }
      ]
    }

    return [
      {
        type: 'region',
        start: [startTime * 1000, 'min'],
        end: [endTime * 1000, 'max'],
        top: true,
        style: {
          fillOpacity: 0.2
        }
      }
    ]
  }, [startTime, endTime])

  const handleZoomOut = () => {
    setLoadingMetrics(true)
    const expand = (timeRange[1] - timeRange[0]) * ZOOM_OUT_RATE
    const newStart =
      timeRange[0] - expand < maxTS[0] ? maxTS[0] : timeRange[0] - expand
    const newEnd =
      timeRange[1] + expand > maxTS[1] ? maxTS[1] : timeRange[1] + expand

    setTimeRange([newStart, Math.min(dayjs().unix(), newEnd)])
  }

  const handleZoomIn = () => {
    setLoadingMetrics(true)
    const expand = (timeRange[1] - timeRange[0]) * ZOOM_IN_RATE
    const newStart =
      timeRange[0] + expand > minTS[0] ? minTS[0] : timeRange[0] + expand
    const newEnd =
      timeRange[1] - expand < minTS[1] ? minTS[1] : timeRange[1] - expand

    setTimeRange([newStart, Math.min(dayjs().unix(), newEnd)])
  }

  const handleReset = () => {
    if (initialTS === timeRange) return
    setLoadingMetrics(true)
    setTimeRange(initialTS)
  }

  // Automatically refresh every minute
  useEffect(() => {
    let timer: any
    timer = setInterval(() => {
      setTimeRange((prev) => {
        const newStart = prev[0] + ONE_MINUTE
        const newEnd = prev[1] + ONE_MINUTE
        return [newStart, newEnd]
      })
    }, ONE_MINUTE * 1000)

    return () => {
      clearInterval(timer)
    }
  }, [timeRange])

  return {
    timeRange,
    eventAnnotations,
    isLoadingMetrics,
    exccedMaxTS,
    smallThanMinTS,
    setLoadingMetrics,
    handleZoomOut,
    handleZoomIn,
    handleReset
  }
}

export default MetricsChart
