<template>
  <el-card class="box-card upload-card shadow-box">
    <div slot="header" class="clearfix">
      <i class="el-icon-upload2" />
      <span style="margin-left: 8px">{{ uploadConfig.title }}</span>
      <el-tag v-if="uploadStatus" :type="statusTagType" size="mini" style="float: right">
        {{ uploadStatus }}
      </el-tag>
    </div>

    <uploader
      v-if="uploaderOptions"
      ref="uploader"
      :options="uploaderOptions"
      :auto-start="false"
      class="uploader-app"
      @file-added="onFileAdded"
      @file-success="onFileSuccess"
      @file-error="onFileError"
      @file-progress="onFileProgress"
    >
      <uploader-unsupport />
      <uploader-drop>
        <div class="drop-area">
          <i class="el-icon-video-camera" />
          <p>将文件拖到此处，或 <uploader-btn :attrs="uploadConfig.fileAttrs" class="uploader-btn">点击上传</uploader-btn></p>
          <div class="upload-tip">文件最大不超过 10GB</div>
        </div>
      </uploader-drop>
      <uploader-list />
    </uploader>
  </el-card>
</template>

<script>
import SparkMD5 from 'spark-md5'
import { prepareUpload, checkSample } from '@/api/file'
import { hashFile } from '@/utils/functions'

export default {
  name: 'UploaderCard',
  props: {
    // 接收父组件传入的配置对象
    uploadConfig: {
      type: Object,
      required: true
    }
  },
  data() {
    return {
      uploaderOptions: null,
      uploadStatus: '', // 计算Hash, 预校验, 采样校验, 上传中, 已完成, 错误
      videoChannelCode: null
    }
  },
  computed: {
    statusTagType() {
      const map = { '上传中': '', '已完成': 'success', '错误': 'danger', '预校验': 'warning', '采样校验': 'warning' }
      return map[this.uploadStatus] || 'info'
    }
  },
  watch: {
    // 确保在数据传入后初始化
    uploadConfig: {
      immediate: true,
      handler(newVal) {
        if (newVal) {
          this.initUploader(newVal)
        }
      }
    }
  },
  created() {
  },
  methods: {
    initUploader(uploadConfig) {
      this.videoChannelCode = uploadConfig.channelCode
      var ossUrl = uploadConfig.ossUrl
      var token = uploadConfig.token

      this.uploaderOptions = {
        target: ossUrl,
        chunkSize: 1024 * 1024 * 10, // 初始分片大小，后续会被 prepare 接口返回的 splitSize 覆盖
        fileParameterName: 'file',
        testChunks: false,
        processParams: (params, file, chunk) => ({
          identifier: file.uniqueIdentifier,
          chunkSize: params.chunkSize,
          totalChunks: params.totalChunks,
          chunkNumber: params.chunkNumber,
          filename: file.name,
          sha256sum: file.sha256sum
        }),
        query: () => ({ channelCode: this.videoChannelCode }),
        checkChunkUploadedByResponse: (chunk, message) => {
          // 上传文件到 oss 后的响应
          const obj = JSON.parse(message)
          return obj.data.uploaded
          // return (obj.data.uploaded || []).indexOf(chunk.offset + 1) >= 0
        },
        headers: { Authorization: 'Bearer ' + token }
      }
    },

    onFileAdded: async function(file) {
      this.$emit('before-upload', file.name)

      try {
        // 1. 全文件 SHA256 计算
        this.uploadStatus = '计算Hash'
        const hashResult = await hashFile(file.file)
        file.sha256sum = hashResult.sha256sum

        // 2. /upload/prepare 预校验
        this.uploadStatus = '预校验'
        const prepareRes = await prepareUpload({
          channelCode: this.videoChannelCode,
          filename: file.name,
          size: file.size,
          sha256sum: file.sha256sum
        })

        if (prepareRes.code !== 0) throw new Error(prepareRes.msg)

        const { uploadId, splitSize, exist, offset, length } = prepareRes.data
        file.uniqueIdentifier = uploadId
        // 动态调整分片大小（如果后端有要求）
        if (splitSize) this.uploaderOptions.chunkSize = splitSize

        // 3. /upload/check_sample 采样校验
        let isFastUpload = false
        if (exist) {
          this.uploadStatus = '采样校验'
          const sampleMd5 = await this.calculateSampleMd5(file.file, offset, length)

          const checkRes = await checkSample({
            uploadId: uploadId,
            sampleMd5: sampleMd5
          })

          if (checkRes.code === 0 && checkRes.data.fastUpload) {
            isFastUpload = true
            // 1. 直接取消该文件的上传任务（从 uploader-list 中移除）
            file.cancel()
            // 2. 更新全局状态
            this.uploadStatus = '已完成'

            // 2. 延迟一小会儿执行成功回调，让用户看到进度条拉满的快感
            setTimeout(() => {
              this.handleUploadSuccess(file, checkRes.data)
            }, 1000)
            return
          }
        }

        // 4. 如果没能秒传，启动分片上传
        if (!isFastUpload) {
          this.uploadStatus = '上传中'
          file.resume()
        }
      } catch (e) {
        this.uploadStatus = '错误'
        this.$notify.error({ title: '校验失败', message: e.message })
        file.cancel()
      }
    },

    // 核心工具：计算文件指定位置的 MD5
    calculateSampleMd5(file, offset, length) {
      return new Promise((resolve, reject) => {
        const spark = new SparkMD5.ArrayBuffer()
        const reader = new FileReader()
        // 只读取后端要求的片段 [offset, offset + length]
        const blob = file.slice(offset, offset + length)

        reader.onload = (e) => {
          spark.append(e.target.result)
          resolve(spark.end())
        }
        reader.onerror = () => reject('采样读取失败')
        reader.readAsArrayBuffer(blob)
      })
    },

    handleUploadSuccess(file, resData) {
      this.uploadStatus = '已完成'
      this.$emit('success', {
        uploadId: resData.uploadId,
        file: file,
        channelCode: this.videoChannelCode
      })
    },

    onFileSuccess(rootFile, file, response) {
      const res = JSON.parse(response)
      if (res.code === 0) {
        this.handleUploadSuccess(file, res.data)
      } else {
        this.uploadStatus = '错误'
        this.$message.error(res.msg || '上传失败')
      }
    },

    onFileError() {
      this.uploadStatus = '错误'
    },

    onFileProgress() {
      this.uploadStatus = '上传中'
    }
  }
}
</script>
