package com.keyware.shandan.system.utils;

import cn.hutool.core.io.FileUtil;
import com.keyware.shandan.common.util.DateUtil;
import com.keyware.shandan.frame.config.component.AppContext;
import com.keyware.shandan.frame.properties.CustomProperties;
import com.keyware.shandan.system.entity.SysFile;
import com.keyware.shandan.system.entity.SysFileChunk;
import lombok.Data;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.constraints.NotNull;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.stream.Collectors;

@Data
public class FileChunkUploadUtil {
    private static final Logger logger = LoggerFactory.getLogger(FileChunkUploadUtil.class);
    private static CustomProperties customProperties;
    private static ConcurrentHashMap<String, TempFile> cache = new ConcurrentHashMap<>();

    /**
     * 上传分片文件
     *
     * @param uploadFile 分片文件
     * @param sysFile    文件信息
     * @param chunk      分片信息
     * @throws IOException
     */
    public static SysFile uploadChunk(@NotNull MultipartFile uploadFile, SysFile sysFile, @NotNull SysFileChunk chunk) throws IOException {
        sysFile.setMD5(chunk.getFileMd5());
        TempFile tempFile = checkOrInit(sysFile);

        tempFile.uploadChunk(uploadFile, chunk);

        return tempFile.getSysFile();
    }

    /**
     * 合并分片
     *
     * @param file 文件信息
     * @throws IOException
     */
    public static void mergeChunk(SysFile file, Consumer<SysFile> consumer) throws IOException {
        TempFile tempFile = cache.get(file.getMD5());
        if (tempFile != null) {
            SysFile file1 = tempFile.mergeChunk();
            file.setPath(file1.getPath());
            file.setIsMerge(true);
            file1.setIsMerge(true);
            file1.setIsFirst(true);
            file1.setEntityId("");
            consumer.accept(file1);
            cache.remove(file.getMD5());
        }
    }

    /**
     * 检查并创建文件上传缓存
     *
     * @param file
     * @return
     */
    public static synchronized TempFile checkOrInit(SysFile file) {
        TempFile tempFile = cache.get(file.getMD5());
        if (tempFile == null) {
            String fileType = FileUtil.extName(file.getFileName());
            file.setFileSuffix("." + fileType);
            file.setFileSize(file.getFileSize() / 1024d / 1024d);
            file.setFileType(fileType);
            file.setPath("");
            file.setIsChunk(true);
            file.setIsMerge(false);
            file.setIsFirst(true);

            tempFile = new TempFile(file);
            cache.put(file.getMD5(), tempFile);
        }
        return tempFile;
    }

    /**
     * 检测缓存是否存在
     *
     * @param fileMd5 文件MD5
     * @return 如果存在，则返回上传文件分片的索引集合
     */
    public static List<Object> exists(String fileMd5) {
        TempFile tempFile = cache.get(fileMd5);
        if (tempFile == null) {
            return new ArrayList<>();
        }

        return tempFile.getIndexSet();
    }

    public static CustomProperties.FileStorageProperties getFileStorage() {
        return getProperties().getFileStorage();
    }

    private static CustomProperties getProperties() {
        if (customProperties == null) {
            customProperties = AppContext.getContext().getBean(CustomProperties.class);
        }
        return customProperties;
    }


    @Data
    public static class TempFile {
        private SysFile sysFile;

        private ConcurrentHashMap<String, SysFileChunk> chunksCache = new ConcurrentHashMap<>();

        // 分片文件的存储路径
        private String chunkFIleStorePath;

        public TempFile(SysFile sysFile) {
            this.sysFile = sysFile;
            chunkFIleStorePath = FileChunkUploadUtil.getFileStorage().getTempPath() + File.separator + sysFile.getMD5();
            FileUtil.mkdir(chunkFIleStorePath);
        }

        /**
         * 上传分片文件
         *
         * @param uploadFile 上传的文件
         * @param chunk      分片信息
         * @throws IOException
         */
        public void uploadChunk(MultipartFile uploadFile, SysFileChunk chunk) throws IOException {
            if (!chunksCache.containsKey(chunk.getChunkMd5())) {
                File chunkFile = new File(chunkFIleStorePath + File.separator + chunk.getChunkMd5() + ".chunk");
                if(chunkFile.exists()){
                    chunkFile.delete();
                }
                uploadFile.transferTo(chunkFile);
                chunk.setChunkPath(chunkFile.getAbsolutePath());
                chunksCache.put(chunk.getChunkMd5(), chunk);
            }
        }

        /**
         * 合并分片文件
         *
         * @return
         * @throws IOException
         */
        public SysFile mergeChunk() throws IOException {
            this.sysFile.setPath(DateUtil.getFormatNowDate("yyyy" + File.separator + "MM" + File.separator + "dd") + File.separator + sysFile.getMD5() + sysFile.getFileSuffix());
            File targetFile = new File(FileChunkUploadUtil.getFileStorage().getPath() + File.separator + sysFile.getPath());
            File parent = targetFile.getParentFile();
            if (!parent.exists()) {
                parent.mkdirs();
            }
            if(targetFile.exists()){
                targetFile.delete();
                targetFile.createNewFile();
            }


            List<SysFileChunk> chunks = new ArrayList<>(chunksCache.values());
            chunks.sort((o1, o2) -> (int) (o1.getChunkIndex() - o2.getChunkIndex()));
            try (FileOutputStream outputStream = new FileOutputStream(targetFile, true)) {
                for (SysFileChunk chunk : chunks) {
                    File chunkFile = new File(chunk.getChunkPath());
                    FileUtils.copyFile(chunkFile, outputStream);
                }
            }

            // 删除分片缓存文件，该方法为递归删除
            FileUtil.del(chunkFIleStorePath);
            return this.sysFile;
        }

        /**
         * @return 已上传文件分片的索引集合
         */
        public List<Object> getIndexSet() {
            return chunksCache.values().stream().map(SysFileChunk::getChunkIndex).collect(Collectors.toList());
        }
    }
}
