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

import useUrlState from '@ahooksjs/use-url-state'
import { getValueFormat } from '@baurine/grafana-value-formats'
import {
  Alert,
  Button,
  Card,
  Group,
  Select,
  Skeleton,
  Stack,
  Table,
  Text
} from '@mantine/core'
import { IconAlertCircle } from '@tabler/icons-react'
import { DatePicker, message } from 'antd'
import { AxiosError } from 'axios'
import moment from 'moment-timezone'

import {
  generatePlanReplayerFileDownloadUrl,
  listClusterFiles,
  listPlanReplayerFiles,
  PlanReplayerFile
} from '~/api'
import { useCurCluster, useCurOrg } from '~/hooks/state'
import { useMutation } from '~/hooks/use-mutation'
import { clusterFilesTelemetry } from '~/telemetry/cluster-files'
import { formatDateTimeWithTz } from '~/utils/date-time'

const dedicatedDefaultQueryParams = {
  fileType: 'oom_record',
  beginTime: 'now-24h',
  endTime: 'now',
  notEmpty: 'false'
}

const PLAN_REPLAYER_FILE_TYPE = {
  value: 'plan_replayer',
  label: 'Plan Replayer'
}

const timeRangeOptions = [
  { value: 'now-24h', label: 'Recent 24 hours' },
  { value: 'now-2d', label: 'Recent 2 days' },
  { value: 'now-7d', label: 'Recent 7 days' },
  { value: 'absolute', label: 'Absolute' }
]

const dedicatedFileTypes: { value: string; label: string }[] = [
  { value: 'oom_record', label: 'OOM Files' },
  PLAN_REPLAYER_FILE_TYPE
]

const planReplayerDefaultQueryParams = {
  fileType: PLAN_REPLAYER_FILE_TYPE.value
}

type PlanReplayerQueryParams = {
  fileType: string
  beginTime?: string
  endTime?: string
  notEmpty?: string
}

const MAX_VISIBLE_FILES = 100
const PLAN_REPLAYER_ACCESS_ERROR =
  'Unable to access Plan Replayer files. The cluster owner must authorize temporary Plan Replayer Support Access in TiDB Cloud, or you may not have permission to this cluster.'

function isForbiddenError(error: unknown) {
  return (error as AxiosError).response?.status === 403
}

function getHttpsUrl(value: unknown) {
  if (typeof value !== 'string' || value === '') {
    return undefined
  }

  try {
    const url = new URL(value)
    return url.protocol === 'https:' ? url.href : undefined
  } catch {
    return undefined
  }
}

const DedicatedFilesPage: React.FC = () => {
  const { curOrg } = useCurOrg()
  const { curCluster } = useCurCluster()

  const [queryParams, setQueryParams] = useUrlState(dedicatedDefaultQueryParams)
  const { beginTime, endTime } = queryParams
  const timeRangeKey = useMemo(() => {
    if (endTime === 'now' && beginTime.startsWith('now')) {
      return beginTime
    }
    // Number('now') = NaN
    // Number('') = 0
    if (Number(beginTime) >= 0 && Number(endTime) >= 0) {
      return 'absolute'
    }
    return 'now-24h'
  }, [beginTime, endTime])

  const defTimeRange = useMemo<[moment.Moment, moment.Moment] | null>(() => {
    if (timeRangeKey === 'absolute' && beginTime !== '' && endTime !== '') {
      return [moment.unix(Number(beginTime)), moment.unix(Number(endTime))]
    }
    return null
  }, [timeRangeKey, beginTime, endTime])

  function handleTimeChange(values: any) {
    if (values === null) {
      setQueryParams({
        beginTime: '',
        endTime: '',
        notEmpty: 'false'
      })
    } else {
      const beginTime = values[0].unix()
      const endTime = values[1].unix()
      setQueryParams({
        beginTime: `${beginTime}`,
        endTime: `${endTime}`,
        notEmpty: 'false'
      })
    }
  }

  const enableQuery = beginTime !== '' && endTime !== ''

  // TODO: rename useMutation to useRequestWithoutCache
  const listFilesRequest = useMutation(listClusterFiles)
  function handleQuery() {
    listFilesRequest.run({
      fileType: queryParams.fileType,
      beginTime,
      endTime,
      cluster: curCluster!,
      org: curOrg!
    })
  }
  useEffect(() => {
    if (beginTime !== '' || endTime !== '') {
      handleQuery()
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  const files = useMemo(() => {
    let _files: any[] = []
    const records = listFilesRequest.data?.data.records ?? []
    records.forEach((r: any) => {
      r.files.forEach((f: any) => {
        _files.push({
          ...f,
          instance: r.instance,
          friendSize: getValueFormat('decbytes')(f.size, 1),
          friendCreateTime: formatDateTimeWithTz(f.created_at_unix_sec)
        })
      })
    })
    return _files.sort((a, b) => b.created_at_unix_sec - a.created_at_unix_sec)
  }, [listFilesRequest.data])

  return (
    <Card withBorder mih={400}>
      <Stack spacing='xl'>
        <Group className='items-end'>
          <Select
            label='Files Type'
            data={dedicatedFileTypes}
            defaultValue={queryParams.fileType}
            onChange={(v) => setQueryParams({ fileType: v, notEmpty: 'false' })}
          />

          <Select
            label='Time Range'
            data={timeRangeOptions}
            defaultValue={timeRangeKey}
            onChange={(v) => {
              clusterFilesTelemetry.selectTimeRange({ time_range_type: v! })
              if (v !== 'absolute') {
                setQueryParams({
                  beginTime: v,
                  endTime: 'now',
                  notEmpty: 'false'
                })
              } else {
                setQueryParams({
                  beginTime: '',
                  endTime: '',
                  notEmpty: 'false'
                })
              }
            }}
          />

          {timeRangeKey === 'absolute' && (
            <DatePicker.RangePicker
              showTime
              className='rounded-[4px] h-9'
              onChange={handleTimeChange}
              defaultValue={defTimeRange}
            />
          )}

          <Button
            disabled={!enableQuery}
            loading={listFilesRequest.loading}
            onClick={() => {
              handleQuery()
              clusterFilesTelemetry.clickQuery()
            }}
          >
            Query
          </Button>
        </Group>

        <Skeleton visible={listFilesRequest.loading} animate>
          <Text color='dimmed'>
            {files.length} {files.length === 1 ? 'file' : 'files'}, show up to
            100 files.
          </Text>

          {listFilesRequest.data &&
            files.length === 0 &&
            queryParams.notEmpty === 'true' && (
              <Alert
                icon={<IconAlertCircle size={16} />}
                color='red'
                className='my-2'
              >
                It's expected to have non emtpy results, please inquire at Ask
                Clinic Group.
              </Alert>
            )}

          <Table horizontalSpacing={0} verticalSpacing='sm'>
            <thead>
              <tr>
                <th>Instance</th>
                <th>File</th>
                <th>Size</th>
                <th>Create Time</th>
                <th>Action</th>
              </tr>
            </thead>
            <tbody>
              {files.map((f) => (
                <tr key={f.key}>
                  <td>{f.instance}</td>
                  <td>{f.filename}</td>
                  <td>{f.friendSize}</td>
                  <td>{f.friendCreateTime}</td>
                  <td>
                    <Text
                      className='underline'
                      component='a'
                      href={f.download_url}
                      onClick={() => clusterFilesTelemetry.clickDownload()}
                    >
                      Download
                    </Text>
                  </td>
                </tr>
              ))}
            </tbody>
          </Table>
        </Skeleton>
      </Stack>
    </Card>
  )
}

const PlanReplayerFilesPage: React.FC = () => {
  const { curOrg } = useCurOrg()
  const { curCluster } = useCurCluster()
  const [queryParams, setQueryParams] = useUrlState<PlanReplayerQueryParams>(
    planReplayerDefaultQueryParams,
    { navigateMode: 'replace' }
  )

  const [files, setFiles] = useState<PlanReplayerFile[]>([])
  const [filesClusterId, setFilesClusterId] = useState<string>()
  const [loading, setLoading] = useState(false)
  const [accessError, setAccessError] = useState(false)
  const [downloadingFileName, setDownloadingFileName] = useState<string>()
  const queryRequestIdRef = useRef(0)
  const downloadRequestIdRef = useRef(0)

  const handleQuery = useCallback(async () => {
    const oid = curOrg?.id
    const cid = curCluster?.id
    if (!oid || !cid) {
      return
    }

    const requestId = ++queryRequestIdRef.current
    setLoading(true)
    setAccessError(false)

    try {
      const collectedFiles: PlanReplayerFile[] = []
      const seenPageTokens = new Set<string>()
      let pageToken: string | undefined

      while (collectedFiles.length < MAX_VISIBLE_FILES) {
        const response = await listPlanReplayerFiles({
          oid,
          cid,
          pageSize: MAX_VISIBLE_FILES - collectedFiles.length,
          pageToken
        })
        if (requestId !== queryRequestIdRef.current) {
          return
        }

        const remaining = MAX_VISIBLE_FILES - collectedFiles.length
        collectedFiles.push(...(response.data.files ?? []).slice(0, remaining))

        const nextPageToken = response.data.nextPageToken || undefined
        if (!nextPageToken || seenPageTokens.has(nextPageToken)) {
          break
        }

        seenPageTokens.add(nextPageToken)
        pageToken = nextPageToken
      }

      setFilesClusterId(cid)
      setFiles(
        collectedFiles.sort(
          (a, b) =>
            moment(b.lastModified).unix() - moment(a.lastModified).unix()
        )
      )
    } catch (error) {
      if (requestId === queryRequestIdRef.current) {
        setFilesClusterId(cid)
        setFiles([])
        if (isForbiddenError(error)) {
          setAccessError(true)
        }
      }
    } finally {
      if (requestId === queryRequestIdRef.current) {
        setLoading(false)
      }
    }
  }, [curCluster?.id, curOrg?.id])

  useEffect(() => {
    if (
      queryParams.fileType !== PLAN_REPLAYER_FILE_TYPE.value ||
      queryParams.beginTime !== undefined ||
      queryParams.endTime !== undefined ||
      queryParams.notEmpty !== undefined
    ) {
      setQueryParams({
        fileType: PLAN_REPLAYER_FILE_TYPE.value,
        beginTime: undefined,
        endTime: undefined,
        notEmpty: undefined
      })
    }
  }, [
    queryParams.beginTime,
    queryParams.endTime,
    queryParams.fileType,
    queryParams.notEmpty,
    setQueryParams
  ])

  useEffect(() => {
    setDownloadingFileName(undefined)
    handleQuery()
    return () => {
      queryRequestIdRef.current += 1
      downloadRequestIdRef.current += 1
    }
    // Query on mount and when switching clusters.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [curCluster?.id, curOrg?.id])

  const handleDownload = async (fileName: string) => {
    const oid = curOrg?.id
    const cid = curCluster?.id
    if (!oid || !cid) {
      return
    }

    clusterFilesTelemetry.clickDownload()
    const requestId = ++downloadRequestIdRef.current
    setDownloadingFileName(fileName)
    setAccessError(false)

    try {
      const response = await generatePlanReplayerFileDownloadUrl({
        oid,
        cid,
        fileName
      })
      if (requestId === downloadRequestIdRef.current) {
        const downloadUrl = getHttpsUrl(response.data.downloadUrl)
        if (!downloadUrl) {
          const errorMessage =
            'Unable to generate a valid Plan Replayer download URL.'
          message.error({
            content: `Error: ${errorMessage}`,
            key: errorMessage
          })
          return
        }
        window.location.assign(downloadUrl)
      }
    } catch (error) {
      if (
        requestId === downloadRequestIdRef.current &&
        isForbiddenError(error)
      ) {
        setAccessError(true)
      }
    } finally {
      if (requestId === downloadRequestIdRef.current) {
        setDownloadingFileName(undefined)
      }
    }
  }

  const currentFiles: PlanReplayerFile[] =
    filesClusterId === curCluster?.id ? files : []
  const visibleFiles = currentFiles.slice(0, MAX_VISIBLE_FILES)

  return (
    <Card withBorder mih={400}>
      <Stack spacing='xl'>
        <Group className='items-end'>
          <Select
            label='Files Type'
            data={[PLAN_REPLAYER_FILE_TYPE]}
            value={PLAN_REPLAYER_FILE_TYPE.value}
            onChange={() =>
              setQueryParams({ fileType: PLAN_REPLAYER_FILE_TYPE.value })
            }
          />

          <Button
            loading={loading}
            onClick={() => {
              handleQuery()
              clusterFilesTelemetry.clickQuery()
            }}
          >
            Query
          </Button>
        </Group>

        <Skeleton visible={loading} animate>
          {accessError && filesClusterId === curCluster?.id && (
            <Alert
              icon={<IconAlertCircle size={16} />}
              title='Plan Replayer file access unavailable'
              color='red'
              className='my-2'
            >
              {PLAN_REPLAYER_ACCESS_ERROR}
            </Alert>
          )}

          <Text color='dimmed'>
            Showing {visibleFiles.length}{' '}
            {visibleFiles.length === 1 ? 'file' : 'files'} (up to{' '}
            {MAX_VISIBLE_FILES}).
          </Text>

          <Table horizontalSpacing={0} verticalSpacing='sm'>
            <thead>
              <tr>
                <th>File</th>
                <th>Size</th>
                <th>Last Modified</th>
                <th>Action</th>
              </tr>
            </thead>
            <tbody>
              {visibleFiles.map((file) => (
                <tr key={file.fileName}>
                  <td>{file.fileName}</td>
                  <td>
                    {getValueFormat('decbytes')(Number(file.sizeBytes), 1)}
                  </td>
                  <td>
                    {formatDateTimeWithTz(moment(file.lastModified).unix())}
                  </td>
                  <td>
                    <Text
                      className='underline'
                      component='a'
                      href='#'
                      aria-busy={downloadingFileName === file.fileName}
                      aria-disabled={downloadingFileName !== undefined}
                      onClick={(event) => {
                        event.preventDefault()
                        if (downloadingFileName === undefined) {
                          handleDownload(file.fileName)
                        }
                      }}
                    >
                      Download
                    </Text>
                  </td>
                </tr>
              ))}
            </tbody>
          </Table>
        </Skeleton>
      </Stack>
    </Card>
  )
}

const Page: React.FC = () => {
  const { curCluster } = useCurCluster()

  if (!curCluster) {
    return null
  }

  return curCluster.computed.isPremium ? (
    <PlanReplayerFilesPage />
  ) : (
    <DedicatedFilesPage />
  )
}

export default Page
