import fs from 'fs/promises';
import path from 'path';
import crypto from 'crypto';
export class ChunkUploadManager {
    constructor() {
        this.uploads = new Map();
        this.CHUNK_DIR = path.join(process.cwd(), 'server', 'temp', 'chunks');
        this.CLEANUP_INTERVAL = 1000 * 60 * 60;
        this.MAX_UPLOAD_AGE = 1000 * 60 * 60 * 24;
        this.ensureChunkDir();
        this.startCleanupTimer();
    }
    static getInstance() {
        if (!ChunkUploadManager.instance) {
            ChunkUploadManager.instance = new ChunkUploadManager();
        }
        return ChunkUploadManager.instance;
    }
    async ensureChunkDir() {
        try {
            await fs.mkdir(this.CHUNK_DIR, { recursive: true });
        }
        catch (error) {
            console.error('创建分片目录失败:', error);
        }
    }
    getUploadDir(uploadId) {
        return path.join(this.CHUNK_DIR, uploadId);
    }
    getChunkPath(uploadId, chunkIndex) {
        return path.join(this.getUploadDir(uploadId), `chunk_${chunkIndex}`);
    }
    async getOrCreateUpload(uploadId, fileName, fileSize, totalChunks, targetPath) {
        if (this.uploads.has(uploadId)) {
            const upload = this.uploads.get(uploadId);
            upload.lastActivity = Date.now();
            return upload;
        }
        const upload = {
            uploadId,
            fileName,
            fileSize,
            totalChunks,
            uploadedChunks: new Set(),
            chunks: new Map(),
            targetPath,
            createdAt: Date.now(),
            lastActivity: Date.now()
        };
        const uploadDir = this.getUploadDir(uploadId);
        await fs.mkdir(uploadDir, { recursive: true });
        try {
            const files = await fs.readdir(uploadDir);
            for (const file of files) {
                const match = file.match(/^chunk_(\d+)$/);
                if (match) {
                    const chunkIndex = parseInt(match[1], 10);
                    upload.uploadedChunks.add(chunkIndex);
                }
            }
        }
        catch (error) {
        }
        this.uploads.set(uploadId, upload);
        return upload;
    }
    async saveChunk(uploadId, chunkIndex, chunkData, chunkHash) {
        const upload = this.uploads.get(uploadId);
        if (!upload) {
            throw new Error('上传会话不存在');
        }
        if (chunkHash && chunkHash !== 'skip') {
            const actualHash = crypto.createHash('sha256').update(chunkData).digest('hex');
            if (actualHash !== chunkHash) {
                console.warn(`分片 ${chunkIndex} hash不匹配，但继续保存。期望: ${chunkHash}, 实际: ${actualHash}`);
            }
        }
        const chunkPath = this.getChunkPath(uploadId, chunkIndex);
        await fs.writeFile(chunkPath, chunkData);
        upload.uploadedChunks.add(chunkIndex);
        upload.chunks.set(chunkIndex, {
            chunkIndex,
            chunkSize: chunkData.length,
            chunkHash,
            filePath: chunkPath,
            uploaded: true
        });
        upload.lastActivity = Date.now();
        console.log(`分片已保存: ${uploadId} - ${chunkIndex}/${upload.totalChunks}`);
    }
    isUploadComplete(uploadId) {
        const upload = this.uploads.get(uploadId);
        if (!upload) {
            return false;
        }
        return upload.uploadedChunks.size === upload.totalChunks;
    }
    async mergeChunks(uploadId, targetFilePath) {
        const upload = this.uploads.get(uploadId);
        if (!upload) {
            throw new Error('上传会话不存在');
        }
        if (!this.isUploadComplete(uploadId)) {
            throw new Error('部分分片未上传，无法合并');
        }
        console.log(`开始合并文件: ${upload.fileName}`);
        const targetDir = path.dirname(targetFilePath);
        await fs.mkdir(targetDir, { recursive: true });
        const writeStream = await fs.open(targetFilePath, 'w');
        try {
            for (let i = 0; i < upload.totalChunks; i++) {
                const chunkPath = this.getChunkPath(uploadId, i);
                const chunkData = await fs.readFile(chunkPath);
                await writeStream.write(chunkData);
            }
            console.log(`文件合并完成: ${upload.fileName}`);
        }
        finally {
            await writeStream.close();
        }
        await this.cleanupUpload(uploadId);
    }
    getUploadedChunks(uploadId) {
        const upload = this.uploads.get(uploadId);
        if (!upload) {
            return [];
        }
        return Array.from(upload.uploadedChunks).sort((a, b) => a - b);
    }
    async cleanupUpload(uploadId) {
        const upload = this.uploads.get(uploadId);
        if (!upload) {
            return;
        }
        try {
            const uploadDir = this.getUploadDir(uploadId);
            await fs.rm(uploadDir, { recursive: true, force: true });
            console.log(`已清理上传会话: ${uploadId}`);
        }
        catch (error) {
            console.error(`清理上传会话失败: ${uploadId}`, error);
        }
        this.uploads.delete(uploadId);
    }
    startCleanupTimer() {
        setInterval(async () => {
            const now = Date.now();
            const expiredUploads = [];
            for (const [uploadId, upload] of this.uploads.entries()) {
                const age = now - upload.lastActivity;
                if (age > this.MAX_UPLOAD_AGE) {
                    expiredUploads.push(uploadId);
                }
            }
            for (const uploadId of expiredUploads) {
                console.log(`清理过期上传会话: ${uploadId}`);
                await this.cleanupUpload(uploadId);
            }
        }, this.CLEANUP_INTERVAL);
    }
    async cancelUpload(uploadId) {
        await this.cleanupUpload(uploadId);
    }
}
