<template>
  <div class="chunk-upload">
    <el-upload
      ref="uploadRef"
      :auto-upload="false"
      :on-change="handleFileChange"
      :show-file-list="false"
      drag
    >
      <el-icon class="el-icon--upload"><upload-filled /></el-icon>
      <div class="el-upload__text">
        将文件拖到此处，或<em>点击上传</em>
      </div>
      <template #tip>
        <div class="el-upload__tip">
          支持大文件分片上传，建议单个文件不超过5GB
        </div>
      </template>
    </el-upload>

    <!-- 上传进度 -->
    <div v-if="uploading" class="upload-progress">
      <el-progress 
        :percentage="uploadProgress" 
        :status="uploadStatus"
        :stroke-width="8"
      />
      <div class="upload-info">
        <span>文件名: {{ currentFile?.name }}</span>
        <span>大小: {{ formatFileSize(currentFile?.size) }}</span>
        <span>进度: {{ uploadedChunks }}/{{ totalChunks }} 分片</span>
      </div>
    </div>

    <!-- 上传结果 -->
    <div v-if="uploadResult" class="upload-result">
      <el-alert
        :title="uploadResult.success ? '上传成功' : '上传失败'"
        :type="uploadResult.success ? 'success' : 'error'"
        :description="uploadResult.message"
        show-icon
        :closable="false"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue';
import { ElMessage } from 'element-plus';
import { UploadFilled } from '@element-plus/icons-vue';
import { 
  initMultipartUpload, 
  uploadPart, 
  completeMultipartUpload, 
  abortMultipartUpload,
  checkUploadedParts 
} from '@/api/system/oss/multipart';

// 分片大小：8MB
const CHUNK_SIZE = 8 * 1024 * 1024;
// 并发数：3
const CONCURRENT_LIMIT = 3;

// 响应式数据
const uploadRef = ref();
const uploading = ref(false);
const uploadProgress = ref(0);
const uploadStatus = ref<'success' | 'exception' | 'warning' | ''>('');
const uploadedChunks = ref(0);
const totalChunks = ref(0);
const currentFile = ref<File | null>(null);
const uploadResult = ref<{ success: boolean; message: string } | null>(null);

// 上传状态
const uploadId = ref('');
const partETags = ref<string[]>([]);
const uploadedParts = ref<Set<number>>(new Set());

// 计算属性
const isUploading = computed(() => uploading.value);

// 处理文件选择
const handleFileChange = (file: any) => {
  if (file.raw) {
    currentFile.value = file.raw;
    startUpload(file.raw);
  }
};

// 开始上传
const startUpload = async (file: File) => {
  try {
    uploading.value = true;
    uploadProgress.value = 0;
    uploadStatus.value = '';
    uploadResult.value = null;
    uploadedChunks.value = 0;
    partETags.value = [];
    uploadedParts.value.clear();

    // 计算分片数量
    totalChunks.value = Math.ceil(file.size / CHUNK_SIZE);
    
    // 初始化分片上传
    const initResponse = await initMultipartUpload(file.name, file.size);
    if (!initResponse.success) {
      throw new Error(initResponse.msg || '初始化分片上传失败');
    }
    uploadId.value = initResponse.data;

    // 检查已上传的分片（断点续传）
    await checkExistingParts();

    // 开始上传分片
    await uploadChunks(file);

  } catch (error) {
    console.error('上传失败:', error);
    uploadStatus.value = 'exception';
    uploadResult.value = {
      success: false,
      message: error instanceof Error ? error.message : '上传失败'
    };
  } finally {
    uploading.value = false;
  }
};

// 检查已上传的分片
const checkExistingParts = async () => {
  try {
    const response = await checkUploadedParts(uploadId.value);
    if (response.success && response.data) {
      uploadedParts.value = new Set(response.data);
      uploadedChunks.value = uploadedParts.value.size;
      // console.log('已上传分片:', Array.from(uploadedParts.value));
    }
  } catch (error) {
    console.warn('检查已上传分片失败:', error);
  }
};

// 上传分片
const uploadChunks = async (file: File) => {
  const chunks: { partNumber: number; data: Blob }[] = [];
  
  // 准备所有分片
  for (let i = 0; i < totalChunks.value; i++) {
    const start = i * CHUNK_SIZE;
    const end = Math.min(start + CHUNK_SIZE, file.size);
    const chunk = file.slice(start, end);
    chunks.push({
      partNumber: i + 1,
      data: chunk
    });
  }

  // 过滤掉已上传的分片
  const remainingChunks = chunks.filter(chunk => !uploadedParts.value.has(chunk.partNumber));
  
  if (remainingChunks.length === 0) {
    // 所有分片都已上传，直接完成
    await completeUpload();
    return;
  }

  // 并发上传分片
  await uploadChunksConcurrently(remainingChunks);
  
  // 完成上传
  await completeUpload();
};

// 并发上传分片
const uploadChunksConcurrently = async (chunks: { partNumber: number; data: Blob }[]) => {
  const semaphore = new Semaphore(CONCURRENT_LIMIT);
  const uploadPromises: Promise<void>[] = [];

  for (const chunk of chunks) {
    const promise = semaphore.acquire().then(async (release) => {
      try {
        await uploadSingleChunk(chunk.partNumber, chunk.data);
      } finally {
        release();
      }
    });
    uploadPromises.push(promise);
  }

  await Promise.all(uploadPromises);
};

// 上传单个分片
const uploadSingleChunk = async (partNumber: number, chunkData: Blob) => {
  const maxRetries = 3;
  let retryCount = 0;

  while (retryCount < maxRetries) {
    try {
      const formData = new FormData();
      formData.append('file', chunkData);
      
      const response = await uploadPart(uploadId.value, partNumber, formData);
      
      if (response.success) {
        partETags.value[partNumber - 1] = response.data;
        uploadedChunks.value++;
        uploadProgress.value = Math.round((uploadedChunks.value / totalChunks.value) * 100);
        // console.log(`分片 ${partNumber} 上传成功`);
        return;
      } else {
        throw new Error(response.msg || '上传分片失败');
      }
    } catch (error) {
      retryCount++;
      console.error(`分片 ${partNumber} 上传失败 (重试 ${retryCount}/${maxRetries}):`, error);
      
      if (retryCount >= maxRetries) {
        throw new Error(`分片 ${partNumber} 上传失败，已重试 ${maxRetries} 次`);
      }
      
      // 等待一段时间后重试
      await new Promise(resolve => setTimeout(resolve, 1000 * retryCount));
    }
  }
};

// 完成上传
const completeUpload = async () => {
  try {
    const response = await completeMultipartUpload(
      uploadId.value,
      partETags.value.join(','),
      currentFile.value?.name || '',
      currentFile.value?.name || ''
    );
    
    if (response.success) {
      uploadStatus.value = 'success';
      uploadResult.value = {
        success: true,
        message: '文件上传成功'
      };
      ElMessage.success('文件上传成功');
    } else {
      throw new Error(response.msg || '完成上传失败');
    }
  } catch (error) {
    console.error('完成上传失败:', error);
    uploadStatus.value = 'exception';
    uploadResult.value = {
      success: false,
      message: error instanceof Error ? error.message : '完成上传失败'
    };
  }
};

// 取消上传
const cancelUpload = async () => {
  if (uploadId.value) {
    try {
      await abortMultipartUpload(uploadId.value);
      // console.log('已取消上传');
    } catch (error) {
      console.error('取消上传失败:', error);
    }
  }
  
  uploading.value = false;
  uploadProgress.value = 0;
  uploadStatus.value = '';
  uploadResult.value = null;
  uploadedChunks.value = 0;
  totalChunks.value = 0;
  currentFile.value = null;
  uploadId.value = '';
  partETags.value = [];
  uploadedParts.value.clear();
};

// 格式化文件大小
const formatFileSize = (bytes: number | undefined) => {
  if (!bytes) return '0 B';
  
  const sizes = ['B', 'KB', 'MB', 'GB'];
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
  return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
};

// 信号量类，用于控制并发
class Semaphore {
  private permits: number;
  private waiting: (() => void)[] = [];

  constructor(permits: number) {
    this.permits = permits;
  }

  async acquire(): Promise<() => void> {
    return new Promise((resolve) => {
      if (this.permits > 0) {
        this.permits--;
        resolve(() => this.release());
      } else {
        this.waiting.push(() => {
          this.permits--;
          resolve(() => this.release());
        });
      }
    });
  }

  private release() {
    this.permits++;
    if (this.waiting.length > 0) {
      const next = this.waiting.shift();
      if (next) next();
    }
  }
}

// 暴露方法给父组件
defineExpose({
  cancelUpload,
  isUploading
});
</script>

<style scoped>
.chunk-upload {
  width: 100%;
}

.upload-progress {
  margin-top: 20px;
  padding: 20px;
  background-color: #f5f7fa;
  border-radius: 4px;
}

.upload-info {
  display: flex;
  justify-content: space-between;
  margin-top: 10px;
  font-size: 14px;
  color: #606266;
}

.upload-result {
  margin-top: 20px;
}

.el-upload__tip {
  color: #909399;
  font-size: 12px;
  margin-top: 7px;
}
</style>
