import { Group, Button, Progress, Typography, Stack, Dropzone, DropzoneProps, Box } from '@tidbcloud/uikit'
import { IconCheckCircle, IconFileImport02, IconXCircle } from '@tidbcloud/uikit/icons'
import axios from 'axios'
import { forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react'

import { humanByteSize } from 'common/services/prommetric'
import { MAX_UPLOAD_FILE_SIZE } from 'dbaas/screens/DataImportV2/constant'
import { importServiceUpload } from 'dbaas/services/api-clients/legacy/serverless'
import { UploadResponse as ServerlessUploadResponse } from 'dbaas/services/api-clients/legacy/serverless/interface'
import { getErrorMessage } from 'dbaas/services/errorCodes'

export interface ServerlessLocalFileUploaderProps {
  className?: string

  /** File input accept attribute, for example, "image/png,image/jpeg" */
  accept?: string[]

  // Amazon s3 region
  region?: string
  disabled?: boolean

  // for security compliance, it requires projectId to upload files
  orgId: string
  projectId: string
  clusterId: string

  defaultValue?: {
    file?: File
    progress?: number
  }

  onProgress?: (e: ProgressEvent) => void
  onBeforeUpload?: (file: File) => boolean | Promise<boolean> | undefined
  onSuccess?: (result: ServerlessUploadResponse, file: File) => void
  onError?: (err: unknown) => void
  onReset?: () => void
  onCancel?: () => void
  onReject?: DropzoneProps['onReject']
}

type S3UploaderRefInstance = { upload: (file: File) => void }

export const ServerlessLocalFileUploader = forwardRef<S3UploaderRefInstance, ServerlessLocalFileUploaderProps>(
  (
    {
      accept,
      onSuccess,
      onError,
      onBeforeUpload,
      onProgress,
      onReset,
      onCancel,
      onReject,
      defaultValue,
      orgId,
      projectId,
      clusterId
    },
    ref
  ) => {
    const [progress, setProgress] = useState(defaultValue?.progress ?? 0)
    const [file, setFile] = useState<File | null>(defaultValue?.file ?? null)
    const abortRef = useRef<AbortController>()
    const [error, setError] = useState('')

    const handleChange = async (file: File | null) => {
      if (!file) return

      if (onBeforeUpload) {
        const shouldContinue = await onBeforeUpload(file)
        if (shouldContinue === false) {
          return
        }
      }

      setFile(file)

      const controller = new AbortController()
      abortRef.current = controller
      try {
        const res = await importServiceUpload(
          orgId,
          projectId,
          clusterId,
          {
            file_name: file.name
          },
          {
            signal: controller.signal
          }
        )

        await axios.put(res.upload_url!, file, {
          headers: {
            'Content-Type': file.type
          },
          onUploadProgress(e: ProgressEvent) {
            const p = Math.floor((e.loaded / e.total) * 100)

            // api call is not fully finished when progress is 100
            // so we'll set it to 100 when api call is done
            setProgress(p === 100 ? 99 : p)
            onProgress?.(e)
          },
          signal: controller.signal
        })

        setProgress(100)
        onSuccess?.(res, file)
      } catch (e) {
        const message = getErrorMessage(e)
        if (message !== 'canceled') {
          setError(message)
          onError?.(e)
        }
      }
    }

    const handleReset = () => {
      setError('')
      setProgress(0)
      setFile(null)
      onReset?.()
    }

    const handleCancel = () => {
      abortRef.current?.abort()
      handleReset()
      onCancel?.()
    }

    const progressColor = useMemo(() => {
      if (error) return 'red'
      if (progress === 100) return 'green'
      return 'peacock'
    }, [progress, error])

    useImperativeHandle(ref, () => ({
      upload: (file: File) => {
        handleChange(file)
      }
    }))

    return (
      <div>
        <Dropzone
          data-testid="import-dropzone"
          multiple={false}
          maxFiles={1}
          onDrop={(files) => {
            handleChange(files?.[0])
          }}
          onReject={onReject}
          maxSize={MAX_UPLOAD_FILE_SIZE.value}
          accept={accept}
          styles={(theme) => {
            return {
              root: {
                display: file ? 'none' : 'block',
                '&[data-accept]': {
                  backgroundColor: theme.colors.peacock[1],
                  borderColor: theme.colors.peacock[7],

                  '& .mantine-Text-root': {
                    color: theme.colors.peacock[7]
                  }
                },
                '&[data-reject]': {
                  backgroundColor: theme.colors.red[1],
                  borderColor: theme.colors.red[7],

                  '& .mantine-Text-root': {
                    color: theme.colors.red[7]
                  }
                }
              }
            }
          }}
        >
          <Group justify="center" gap="xl" style={{ pointerEvents: 'none' }} w={600} py={40}>
            <Dropzone.Accept>
              <Box component={IconCheckCircle} size={32} c="peacock.7" />
            </Dropzone.Accept>
            <Dropzone.Reject>
              <Box component={IconXCircle} size={32} c="red.7" />
            </Dropzone.Reject>
            <Dropzone.Idle>
              <Box component={IconFileImport02} size={32} c="gray.7" />
            </Dropzone.Idle>

            <div data-testid="import-dropzone-title">
              <Typography variant="title-md">Drag and drop or click and select a local file here to upload</Typography>
              <Typography variant="body-lg">
                Only one CSV file less than {MAX_UPLOAD_FILE_SIZE.label} is supported.
              </Typography>
            </div>
          </Group>
        </Dropzone>

        {file && (
          <Group align="flex-start">
            <Stack gap={4} style={{ flexGrow: 1 }}>
              <Progress value={progress} color={progressColor} />

              <Group justify="space-between">
                {error ? (
                  <Typography c="red" dangerouslySetInnerHTML={{ __html: error }} />
                ) : (
                  <Group>
                    <Typography variant="label-lg" truncate title={file?.name}>
                      {file?.name}
                    </Typography>
                    <Typography variant="body-lg">{humanByteSize(file?.size, false)}</Typography>
                  </Group>
                )}
                <Typography c={progressColor} fz={14} fw={500} data-testid="upload-status">
                  {progress === 100 ? `Success` : error ? `Error` : `Uploading`}
                </Typography>
              </Group>
            </Stack>

            {progress >= 0 && progress < 100 && (
              <Button variant="default" onClick={handleCancel} size="xs">
                Cancel
              </Button>
            )}

            {progress === 100 && (
              <Button variant="default" onClick={handleReset} size="xs">
                Delete
              </Button>
            )}
          </Group>
        )}
      </div>
    )
  }
)
