package com.hdkj.lt.utils.file;

import com.google.common.collect.Lists;
import com.hdkj.lt.base.exception.ExceptionCast;
import com.hdkj.lt.utils.serialize.JsonUtils;
import lombok.SneakyThrows;
import org.apache.commons.codec.Charsets;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @Description:
 * @Author: wenhao
 * @Create: 2022/10/14 12:33
 * @Version: 1.0
 */
public class FileUtils {

    private FileUtils() {
    }

    // 文件格式
    // 图片
    public static final List<String> FORMAT_PICS = Lists.newArrayList("JPEG", "JPG", "PNG", "GIF", "TIFF", "BMP", "DWG", "PSD");
    // 文档
    public static final List<String> FORMAT_DOCS = Lists.newArrayList("RTF", "XML", "HTML", "CSS", "JS", "EML", "DBX", "PST", "XLS", "XLSX", "DOC",
            "DOCX", "VSD", "MDB", "WPS", "WPD", "EPS", "PDF", "QDF", "PWL", "ZIP", "RAR", "JSP", "JAVA", "CLASS", "JAR", "MF", "EXE", "CHM");
    // 视频
    public static final List<String> FORMAT_VIDEOS = Lists.newArrayList("AVI", "RAM", "RM", "MPG", "MOV", "ASF", "MP4", "FLV", "MID");
    // 音频
    public static final List<String> FORMAT_AUDIOS = Lists.newArrayList("WAV", "MP3");

    // 文件名特殊字符及路径中包含"../","./"
    private static final Pattern FILE_PATTERN = Pattern.compile("[*?\"<>|]|(\\.\\.|\\.)/|(/+\\S?:|(/?~))");

    // 缓冲区大小
    public static final int BUFFER_SIZE = 1024 * 4;

    /**
     * 根据文件路径获取文件
     *
     * @param filePath
     * @return
     */
    @SneakyThrows
    public static File createFile(String filePath) {
        if (StringUtils.isBlank(filePath)) {
            ExceptionCast.cast("文件路径为空，操作失败！");
        }
        filePath = disposeFilePath(filePath);
        File file = new File(filePath);
        if (!file.exists()) {
            File fileDir = new File(file.getParentFile().getPath());
            if (!fileDir.exists()) {
                fileDir.mkdirs();
            }
            file.createNewFile();
        }
        return file;
    }

    /**
     * 根据文件路径获取文件
     *
     * @param parent
     * @param child
     */
    @SneakyThrows
    public static File createFile(String parent, String child) {
        if (StringUtils.isBlank(parent) || StringUtils.isBlank(child)) {
            ExceptionCast.cast("文件路径为空，操作失败！");
        }
        parent = disposeFilePath(parent);
        child = disposeFilePath(child);
        File file = new File(parent, child);
        if (!file.exists()) {
            File fileDir = new File(parent);
            if (!fileDir.exists()) {
                fileDir.mkdirs();
            }
            file.createNewFile();
        }
        return file;
    }

    /**
     * 根据URI地址获取文件
     *
     * @param uri
     */
    @SneakyThrows
    public static File createFile(URI uri) {
        if (Objects.isNull(uri)) {
            ExceptionCast.cast("URI地址为空，操作失败！");
        }
        if (checkFilePath(String.valueOf(uri))) {
            ExceptionCast.cast("文件路径包含特殊字符，操作失败！");
        }
        File file = new File(uri);
        if (!file.exists()) {
            file.createNewFile();
        }
        return file;
    }

    public static File mkdirs(String pathName) {
        if (StringUtils.isBlank(pathName)) {
            ExceptionCast.cast("文件路径为空，操作失败！");
        }
        pathName = disposeFilePath(pathName);
        File file = new File(pathName);
        if (!file.exists()) {
            file.mkdirs();
        }
        return file;
    }

    /**
     * 处理非法文件路径
     *
     * @param filePath
     * @return
     */
    public static String disposeFilePath(String filePath) {
        //先将Windows得双斜杠换成通用得单斜杠
        String strPath = filePath.replace("\\", "/");
        Matcher matcher = FILE_PATTERN.matcher(strPath);
        return matcher.replaceAll("");
    }

    /**
     * 非法文件路径的校验
     *
     * @param filePath
     * @return
     */
    private static boolean checkFilePath(String filePath) {
        //先将Windows得双斜杠换成通用得单斜杠
        String strPath = filePath.replace("\\", "/");
        Matcher matcher = FILE_PATTERN.matcher(strPath);
        return matcher.find();
    }

    /**
     * 请求上传的文件类转File类
     *
     * @param multipartFile
     * @return
     */
    public static File multipartFile2File(MultipartFile multipartFile) {
        if (Objects.isNull(multipartFile) || multipartFile.getSize() <= 0) {
            return null;
        }
        String originalFilename = disposeFilePath(multipartFile.getOriginalFilename());
        File file = new File(originalFilename);
        try (InputStream is = new BufferedInputStream(multipartFile.getInputStream());
             OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
            byte[] buffer = new byte[BUFFER_SIZE];
            int readLine;
            while ((readLine = is.read(buffer)) != -1) {
                os.write(buffer, 0, readLine);
            }
            os.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return file;
    }

    /**
     * File类转请求上传的文件类
     *
     * @param file
     * @return
     */
    @SneakyThrows
    public static MultipartFile file2MultipartFile(File file) {
        String fileName = file.getName();
        FileItem fileItem = new DiskFileItem(fileName, Files.probeContentType(file.toPath()), false, fileName, (int) file.length(), file.getParentFile());
        try (InputStream is = new FileInputStream(file);
             OutputStream os = fileItem.getOutputStream()) {
            byte[] buffer = new byte[BUFFER_SIZE];
            int readLine;
            while ((readLine = is.read(buffer)) != -1) {
                os.write(buffer, 0, readLine);
            }
            os.flush();
            return new CommonsMultipartFile(fileItem);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取文件后缀名
     *
     * @param file
     * @return
     */
    public static String getSuffix(File file) {
        Assert.notNull(file, "文件不允许为空");
        if (file.getName().lastIndexOf(".") != -1 && file.getName().lastIndexOf(".") != 0) {
            return file.getName().substring(file.getName().lastIndexOf(".") + 1);
        }
        return StringUtils.EMPTY;
    }

    /**
     * 输入流转byte字节数组
     *
     * @param is
     * @return
     */
    public static byte[] inputStream2ByteArray(InputStream is) {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[BUFFER_SIZE];
            int readLine;
            while ((readLine = is.read(buffer)) != -1) {
                baos.write(buffer, 0, readLine);
            }
            baos.flush();
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new byte[]{};
    }

    /**
     * 输入流转文件
     *
     * @param is   转换前的输入流
     * @param file 转换后的文件
     */
    private static void inputStream2File(InputStream is, File file) {
        try (OutputStream os = new FileOutputStream(file)) {
            int readLine;
            byte[] buffer = new byte[BUFFER_SIZE];
            while ((readLine = is.read(buffer)) != -1) {
                os.write(buffer, 0, readLine);
            }
            os.flush();
            is.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 文件转byte字节数组
     *
     * @param file 转换前的文件
     * @return 转换后的byte字节数组
     */
    public static byte[] file2ByteArray(File file) {
        try (InputStream is = new BufferedInputStream(new FileInputStream(file))) {
            return inputStream2ByteArray(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new byte[]{};
    }

    /**
     * byte字节数组转文件
     *
     * @param bytes
     * @param targetPath
     * @return
     */
    public static File byteArray2File(byte[] bytes, String targetPath) {
        File targetFile = createFile(targetPath);
        String targetDir = targetFile.getParent();
        if (!targetFile.exists()) {
            createFile(targetDir).mkdir();
        }
        try (InputStream is = new ByteArrayInputStream(bytes);
             OutputStream os = new BufferedOutputStream(new FileOutputStream(targetFile))) {
            byte[] buffer = new byte[BUFFER_SIZE];
            int readLine;
            while ((readLine = is.read(buffer)) != -1) {
                os.write(buffer, 0, readLine);
            }
            os.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return targetFile;
    }

    public static File writeJsonFile(String filePath, String jsonString) {
        return writeJsonFile(filePath, jsonString, false);
    }

    /**
     * 写入json文件
     *
     * @param filePath   文件路径
     * @param jsonString json字符串
     * @param wantFormat 是否需要格式化
     * @return
     */
    public static File writeJsonFile(String filePath, String jsonString, boolean wantFormat) {
        File jsonFile = createFile(filePath);
        String targetDir = filePath.substring(0, filePath.lastIndexOf(File.separator));
        if (!jsonFile.exists()) {
            createFile(targetDir).mkdir();
        }
        try (FileWriter jsonFw = new FileWriter(jsonFile)) {
            if (wantFormat) {
                jsonString = JsonUtils.formatJson(jsonString);
            }
            jsonFw.write(jsonString);
            jsonFw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return jsonFile;
    }

    /**
     * 下载通用方法
     *
     * @param response 响应类
     * @param file     要下载的文件
     * @param fileName 下载后的文件名
     */
    @SneakyThrows
    public static void download(HttpServletResponse response, File file, String fileName) {
        Assert.notNull(file, "下载的文件不允许为空");
        response.reset();
        response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment;filename=%s", URLEncoder.encode(fileName, Charsets.UTF_8.toString())));
        response.setHeader(HttpHeaders.SET_COOKIE, "cookiename=cookievalue;path=/;Domain=domainvaule;Max-age=seconds;HttpOnly");
        response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache");
        response.setHeader(HttpHeaders.PRAGMA, "no-cache");
        response.setCharacterEncoding(Charsets.UTF_8.toString());
        response.setContentLength((int) file.length());
        try (InputStream is = new BufferedInputStream(new FileInputStream(file));
             OutputStream os = new BufferedOutputStream(response.getOutputStream())) {
            byte[] buffer = new byte[BUFFER_SIZE];
            int readLine;
            while ((readLine = is.read(buffer)) != -1) {
                os.write(buffer, 0, readLine);
            }
            os.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            file.delete();
        }
    }

    /**
     * 下载zip文件通用方法
     *
     * @param response      响应类
     * @param downloadFiles 要压缩的文件集合
     * @param fileName      下载后的文件名
     */
    @SneakyThrows
    public static void downloadZip(HttpServletResponse response, List<File> downloadFiles, String fileName) {
        Assert.isTrue(CollectionUtils.isNotEmpty(downloadFiles), "下载的文件不允许为空");
        response.reset();
        response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
        response.setHeader(HttpHeaders.CONTENT_TYPE, "application/zip");
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment;filename=%s", URLEncoder.encode(fileName, Charsets.UTF_8.toString())));
        response.setHeader(HttpHeaders.SET_COOKIE, "cookiename=cookievalue;path=/;Domain=domainvaule;Max-age=seconds;HttpOnly");
        response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache");
        response.setHeader(HttpHeaders.PRAGMA, "no-cache");
        response.setCharacterEncoding(Charsets.UTF_8.toString());
        try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
            for (File downloadFile : downloadFiles) {
                try (InputStream is = new BufferedInputStream(new FileInputStream(downloadFile))) {
                    zos.putNextEntry(new ZipEntry(downloadFile.getName())); //解决同名文件无法下载
                    byte[] buffer = new byte[BUFFER_SIZE];
                    int readLine;
                    while ((readLine = is.read(buffer)) != -1) {
                        zos.write(buffer, 0, readLine);
                    }
                    zos.flush();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            downloadFiles.forEach(File::delete);
        }
    }

    /**
     * 文件拆分
     *
     * @param file       源文件
     * @param fileFolder 分割后文件所在的目录
     * @param size       分割文件大小，以KB为单位
     * @param fileSuffix 分隔后的文件后缀名
     */
    public static void cut(File file, File fileFolder, long size, String fileSuffix) {
//        size = size * 1024;
        int maxx = 0;
        long fileLength = file.length(); //取得文件大小
        long cutNum = fileLength / size; //取得文件要分割的个数
        int i = 0;
        int j = 0;
        try (RandomAccessFile inRaf = new RandomAccessFile(file, "r")) {
            for (; j < cutNum; j++) {
                String cutFileName = file.getName().concat(String.valueOf(System.currentTimeMillis())).concat(fileSuffix);
                File cutFile = new File(fileFolder, cutFileName);
                try (RandomAccessFile outRaf = new RandomAccessFile(cutFile, "rw")) {
                    maxx += size;
                    for (; i < maxx; i++) {
                        outRaf.write(inRaf.read());
                    }
                }
            }
            // 将最后不足size的字节内容输出
//            String cutFileName = file.getName().concat(String.valueOf(System.currentTimeMillis())).concat(fileSuffix);
//            File cutFile = new File(fileFolder, cutFileName);
//            RandomAccessFile outRaf = new RandomAccessFile(cutFile, "rw");
//            for (; i < fileLength; i++) {
//                outRaf.write(inRaf.read());
//            }
//            outRaf.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 拆分后的文件合并
     *
     * @param fileName   合并后的文件路径
     * @param fileFolder 分割后的文件所在的目录
     * @param fileSuffix 分隔后的文件后缀名
     * @return 合并后的文件
     */
    public static File unite(String fileName, File fileFolder, final String fileSuffix) {
        File unitedFile = createFile(fileName); //取得输出名
        /**
         * FilenameFilter：文件名过滤器；
         * boolean accept(File dir, String name)；
         * dir表示文件的当前目录，name表示文件名；
         * FilenameFilter用来把符合要求的文件或目录返回；
         */
        File[] cutFiles = fileFolder.listFiles((dir, name) -> {
            String cutFile = new File(name).toString();
            return cutFile.endsWith(fileSuffix);
            // fileSuffix为final，故方法内部的匿名类可以取得
        });
        Arrays.sort(cutFiles, (f1, f2) -> StringUtils.compare(f1.getName(), f2.getName()));
        if (ArrayUtils.isNotEmpty(cutFiles)) {
            try (RandomAccessFile outRaf = new RandomAccessFile(unitedFile, "rw")) {
                // 打开所有的文件再写入一个文件里
                for (int i = 0; i < Objects.requireNonNull(cutFiles).length; i++) {
                    try (RandomAccessFile inRaf = new RandomAccessFile(cutFiles[i], "r")) {
                        byte[] buffer = new byte[BUFFER_SIZE];
                        int readLine;
                        while ((readLine = inRaf.read(buffer)) != -1) {
                            outRaf.write(buffer, 0, readLine);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return unitedFile;
    }

    /**
     * 删除文件
     *
     * @param file 被删除的文件
     */
    public static void delete(File file) {
        if (!Objects.isNull(file)) {
            String filePath = disposeFilePath(file.toURI().toString());
            File delFile = new File(filePath);
            delFile.delete();
        }
    }
    /**
     * 根据路径获取文件
     *
     * @return File
     */
    public static File customFileByPath(String pathname) {
        boolean res = true;
        if (StringUtils.isNotEmpty(pathname)) {
            //校验是否有路径遍历漏洞：true为存在
            res = checkFilePath(pathname);
        }
        if (res) {
            return null;
        } else {
            String filePath = disposeFilePath(pathname);
            return new File(filePath);
        }
    }

    /**
     * 删除文件夹下所有的文件
     * @param pathname
     */
    public static void deleteFilesByPath(String pathname) {
        File file = new File(pathname);
        if(file.isDirectory()){
            File[] children = file.listFiles();
            if(children != null){
                for (File child : children) {
                    child.delete();
                }
            }
        }
    }
}