import {
  Box,
  Button,
  Divider,
  Group,
  Input,
  Menu,
  PopoverProps,
  Stack,
  Text,
  TextInput,
  Typography,
  DatePicker as MantineDatePicker,
  Flex
} from '@tidbcloud/uikit'
import { IconChevronRight, IconChevronLeft, IconChevronSelectorVertical } from '@tidbcloud/uikit/icons'
import dayjs from 'dayjs'
import { useMemo, useRef, useState } from 'react'

import { DynamicTimeRange, TimeRange, TimeRangeValue, formatDuration, toTimeRangeValue } from './helpers'

export interface DatePickerProps {
  value: TimeRange
  onChange?: (value: TimeRange) => void
  disabled?: boolean
  popoverProps?: Omit<PopoverProps, 'children'>

  // quick range selection items, Last x mins, Last x hours...
  // unit: seconds.
  quickRanges?: number[]
}

const INPUT_FORMAT = 'YYYY-MM-DD'
const DAYJS_TODAY = dayjs()
const TODAY = DAYJS_TODAY.toDate()
const CURRENT_MONTH_DAY = DAYJS_TODAY.date()
const LAST_MONTH_DAYS = DAYJS_TODAY.subtract(1, 'month').daysInMonth()

export const DEFAULT_QUICK_RANGES = [30 * 24 * 60 * 60, 3 * 30 * 24 * 60 * 60, 6 * 30 * 24 * 60 * 60]
export const DEFAULT_CURRENT_MONTH_RANGE: DynamicTimeRange = {
  type: 'dynamic',
  label: 'Current month',
  value: () => [DAYJS_TODAY.startOf('month').unix(), DAYJS_TODAY.unix()]
}
export const DEFAULT_LAST_MONTH_RANGE: DynamicTimeRange = {
  type: 'dynamic',
  label: 'Last month',
  value: () => [
    DAYJS_TODAY.subtract(1, 'month').startOf('month').unix(),
    DAYJS_TODAY.subtract(1, 'month').endOf('month').unix()
  ]
}

const DATE_LIMIT_BY_MONTH = 6

export const DatePicker: React.FC<React.PropsWithChildren<DatePickerProps>> = ({
  value,
  onChange,
  disabled,
  popoverProps,
  quickRanges = DEFAULT_QUICK_RANGES
}) => {
  const [opened, setOpened] = useState(false)
  const [isCustomMode, setIsCustomMode] = useState(false)
  const timeRangeValue = toTimeRangeValue(value)
  const duration = timeRangeValue[1] - timeRangeValue[0]
  const formattedAbsDateTime = useMemo(() => {
    return `${dayjs.unix(timeRangeValue[0]).format('MMM DD, YYYY')} - ${dayjs
      .unix(timeRangeValue[1])
      .format('MMM DD, YYYY')}`
  }, [timeRangeValue])
  const [isError, setIsError] = useState(false)

  const dateRef = useRef(value)
  const handleDateChange = (value: TimeRange) => {
    dateRef.current = value
  }
  const apply = () => {
    const dateRanges = toTimeRangeValue(dateRef.current)
    const isDateLargeThanLimit = dayjs
      .unix(dateRanges[0])
      .add(DATE_LIMIT_BY_MONTH, 'month')
      .isBefore(dayjs.unix(dateRanges[1]))
    setIsError(false)
    if (isDateLargeThanLimit) {
      setIsError(true)
      return
    }

    onChange?.(dateRef.current)
    setOpened(false)
  }

  return (
    <Menu
      position="bottom-end"
      {...popoverProps}
      opened={opened}
      onChange={setOpened}
      shadow="sm"
      // fix flickering
      transitionProps={{ duration: 0 }}
      // mantine bug, won't update auto floating event if width="target" changed
      // https://github.com/mantinedev/mantine/blob/master/packages/%40mantine/core/src/components/Popover/use-popover.ts#L59
      width={isCustomMode ? undefined : 'target'}
    >
      <Menu.Target>
        <Input
          component="button"
          styles={{
            input: {
              userSelect: 'none',
              height: '40px',
              lineHeight: '38px',
              paddingRight: '40px',
              ':hover': {
                cursor: disabled ? 'not-allowed' : 'pointer'
              },
              '::selection': {
                backgroundColor: 'transparent'
              }
            },
            section: {
              width: '40px',
              pointerEvents: 'none'
            }
          }}
          sx={{
            lineHeight: '1.55'
          }}
          disabled={disabled}
          rightSection={<IconChevronSelectorVertical size={16} color="var(--mantine-color-carbon-7)!important" />}
          onClick={() => setOpened((v) => !v)}
        >
          <Group w="100%" gap={0}>
            <DurationBadge>{formatDuration(duration, true)}</DurationBadge>
            <Text className="wd-flex-1 wd-truncate wd-px-2">
              {value.type === 'dynamic'
                ? value.label
                : value.type === 'relative'
                  ? `Past ${formatDuration(duration)}`
                  : formattedAbsDateTime}
            </Text>
          </Group>
        </Input>
      </Menu.Target>
      <Menu.Dropdown>
        {isCustomMode ? (
          <Stack gap={0} m={-4}>
            <Group>
              <Group gap={8} p="md" sx={{ cursor: 'pointer' }} onClick={() => setIsCustomMode(false)}>
                <IconChevronLeft size={16} />
                <Text>Back</Text>
              </Group>
            </Group>
            <Divider color="carbon.4" />
            <DatePanel value={value} onChange={handleDateChange} />
            <Divider color="carbon.4" />
            <Group p="md" justify="space-between">
              <Box>
                {isError && (
                  <Text fz="sm" c="red.7">
                    The selected time range cannot exceed 6 months.
                  </Text>
                )}
              </Box>
              <Group gap={8}>
                <Button variant="default" size="xs" onClick={() => setOpened(false)}>
                  Cancel
                </Button>
                <Button size="xs" onClick={apply}>
                  Apply
                </Button>
              </Group>
            </Group>
          </Stack>
        ) : (
          <QuickDateItems
            quickRanges={quickRanges}
            value={value}
            onChange={(v) => {
              handleDateChange(v)
              apply()
            }}
            onClickCustomItem={() => {
              setIsCustomMode(true)
              // mantine bug, won't update auto floating event if width="target" changed
              // https://github.com/mantinedev/mantine/blob/master/packages/%40mantine/core/src/components/Popover/use-popover.ts#L59
              setTimeout(() => {
                setOpened(true)
              })
            }}
          />
        )}
      </Menu.Dropdown>
    </Menu>
  )
}

const QuickDateItems: React.FC<
  React.PropsWithChildren<{
    value: TimeRange
    onChange: (value: TimeRange) => void
    quickRanges: number[]
    onClickCustomItem: () => void
  }>
> = ({ value, onChange, quickRanges, onClickCustomItem }) => {
  const isAbsoluteRange = value.type === 'absolute'
  const isDynamicRange = value.type === 'dynamic'
  const selectedRelativeItem = useMemo(() => {
    if (value.type !== 'relative') {
      return
    }
    return quickRanges.find((it) => it === value.value)
  }, [quickRanges, value])

  return (
    <>
      <Menu.Item
        onClick={onClickCustomItem}
        sx={(theme) => ({
          background: isAbsoluteRange ? theme.colors.carbon[3] : ''
        })}
      >
        <Group justify="space-between">
          <Group gap={8}>
            <DurationBadge>
              {!isAbsoluteRange ? '- -' : formatDuration(value.value[1] - value.value[0], true)}
            </DurationBadge>
            <Text>Custom range</Text>
          </Group>
          <IconChevronRight size={16} />
        </Group>
      </Menu.Item>
      <Menu.Divider color="carbon.4" />
      <Menu.Item
        sx={(theme) => ({
          background: isDynamicRange && value.label === DEFAULT_CURRENT_MONTH_RANGE.label ? theme.colors.carbon[3] : ''
        })}
        onClick={() => onChange(DEFAULT_CURRENT_MONTH_RANGE)}
      >
        <Group gap={8}>
          <DurationBadge>{CURRENT_MONTH_DAY}d</DurationBadge>
          <Text>Current month</Text>
        </Group>
      </Menu.Item>
      <Menu.Item
        sx={(theme) => ({
          background: isDynamicRange && value.label === DEFAULT_LAST_MONTH_RANGE.label ? theme.colors.carbon[3] : ''
        })}
        onClick={() => onChange(DEFAULT_LAST_MONTH_RANGE)}
      >
        <Group gap={8}>
          <DurationBadge>{LAST_MONTH_DAYS}d</DurationBadge>
          <Text>Last month</Text>
        </Group>
      </Menu.Item>
      <Menu.Divider color="carbon.4" />
      {quickRanges.map((t) => (
        <Menu.Item
          key={t}
          sx={(theme) => ({
            background: t === selectedRelativeItem ? theme.colors.carbon[3] : ''
          })}
          onClick={() => onChange({ type: 'relative', value: t })}
        >
          <Group gap={8}>
            <DurationBadge>{formatDuration(t, true)}</DurationBadge>
            <Text>Past {formatDuration(t)}</Text>
          </Group>
        </Menu.Item>
      ))}
    </>
  )
}

const DatePanel: React.FC<
  React.PropsWithChildren<{
    value: TimeRange
    onChange: (value: TimeRange) => void
  }>
> = ({ value, onChange }) => {
  const timeRangeValue = toTimeRangeValue(value)
  const [start, setStart] = useState(() => new Date(timeRangeValue[0] * 1000))
  const [end, setEnd] = useState(() => new Date(timeRangeValue[1] * 1000))
  const [startInput, setStartInput] = useState(dayjs(start).startOf('day').format(INPUT_FORMAT))
  const [endInput, setEndInput] = useState(dayjs(end).endOf('day').format(INPUT_FORMAT))
  const [dateRangeDisplayValue, setDateRangeDisplayValue] = useState<[Date | null, Date | null]>([start, end])

  const updateDates = (timeRange: TimeRange) => {
    const trs = toTimeRangeValue(timeRange)
    const dates = trs.map((t) => dayjs.unix(t).toDate())

    setStart(dates[0])
    setEnd(dates[1])
    setStartInput(dayjs(dates[0]).format(INPUT_FORMAT))
    setEndInput(dayjs(dates[1]).format(INPUT_FORMAT))
    onChange(timeRange)
  }
  const updateStart = (date: Date) => {
    const newStart = dayjs(date).startOf('day')
    let newEnd = dayjs(end).endOf('day')
    if (newStart.isAfter(end)) {
      newEnd = dayjs(date).endOf('day')
    }
    setDateRangeDisplayValue([newStart.toDate(), newEnd.toDate()])
    updateDates({ type: 'absolute', value: [newStart, newEnd].map((d) => d.unix()) as TimeRangeValue })
  }
  const updateEnd = (date: Date) => {
    const newEnd = dayjs(date).endOf('day')
    let newStart = dayjs(start).startOf('day')
    if (newEnd.isBefore(start)) {
      newStart = dayjs(date).startOf('day')
    }
    setDateRangeDisplayValue([newStart.toDate(), newEnd.toDate()])
    updateDates({ type: 'absolute', value: [newStart, newEnd].map((d) => d.unix()) as TimeRangeValue })
  }

  return (
    <Group gap={0} align="flex-start">
      <Stack p="md">
        <Flex align="center" gap={8}>
          <Typography variant="body-lg" w={30}>
            Start
          </Typography>
          <TextInput
            size="sm"
            w={114}
            value={startInput}
            onChange={(e) => {
              setStartInput(e.currentTarget.value)
            }}
            onBlur={() => {
              const startInputDate = dayjs(startInput)
              if (!startInputDate.isValid() || startInputDate.isAfter(TODAY)) {
                setStartInput(dayjs(start).startOf('day').format(INPUT_FORMAT))
                return
              }

              updateStart(startInputDate.toDate())
            }}
          />
        </Flex>
        <Flex align="center" gap={8}>
          <Typography variant="body-lg" w={30}>
            End
          </Typography>
          <TextInput
            size="sm"
            w={114}
            value={endInput}
            onChange={(e) => {
              setEndInput(e.currentTarget.value)
            }}
            onBlur={() => {
              const endInputDate = dayjs(endInput)
              if (!endInputDate.isValid() || endInputDate.isAfter(TODAY)) {
                setEndInput(dayjs(end).endOf('day').format(INPUT_FORMAT))
                return
              }

              updateEnd(endInputDate.toDate())
            }}
          />
        </Flex>
        <MantineDatePicker
          type="range"
          defaultDate={start}
          value={dateRangeDisplayValue}
          onChange={(d) => {
            setDateRangeDisplayValue(d)

            if (!!d[1]) {
              updateDates({ type: 'absolute', value: d.map((d) => dayjs(d).unix()) as TimeRangeValue })
            }
          }}
          maxDate={TODAY}
        />
      </Stack>
    </Group>
  )
}

const DurationBadge: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
  return (
    <Box
      display="inline-block"
      w={35}
      py={3}
      bg="carbon.5"
      c="carbon.8"
      fz={10}
      lh="14px"
      ta="center"
      className="wd-rounded-lg"
    >
      {children}
    </Box>
  )
}
