package com.qmth.teachcloud.common.util;

import com.qmth.teachcloud.common.contant.SystemConstant;
import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

public class FileUtil {
    private final static Logger log = LoggerFactory.getLogger(FileUtil.class);

    /**
     * 将网络文件保存到本地
     *
     * @param fileUrl       网络文件URL
     * @param localFilePath 例如D:/123.txt
     * @throws IOException
     */
    public static void saveUrlAsFile(String fileUrl, String localFilePath) {
        HttpURLConnection connection = null;
        DataOutputStream dataOutputStream = null;
        DataInputStream dataInputStream = null;
        try {
            URL url = new URL(fileUrl);

            connection = (HttpURLConnection) url.openConnection();

            dataInputStream = new DataInputStream(connection.getInputStream());
            FileOutputStream fileOutputStream = new FileOutputStream(localFilePath);
            dataOutputStream = new DataOutputStream(fileOutputStream);

            byte[] buffer = new byte[4096];
            int count;
            while ((count = dataInputStream.read(buffer)) > 0) {
                dataOutputStream.write(buffer, 0, count);
            }
            fileOutputStream.flush();
            dataOutputStream.flush();
        } catch (IOException e) {
            throw ExceptionResultEnum.ERROR.exception("下载文件出错" + e.getMessage());
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
            if (dataOutputStream != null) {
                try {
                    dataOutputStream.close();
                } catch (IOException e) {
                }
            }
            if (dataInputStream != null) {
                try {
                    dataInputStream.close();
                } catch (IOException e) {
                }
            }
        }
    }

    /**
     * 将网络文件保存到本地
     *
     * @param fileUrl   网络文件URL
     * @param localFile 本地文件对象
     * @throws IOException
     */
    public static void saveUrlAsFile(String fileUrl, File localFile) {
        saveUrlAsFile(fileUrl, localFile.getAbsolutePath());
    }

    /**
     * 读取文件内容
     *
     * @param file
     * @return
     * @throws IOException
     */
    public static String readFileContent(File file) {
        StringBuilder content = new StringBuilder();
        InputStreamReader streamReader = null;
        BufferedReader bufferedReader = null;
        try {
            String encoding = SystemConstant.CHARSET_NAME;
            if (file.exists() && file.isFile()) {
                streamReader = new InputStreamReader(new FileInputStream(file), encoding);
                bufferedReader = new BufferedReader(streamReader);
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    content.append(line);
                }
            }
        } catch (IOException e) {
            throw ExceptionResultEnum.ERROR.exception("读取文件出错" + e.getMessage());
        } finally {
            if (streamReader != null) {
                try {
                    streamReader.close();
                } catch (IOException e) {
                }
            }
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                }
            }
        }
        return content.toString();
    }

    /**
     * 创建文件目录
     */
    public static boolean makeDirs(String path) {
        if (path == null || "".equals(path)) {
            return false;
        }
        File folder = new File(path);
        if (!folder.exists()) {
            return folder.mkdirs();
        }
        return true;
    }

    /**
     * 保存字符串到文件中 @throws IOException @throws
     */
    public static void saveAsFile(String path, String content) {
        saveAsFile(path, content, null);
    }

    public static void saveAsFile(String path, String content, String encoding) {
        if (path == null || content == null) {
            return;
        }

        if (encoding == null) {
            encoding = SystemConstant.CHARSET_NAME;
        }
        BufferedWriter bw = null;
        try {
            File file = new File(path);
            if (!file.exists()) {
                if (makeDirs(file.getParent())) {
                    boolean ok = file.createNewFile();
                    if (!ok) {
                        throw ExceptionResultEnum.ERROR.exception("文件创建失败！");
                    }
                }
            }
            IOUtils.write(content.getBytes(StandardCharsets.UTF_8), new FileOutputStream(file));
        } catch (IOException e) {
            throw ExceptionResultEnum.ERROR.exception("保存文件出错" + e.getMessage());
        } finally {
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                }
            }
        }
    }

    /**
     * 解压文件
     *
     * @param targetDir 解压目录
     * @param zipFile   待解压的ZIP文件
     * @throws IOException
     */
    public static List<File> unZip(File targetDir, File zipFile) throws IOException {
        if (targetDir == null) {
            throw ExceptionResultEnum.ERROR.exception("解压目录不能为空！");
        }

        if (zipFile == null) {
            throw ExceptionResultEnum.ERROR.exception("待解压的文件不能为空！");
        }

        if (!zipFile.exists()) {
            throw ExceptionResultEnum.ERROR.exception("待解压的文件不存在！" + zipFile.getAbsolutePath());
        }

        String zipName = zipFile.getName().toLowerCase();
        if (zipFile.isDirectory() || zipName.indexOf(SystemConstant.ZIP_PREFIX) < 0) {
            throw ExceptionResultEnum.ERROR.exception("待解压的文件格式错误！");
        }

        if (!targetDir.exists()) {
            targetDir.mkdir();
        }

        List<File> result = new LinkedList<>();

        try (ZipFile zip = new ZipFile(zipFile, StandardCharsets.UTF_8);) {

            Enumeration entries = zip.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = (ZipEntry) entries.nextElement();

                // Linux中需要替换掉路径的反斜杠
                String entryName = replaceSplit((File.separator + entry.getName()));

                String filePath = targetDir.getAbsolutePath() + entryName;
                File target = new File(filePath);
                if (entry.isDirectory()) {
                    target.mkdirs();
                } else {
                    File dir = target.getParentFile();
                    if (!dir.exists()) {
                        dir.mkdirs();
                    }

                    try (OutputStream os = new FileOutputStream(target); InputStream is = zip.getInputStream(entry);) {
                        IOUtils.copy(is, os);
                        os.flush();
                    }
                    result.add(target);
                }
            }

        }

        return result;
    }

    /**
     * 文件压缩
     *
     * @param target 目录或文件 @param zipFile 压缩后的ZIP文件 @throws IOException @throws
     */
    public static boolean doZip(File target, File zipFile) throws IOException {
        if (target == null || !target.exists()) {
            throw ExceptionResultEnum.ERROR.exception("目录或文件不能为空！");
        }

        if (zipFile == null) {
            throw ExceptionResultEnum.ERROR.exception("待压缩的文件不能为空！");
        }

        try (OutputStream outStream = new FileOutputStream(zipFile);
             ZipOutputStream zipOutStream = new ZipOutputStream(outStream, StandardCharsets.UTF_8);) {
            if (!zipFile.exists()) {
                boolean ok = zipFile.createNewFile();
                if (!ok) {
                    throw ExceptionResultEnum.ERROR.exception("压缩的文件创建失败！");
                }
            }

            if (target.isDirectory()) {
                File[] files = target.listFiles();
                if (files.length == 0) {
                    throw ExceptionResultEnum.ERROR.exception("文件夹内未找到任何文件！");
                }

                for (File file : files) {
                    doZip(zipOutStream, file, null);
                }
            } else {
                doZip(zipOutStream, target, null);
            }
        }

        return true;
    }

    /**
     * @param resultFile
     * @param sourseFiles
     * @return
     * @throws IOException
     */
    public static boolean doZip(File resultFile, List<File> sourseFiles) {
        if (sourseFiles == null || sourseFiles.size() == 0) {
            throw ExceptionResultEnum.ERROR.exception("待压缩的文件不能为空！");
        }

        if (resultFile == null) {
            throw ExceptionResultEnum.ERROR.exception("压缩后的文件不能为空！");
        }
        ZipOutputStream zipOutStream = null;
        try {
            OutputStream outStream = new FileOutputStream(resultFile);
            zipOutStream = new ZipOutputStream(outStream, StandardCharsets.UTF_8);
            if (!resultFile.exists()) {
                boolean ok = resultFile.createNewFile();
                if (!ok) {
                    throw ExceptionResultEnum.ERROR.exception("压缩的文件创建失败！");
                }
            }

            for (File file : sourseFiles) {
                doZip(zipOutStream, file, null);
            }
        } catch (IOException e) {
            throw ExceptionResultEnum.ERROR.exception("压缩的文件创建失败！" + e.getMessage());
        } finally {
            if (zipOutStream != null) {
                try {
                    zipOutStream.close();
                } catch (IOException e) {
                }
            }
        }

        return true;
    }

    private static void doZip(ZipOutputStream zipOutStream, File target, String parentDir) throws IOException {
        if (parentDir == null) {
            parentDir = "";
        }

        if (!"".equals(parentDir) && !parentDir.endsWith(File.separator)) {
            parentDir += File.separator;
        }

        if (target.isDirectory()) {
            File[] files = target.listFiles();
            if (files.length > 0) {
                for (File file : files) {
                    doZip(zipOutStream, file, parentDir + target.getName());
                }
            } else {
                zipOutStream.putNextEntry(new ZipEntry(parentDir + target.getName()));
                zipOutStream.closeEntry();
            }
        } else {
            try (InputStream is = new FileInputStream(target);) {
                zipOutStream.putNextEntry(new ZipEntry(parentDir + target.getName()));
                int len;
                byte[] bytes = new byte[1024];
                while ((len = is.read(bytes)) > 0) {
                    zipOutStream.write(bytes, 0, len);
                }
            }
            zipOutStream.closeEntry();
        }
    }

    public static void deleteFolder(String path) {

        File file = new File(path);
        if (file.exists()) {
            if (file.isFile()) {
                deleteFile(path);
            } else {
                deleteDirectory(path);
            }
        }
    }

    public static void deleteFile(String path) {
        File file = new File(path);
        deleteFile(file);
    }

    public static void deleteFile(File file) {
        if (file.isFile() && file.exists()) {
            file.delete();
        }
    }

    public static void deleteDirectory(File dirFile) {
        if (!dirFile.exists() || !dirFile.isDirectory()) {
            return;
        }
        File[] files = dirFile.listFiles();
        if (files != null) {
            for (int i = 0; i < files.length; i++) {
                if (files[i].isFile()) {
                    deleteFile(files[i]);
                } else {
                    deleteDirectory(files[i]);
                }
            }
        }

        dirFile.delete();
    }

    public static void deleteDirectory(String path) {
        if (!path.endsWith(File.separator)) {
            path = path + File.separator;
        }
        File dirFile = new File(path);
        deleteDirectory(dirFile);
    }

    private static Cipher initAESCipher(String sKey, String vector, int cipherMode) throws Exception {
        byte[] raw;
        raw = sKey.getBytes();
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        IvParameterSpec iv = new IvParameterSpec(vector.getBytes());
        cipher.init(cipherMode, skeySpec, iv);
        return cipher;
    }

    /**
     * 解密文件
     *
     * @param sourceFile  源文件
     * @param decryptFile 解密后的文件
     * @param sKey        密钥
     * @param vc          向量
     * @throws Exception
     */
    public static void decryptFile(File sourceFile, File decryptFile, String sKey, String vc) {
        decryptOrEncryptFile(Cipher.DECRYPT_MODE, sourceFile, decryptFile, sKey, vc);
    }

    /**
     * 加密文件
     *
     * @param sourceFile  源文件
     * @param encryptFile 加密后的文件
     * @param sKey        密钥
     * @param vc          向量
     * @throws Exception
     */
    public static void encryptFile(File sourceFile, File encryptFile, String sKey, String vc) {
        decryptOrEncryptFile(Cipher.ENCRYPT_MODE, sourceFile, encryptFile, sKey, vc);
    }

    private static void decryptOrEncryptFile(int cipherMode, File sourceFile, File targetFile, String sKey, String vc) {
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            Cipher cipher = initAESCipher(sKey, vc, cipherMode);
            inputStream = new FileInputStream(sourceFile);
            outputStream = new FileOutputStream(targetFile);
            CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);
            byte[] buffer = new byte[1024];
            int r;
            while ((r = inputStream.read(buffer)) >= 0) {
                cipherOutputStream.write(buffer, 0, r);
            }
            cipherOutputStream.close();
        } catch (Exception e) {
            throw ExceptionResultEnum.ERROR.exception("加解密文件失败！" + e.getMessage());
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
            }
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
            }
        }
    }

    /**
     * 文件转数组
     *
     * @param file
     * @return
     * @throws IOException
     */
    public static byte[] fileConvertToByteArray(File file) throws IOException {
        byte[] data = null;
        FileInputStream fis = null;
        ByteArrayOutputStream baos = null;
        try {
            fis = new FileInputStream(file);
            baos = new ByteArrayOutputStream();
            int len;
            byte[] buffer = new byte[1024];
            while ((len = fis.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            data = baos.toByteArray();
        } catch (Exception e) {
            log.error(SystemConstant.LOG_ERROR, e);
        } finally {
            if (Objects.nonNull(fis)) {
                fis.close();
            }
            if (Objects.nonNull(baos)) {
                baos.flush();
                baos.close();
            }
        }
        return data;
    }

    public static File multipartFileToFile(MultipartFile file) throws Exception {

        File toFile = null;
        if (file.equals("") || file.getSize() <= 0) {
            file = null;
        } else {
            InputStream ins = null;
            ins = file.getInputStream();
            toFile = new File(file.getOriginalFilename());
            inputStreamToFile(ins, toFile);
            ins.close();
        }
        return toFile;
    }

    //获取流文件
    private static void inputStreamToFile(InputStream ins, File file) {
        try {
            OutputStream os = new FileOutputStream(file);
            int bytesRead = 0;
            byte[] buffer = new byte[8192];
            while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            os.close();
            ins.close();
        } catch (Exception e) {
            log.error(SystemConstant.LOG_ERROR, e);
        }
    }

    /**
     * 复制文件
     *
     * @param sourceFileName 源文件
     * @param destFileName   目标文件
     */
    public static void copyFile(String sourceFileName, String destFileName) {
        try {
            File sourceFile = new File(sourceFileName);
            if (!sourceFile.exists()) {
                throw ExceptionResultEnum.ERROR.exception("源文件" + sourceFileName + "不存在");
            }

            File desFile = new File(destFileName);
            if (!desFile.exists()) {
                desFile.getParentFile().mkdirs(); //目标文件目录不存在的话需要创建目录
                desFile.createNewFile();
            }

            FileUtils.copyFile(sourceFile, desFile);
        } catch (IOException e) {
            throw ExceptionResultEnum.ERROR.exception(e.getMessage());
        }
    }

    public static void outputFile(HttpServletResponse response, File file, String fileName) {
        try {
            if (!file.exists()) {
                response.sendError(404, "File not found!");
            }

            BufferedInputStream br = new BufferedInputStream(new FileInputStream(file));
            String fName = URLEncoder.encode(fileName, SystemConstant.CHARSET_NAME);

            response.reset();
            response.setContentType("application/x-msdownload");
            response.setHeader("Content-Disposition", "attachment; filename=" + fName);
            IOUtils.copy(br, response.getOutputStream());
            br.close();
//            outStream.close();
        } catch (IOException e) {
            log.error(SystemConstant.LOG_ERROR, e);
        }
    }

    public static void outputFile(HttpServletResponse response, InputStream inputStream, String fileName) {
        try {
            BufferedInputStream br = new BufferedInputStream(inputStream);
            fileName = fileName.replaceAll("/", "\\\\");
            String fName = URLEncoder.encode(fileName, SystemConstant.CHARSET_NAME);

            response.reset();
            response.setContentType("application/x-msdownload");
            response.setHeader("Content-Disposition", "attachment; filename=" + fName);
            IOUtils.copy(br, response.getOutputStream());
            br.close();
//            outStream.close();
        } catch (IOException e) {
            log.error(SystemConstant.LOG_ERROR, e);
        }
    }

    public static InputStream getStream(String path) {
        try {
            ClassLoader classLoader = FileUtil.class.getClassLoader();
            URL url = classLoader.getResource(path);
            return url.openStream();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 将路径中的双"//"替换成单"/"
     *
     * @param path 文件路径
     * @return string
     */
    public static String replaceSplit(String path) {
        return path.replaceAll("\\\\", SystemConstant.ORG_SPLIT);
    }

    public static String md5File(File file) {
        try {
            return md5File(new FileInputStream(file));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static String md5File(InputStream inputStream) {
        try {
            return DigestUtils.md5Hex(inputStream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 生成文本文件
     *
     * @param file    文件
     * @param content 要生成的文件内容
     */
    public static File writeContent(File file, String content) {
        try {
            if (!file.exists()) {
                file.getParentFile().mkdirs();
                file.createNewFile();
            }
            IOUtils.write(content.getBytes(StandardCharsets.UTF_8), new FileOutputStream(file));
        } catch (Exception e) {
            log.error(SystemConstant.LOG_ERROR, e);
        }
        return file;
    }

    /**
     * 下载加密zip文件
     *
     * @param response         response
     * @param downloadPathFile 压缩文件所在文件夹路径
     * @param zipFileName      压缩zip文件路径+文件名
     */
    public static void downloadEncryptZip(HttpServletResponse response, File downloadPathFile, String zipFileName) {
        // 压缩zip文件和图片保存文件夹放在同一级
        File zipFile = new File(downloadPathFile.getParent(), zipFileName);
        try {
            // 压缩zip文件路径+文件名
            String zipFilePath = zipFile.getPath();
            // 待压缩文件所在文件夹
            String zipFilesPath = downloadPathFile.getPath();
            Zip4jUtil.zipEncryptFile(zipFilePath, zipFilesPath, SystemConstant.ZIP_ENCRYPT_PWD);
            FileUtil.outputFile(response, zipFile, zipFile.getName());
        } catch (Exception e) {
            throw ExceptionResultEnum.ERROR.exception("下载失败");
        }
    }

}