import { getCreateAuth, getLocalFileChunk, updateAuth, uploadLocalFile } from '@/api/base'

export interface VideoUploadItem {
  file: File
  name: string
  progress: number
  url: string
  videoId: string
  state: 'pending' | 'uploading' | 'success' | 'failed'
  abortController?: AbortController
  uploader?: any
}

interface VideoUploadCallbacks {
  onUploadStarted?: (file: VideoUploadItem) => void
  onUploadSucceed?: (file: VideoUploadItem) => void
  onUploadFailed?: (file: VideoUploadItem, error: unknown) => void
  onUploadProgress?: (file: VideoUploadItem, progress: number) => void
}

interface VideoUploaderOptions extends VideoUploadCallbacks {
  autoUpload?: boolean
  multiple?: boolean
  onUploadEnd?: () => void
}

const isSchool = import.meta.env.MODE === 'school'

function createVideoUploadItem(file: File): VideoUploadItem {
  return {
    file,
    name: file.name,
    progress: 0,
    url: '',
    videoId: '',
    state: 'pending',
  }
}

async function uploadLocalVideo(fileItem: VideoUploadItem, callbacks: VideoUploadCallbacks) {
  fileItem.abortController = new AbortController()
  const {
    data: { detail },
  } = await getLocalFileChunk({ file_name: fileItem.file.name, file_size: fileItem.file.size })

  const fileName = detail.file_name
  const chunkSize = detail.chunk_size || fileItem.file.size
  const totalChunks = Math.max(1, Math.ceil(fileItem.file.size / chunkSize))

  for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
    const chunk = fileItem.file.slice(chunkIndex * chunkSize, (chunkIndex + 1) * chunkSize)
    const {
      data: { detail: uploadDetail },
    } = await uploadLocalFile(
      {
        file_name: fileName,
        file: chunk,
        now_package_num: chunkIndex + 1,
        total_package_num: totalChunks,
      },
      {
        signal: fileItem.abortController.signal,
        onUploadProgress(event: ProgressEvent) {
          const totalSize = (event.total || chunk.size) * totalChunks
          const loadedSize = event.loaded + chunkIndex * (event.total || chunk.size)
          const progress = Math.min(100, parseFloat(((loadedSize / totalSize) * 100).toFixed(2)))
          fileItem.progress = progress
          callbacks.onUploadProgress?.(fileItem, progress)
        },
      },
    )

    fileItem.url = uploadDetail.uri
  }

  fileItem.progress = 100
}

function uploadAliyunVideo(fileItem: VideoUploadItem, callbacks: VideoUploadCallbacks) {
  return new Promise<void>((resolve, reject) => {
    const uploader = new (window as any).AliyunUpload.Vod({
      userId: '1303984639806000',
      region: 'cn-shanghai',
      partSize: 4 * 1024 * 1024,
      parallel: 3,
      retryCount: 3,
      retryDuration: 2,
      onUploadstarted(uploadInfo: any) {
        getCreateAuth({ title: uploadInfo.file.name, file_name: uploadInfo.file.name })
          .then((res: any) => {
            uploader.setUploadAuthAndAddress(
              uploadInfo,
              res.data.upload_auth,
              res.data.upload_address,
              res.data.source_id,
            )
          })
          .catch(reject)
      },
      onUploadSucceed(uploadInfo: any) {
        fileItem.videoId = uploadInfo.videoId
        fileItem.progress = 100
        resolve()
      },
      onUploadFailed(_uploadInfo: any, code: number, message: string) {
        reject(new Error(message || `视频上传失败: ${code}`))
      },
      onUploadProgress(_uploadInfo: any, _totalSize: number, loadedPercent: number) {
        const progress = Math.min(100, parseFloat((loadedPercent * 100).toFixed(2)))
        fileItem.progress = progress
        callbacks.onUploadProgress?.(fileItem, progress)
      },
      onUploadTokenExpired(uploadInfo: any) {
        updateAuth({ source_id: uploadInfo.videoId })
          .then((res: any) => {
            uploader.resumeUploadWithAuth(res.data.UploadAuth || res.data.upload_auth || res.UploadAuth)
          })
          .catch(reject)
      },
    })

    fileItem.uploader = uploader
    uploader.addFile(fileItem.file, null, null, null, '{"Vod":{}}')
    uploader.startUpload()
  })
}

async function uploadVideoItem(fileItem: VideoUploadItem, callbacks: VideoUploadCallbacks) {
  fileItem.state = 'uploading'
  callbacks.onUploadStarted?.(fileItem)

  try {
    if (isSchool) {
      await uploadLocalVideo(fileItem, callbacks)
    } else {
      await uploadAliyunVideo(fileItem, callbacks)
    }
    fileItem.state = 'success'
    callbacks.onUploadSucceed?.(fileItem)
    return fileItem
  } catch (error) {
    fileItem.state = 'failed'
    callbacks.onUploadFailed?.(fileItem, error)
    throw error
  }
}

export function getUploadedVideoSourceId(fileItem: VideoUploadItem) {
  return isSchool ? fileItem.url : fileItem.videoId
}

export async function uploadVideo(file: File, callbacks: VideoUploadCallbacks = {}) {
  const fileItem = createVideoUploadItem(file)
  await uploadVideoItem(fileItem, callbacks)
  return fileItem
}

export function createVideoUploader(options: VideoUploaderOptions = {}) {
  const settings = Object.assign({ autoUpload: true, multiple: false }, options)
  const files = ref<VideoUploadItem[]>([])
  const uploading = ref(false)

  function addFile(file: File) {
    const existingIndex = files.value.findIndex(
      (item) =>
        item.name === file.name &&
        item.file.size === file.size &&
        item.file.lastModified === file.lastModified &&
        item.state !== 'failed',
    )
    if (existingIndex !== -1) return

    if (!settings.multiple) {
      stopUpload()
    }

    files.value.push(createVideoUploadItem(file))
    if (settings.autoUpload && !uploading.value) {
      void startUpload()
    }
  }

  async function startUpload() {
    if (uploading.value) return
    uploading.value = true

    try {
      while (true) {
        const fileItem = files.value.find((item) => item.state === 'pending')
        if (!fileItem) break
        try {
          await uploadVideoItem(fileItem, settings)
        } catch {
          // Error is surfaced via callbacks and state updates.
        }
      }
    } finally {
      uploading.value = false
      settings.onUploadEnd?.()
    }
  }

  function cancelUpload(fileItem: VideoUploadItem) {
    fileItem.abortController?.abort()
    fileItem.uploader?.stopUpload?.()
    files.value = files.value.filter((item) => item !== fileItem)
  }

  function stopUpload() {
    const currentFiles = [...files.value]
    files.value = []
    currentFiles.forEach((fileItem) => {
      fileItem.abortController?.abort()
      fileItem.uploader?.stopUpload?.()
    })
    uploading.value = false
  }

  return {
    files,
    uploading,
    addFile,
    startUpload,
    cancelUpload,
    stopUpload,
  }
}
