package com.tiesheng.core.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.tiesheng.util.exception.ApiException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Service
public class ToolsFileService {

    private String ext;
    private String absPath;
    private String folder = "/upload";

    ///////////////////////////////////////////////////////////////////////////
    // 方法
    ///////////////////////////////////////////////////////////////////////////


    /**
     * 检查文件类型
     *
     * @param fileType
     */
    private void checkFileType(String fileType) {
        if (!StrUtil.isEmpty(ext) && !StrUtil.split(ext, ",").contains(fileType)) {
            throw new ApiException("该文件格式不支持上传");
        }
    }

    /**
     * 获取upload目录的绝对路径
     *
     * @param dest 文件名称，如果没有/upload，则会添加/upload
     * @return
     */
    public String getUploadAbsPath(String dest) {
        String destFile = absPath;
        if (StrUtil.isEmpty(destFile)) {
            ClassPathResource classPathResource = new ClassPathResource("static");
            File rootFile = new File(classPathResource.getPath());
            destFile = rootFile.getAbsolutePath();
        }

        if (!StrUtil.startWith(dest, folder)) {
            destFile = destFile + folder;
        }

        return String.format("%s/%s", destFile, dest);
    }

    /**
     * 保存文件
     *
     * @param file
     */
    public String saveMultipartFile(MultipartFile file) {
        try {

            // 获取文件类型
            String fileType = FileNameUtil.extName(file.getOriginalFilename());
            if (StrUtil.isEmpty(fileType)) {
                fileType = FileTypeUtil.getType(file.getInputStream(), file.getOriginalFilename());
            }
            if (StrUtil.isEmpty(fileType)) {
                fileType = "unknow";
            }
            checkFileType(fileType);

            String filePath = String.format("/%s/%s.%s", DateUtil.format(DateUtil.date(), "yyyy-MM"), IdUtil.simpleUUID(), fileType);

            InputStream stream = file.getInputStream();
            FileUtil.writeFromStream(stream, getUploadAbsPath(filePath));
            IoUtil.close(stream);

            return folder + filePath;
        } catch (Exception e) {
            throw new ApiException("文件流读取失败:" + e.getMessage());
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // 文件分包上传
    ///////////////////////////////////////////////////////////////////////////

    /**
     * 得到块文件的根目录，上传完成之后便于删除
     *
     * @param fileMd5
     * @return
     */
    private String getChunkFolder(String fileMd5) {
        String folder = getUploadAbsPath(fileMd5);
        FileUtil.mkdir(folder);
        return folder;
    }

    /**
     * 检查待上传文件是否存在
     *
     * @param fileMd5
     * @param fileExt
     */
    public void register(String fileMd5, String fileExt) {
        checkFileType(fileExt);
    }

    /**
     * 检查块文件
     *
     * @param fileMd5
     * @param chunk
     * @return
     */
    public boolean checkChunk(String fileMd5, Integer chunk) {
        // 1，得到块文件所在路径
        String chunkFileFolder = getChunkFolder(fileMd5);
        // 2，块文件的文件名称以1,2,3..序号命名，没有扩展名
        File chunkFile = FileUtil.file(chunkFileFolder, chunk.toString());
        return chunkFile.exists();
    }

    /**
     * 块文件上传
     *
     * @param file
     * @param fileMd5
     * @param chunk
     */
    public void uploadChunk(MultipartFile file, String fileMd5, Integer chunk) {
        if (file == null) {
            throw new ApiException("请选择文件后上传");
        }

        try {
            // 块文件
            File chunkfile = new File(getChunkFolder(fileMd5), chunk.toString());
            InputStream stream = file.getInputStream();
            FileUtil.writeFromStream(stream, chunkfile);
            IoUtil.close(stream);
        } catch (Exception ignored) {
            throw new ApiException("块文件上传失败");
        }
    }

    /**
     * 合并块文件
     *
     * @param fileMd5
     * @param fileExt
     */
    public String mergeChunks(String fileMd5, String fileExt) {

        // 1，获取块文件的路径
        File chunkFileFolder = new File(getChunkFolder(fileMd5));

        // 2，合并文件目录
        String filePath = String.format("/%s/%s.%s", DateUtil.format(DateUtil.date(), "yyyy-MM"), fileMd5, fileExt);
        String outputPath = getUploadAbsPath(filePath);

        // 3，如果文件不存在
        if (!FileUtil.exist(outputPath)) {

            // 4，获取块文件，此列表是已经排好序的列表
            List<File> chunkFiles = getChunkFiles(chunkFileFolder);

            // 5，合并文件
            File newFile = FileUtil.newFile(outputPath);
            for (File file : chunkFiles) {
                byte[] bytes = FileUtil.readBytes(file);
                FileUtil.writeBytes(bytes, newFile, 0, bytes.length, true);
            }
        }

        // 6，删除块文件目录
        FileUtil.del(chunkFileFolder);

        return folder + filePath;

    }

    /**
     * 获取所有块文件
     *
     * @param chunkFileFolder
     * @return
     */
    private List<File> getChunkFiles(File chunkFileFolder) {

        //获取路径下的所有块文件
        File[] chunkFiles = chunkFileFolder.listFiles();
        if (chunkFiles == null) {
            return new ArrayList<>();
        }
        //将文件数组转成list，并排序
        List<File> chunkFileList = new ArrayList<>(Arrays.asList(chunkFiles));
        //排序
        chunkFileList.sort((o1, o2) -> {
            if (Integer.parseInt(o1.getName()) > Integer.parseInt(o2.getName())) {
                return 1;
            }
            return -1;
        });
        return chunkFileList;
    }


    ///////////////////////////////////////////////////////////////////////////
    // setter、getter
    ///////////////////////////////////////////////////////////////////////////

    public String getAbsPath() {
        return absPath;
    }

    public void setAbsPath(String absPath) {
        this.absPath = absPath;
    }

    public String getExt() {
        return ext;
    }

    public void setExt(String ext) {
        this.ext = ext;
    }

    public String getFolder() {
        return folder;
    }

    public void setFolder(String folder) {
        this.folder = folder;
    }
}
