package com.skr.mdm.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import com.skr.mdm.bean.dto.FileChunkResultDTO;
import com.skr.mdm.bean.qo.FileChunkQO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.*;

/**
 * @author nfq
 * @date 2022-11-24 10:05
 */
@Component
@Slf4j
public class FileChunkUtil {

    private final static String UPLOAD_FOLDER = "tmp" + File.separator + "upload";

    @Autowired
    private RedisUtils redisUtils;

    public FileChunkResultDTO checkChunkExist(FileChunkQO chunkQO) {
        //1.检查文件是否已上传过
        //1.1)检查在磁盘中是否存在
        String fileFolderPath = getFileFolderPath(chunkQO.getIdentifier());
        String filePath = getFilePath(chunkQO.getIdentifier(), chunkQO.getFileName());
        File file = new File(filePath);
        boolean exists = file.exists();
        //1.2)检查Redis中是否存在,并且所有分片已经上传完成。
        Set<Integer> uploaded = (Set<Integer>) redisUtils.hget(chunkQO.getIdentifier(), "uploaded");
        if (uploaded != null && uploaded.size() == chunkQO.getTotalChunks() && exists) {
            return new FileChunkResultDTO(true);
        }
        File fileFolder = new File(fileFolderPath);
        if (!fileFolder.exists()) {
            boolean mkdirs = fileFolder.mkdirs();
            log.info("准备工作,创建文件夹,fileFolderPath:{},mkdirs:{}", fileFolderPath, mkdirs);
        }
        return new FileChunkResultDTO(false, uploaded);
    }


    /**
     * 上传分片
     *
     * @param chunkQO
     */
    public File uploadChunk(FileChunkQO chunkQO) {
        //分块的目录
        String chunkFileFolderPath = getChunkFileFolderPath(chunkQO.getIdentifier());
        File chunkFileFolder = new File(chunkFileFolderPath);
        if (!chunkFileFolder.exists()) {
            boolean mkdirs = chunkFileFolder.mkdirs();
            log.info("创建分片文件夹:{} 是否成功：{}", chunkFileFolderPath, mkdirs);
        }
        //写入分片
        /// 如果使用分片恢复
//        try (InputStream inputStream = chunkQO.getFile().getInputStream();
//             FileOutputStream outputStream = new FileOutputStream(chunkFileFolderPath + chunkQO.getChunkNum())) {
//            IOUtils.copy(inputStream, outputStream);
//            log.info("文件标识:{},chunkNumber:{}", chunkQO.getIdentifier(), chunkQO.getChunkNum());
//            //将该分片写入redis
//            long size = saveToRedis(chunkQO);
//            //合并分片
//            if (size == chunkQO.getTotalChunks()) {
//                return mergeChunks(chunkQO.getIdentifier(), chunkQO.getFileName());
//            }
//        } catch (Exception e) {
//            log.error("上传分片异常", e);
//        }
        return null;
    }

    /**
     * 合并分片
     *
     * @param identifier
     * @param filename
     */
    private File mergeChunks(String identifier, String filename) {
        TimeInterval timer = DateUtil.timer();
        String chunkFileFolderPath = getChunkFileFolderPath(identifier);
        String filePath = getFilePath(identifier, filename);
        File chunkFileFolder = new File(chunkFileFolderPath);
        File mergeFile = new File(filePath);
        File[] chunks = chunkFileFolder.listFiles();
        //排序
        Arrays.stream(chunks).sorted(Comparator.comparing(o -> Integer.valueOf(o.getName())));
        try (FileChannel resultFileChannel = new FileOutputStream(mergeFile, true).getChannel()) {
            for (File chunk : chunks) {
                FileChannel inputChannel = new FileInputStream(chunk).getChannel();
                resultFileChannel.transferFrom(inputChannel, resultFileChannel.size(), inputChannel.size());
                inputChannel.close();
            }
        } catch (IOException e) {
            log.error("分片合并失败", e);
        }
        log.info("合并文件耗时：{}毫秒", timer.intervalMs());
        return mergeFile;
    }

    /**
     * 分片写入Redis
     *
     * @param chunkDTO
     */
    @SuppressWarnings("all")
    private synchronized long saveToRedis(FileChunkQO chunkDTO) {
        Set<Integer> uploaded = (Set<Integer>) redisUtils.hget(chunkDTO.getIdentifier(), "uploaded");
        if (uploaded == null) {
            uploaded = new HashSet<>(Arrays.asList(chunkDTO.getChunkNum()));
            HashMap<String, Object> objectObjectHashMap = new HashMap<>();
            objectObjectHashMap.put("uploaded", uploaded);
            objectObjectHashMap.put("totalChunks", chunkDTO.getTotalChunks());
            objectObjectHashMap.put("totalSize", chunkDTO.getTotalSize());
            objectObjectHashMap.put("path", getFileRelativelyPath(chunkDTO.getIdentifier(), chunkDTO.getFileName()));
            redisUtils.hmset(chunkDTO.getIdentifier(), objectObjectHashMap, 60);
        } else {
            uploaded.add(chunkDTO.getChunkNum());
            redisUtils.hset(chunkDTO.getIdentifier(), "uploaded", uploaded, 60);
        }
        return uploaded.size();
    }

    /**
     * 得到文件的绝对路径
     *
     * @param identifier
     * @param filename
     * @return
     */
    private String getFilePath(String identifier, String filename) {
        String ext = filename.substring(filename.lastIndexOf("."));
        return getFileFolderPath(identifier) + filename + ext;
    }

    /**
     * 得到文件的相对路径
     *
     * @param identifier
     * @param filename
     * @return
     */
    private String getFileRelativelyPath(String identifier, String filename) {
        String ext = filename.substring(filename.lastIndexOf("."));
        return File.separator + identifier + File.separator + filename + ext;
    }


    /**
     * 得到分块文件所属的目录
     *
     * @param identifier
     * @return
     */
    private String getChunkFileFolderPath(String identifier) {
        return getFileFolderPath(identifier) + "chunks" + File.separator;
    }

    /**
     * 得到文件所属的目录
     *
     * @param identifier
     * @return
     */
    private String getFileFolderPath(String identifier) {
        return ServerSystemUtil.userDir() + File.separator + UPLOAD_FOLDER + File.separator + identifier + File.separator;
    }
}
