package com.yida.data.common.core.utils;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.google.common.base.Preconditions;
import com.yida.data.common.core.entity.constant.FebsConstant;
import com.yida.data.common.core.exception.FebsException;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
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.apache.poi.util.IOUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.util.FileSystemUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import sun.misc.BASE64Encoder;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.HashMap;
import java.util.Locale;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author MrBird
 */
@Slf4j
public abstract class FileUtil {

    private static final int BUFFER = 1024 * 8;
    private static final String TEMP_DIR = System.getProperty("user.dir") + "/upload/";
    private static final String VIDEO_TYPE = "{mp4}";

    /**
     * 获取本地文件上传路径
     *
     * @return java.lang.String
     * @author ZYJ
     * @date 2021/8/23 14:33
     */
    public static String getLocalUploadAddress() {
        LocalDate now = LocalDate.now();
        // 根据时间生成对应的文件夹
        String path = TEMP_DIR + now.getYear() + "/" + now.getMonthValue() + "/" + now.getDayOfMonth() + "/";
        if (!cn.hutool.core.io.FileUtil.exist(path)) {
            cn.hutool.core.io.FileUtil.mkdir(path);
        }
        return path;
    }

    /**
     * 上传文件到本地,返回url地址
     */
    public static String uploadMultipartFile(MultipartFile file) {
        LocalDate now = LocalDate.now();
        String path = TEMP_DIR + now.getYear() + "/" + now.getMonthValue() + "/" + now.getDayOfMonth() + "/";
        if (!cn.hutool.core.io.FileUtil.exist(path)) {
            cn.hutool.core.io.FileUtil.mkdir(path);
        }
        String fileName =
            StrUtil.uuid().replace("-", "") + "." + file.getOriginalFilename()
                .substring(file.getOriginalFilename().lastIndexOf(".") + 1);
        File targetFile = new File(path + fileName);
        try {
            file.transferTo(targetFile);
        } catch (IOException e) {
            e.printStackTrace();
            throw new FebsException("上传失败");
        }
        return path + fileName;
    }

    public static String uploadBase64Img(String code, String type) {
        LocalDate now = LocalDate.now();
        String path = TEMP_DIR + now.getYear() + "/" + now.getMonthValue() + "/" + now.getDayOfMonth() + "/";
        if (!cn.hutool.core.io.FileUtil.exist(path)) {
            cn.hutool.core.io.FileUtil.mkdir(path);
        }
        String fileName = StrUtil.uuid().replace("-", "") + "." + type;
        File targetFile = new File(path + fileName);
        try {
            cn.hutool.core.io.FileUtil.writeBytes(Base64.getDecoder().decode(code), targetFile);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return path + fileName;
    }

    public static String uploadFileToMediaServer(String url, String path) {
        return uploadFileToMediaServer(url, cn.hutool.core.io.FileUtil.file(path));
    }

    public static String uploadFileToMediaServer(String url, File file) {
        String resp = HttpUtil.post(url + "/a/material/uploadAll", new HashMap<String, Object>() {
            {
                put("upload", file);
                put("source", 50);
            }
        });
//        log.info("上传响应,[{}]", resp);
        JSONObject respJson = JSONUtil.parseObj(resp);
        if (!"200".equals(respJson.get("status").toString())) {
            throw new FebsException(respJson.getStr("message"));
        }
        return respJson.getStr("data");
    }


    public static String uploadCompressBase64Img(MultipartFile file, Long maxSize) throws IOException {
        LocalDate now = LocalDate.now();
        String path =
            TEMP_DIR + now.getYear() + "/" + now.getMonthValue() + "/" + now.getDayOfMonth() + "/";
        if (!cn.hutool.core.io.FileUtil.exist(path)) {
            cn.hutool.core.io.FileUtil.mkdir(path);
        }
        String fileName =
            StrUtil.uuid().replace("-", "") + "." + file.getOriginalFilename()
                .substring(file.getOriginalFilename().lastIndexOf(".") + 1);
        File targetFile = new File(path + fileName);
        try {
            file.transferTo(targetFile);
        } catch (IOException e) {
            e.printStackTrace();
            throw new FebsException("上传失败");
        }
        // 循环压缩到1000kb以下
        while ((new File(path + fileName).length() / 1024) > maxSize) {
            Thumbnails.of(path + fileName).scale(0.9).toFile(path + fileName);
        }
        return path + fileName;
    }

    public static String compressImg(String path, Long maxSize) throws IOException {
        // 循环压缩到1000kb以下
        while ((new File(path).length() / 1024) > maxSize) {
            Thumbnails.of(path).scale(0.5).toFile(path);
        }
        return path;
    }

    public static File compressImg(File file, Long maxSize) throws IOException {
        // 循环压缩到1000kb以下
        while ((file.length() / 1024) > maxSize) {
            Thumbnails.of(file).scale(0.5).toFile(file);
        }
        return file;
    }


    public static String downloadCompressBase64Img(String url, Long maxSize) throws IOException {
        String path = downloadToLocal(url);
        File targetFile = new File(path);
        // 循环压缩到1000kb以下
        while ((new File(path).length() / 1024) > maxSize) {
            Thumbnails.of(path).scale(0.5).toFile(path);
        }
        return path;
    }

    /**
     * 压缩文件或目录
     *
     * @param fromPath 待压缩文件或路径
     * @param toPath   压缩文件，如 xx.zip
     */
    public static void compress(String fromPath, String toPath) throws IOException {
        File fromFile = new File(fromPath);
        File toFile = new File(toPath);
        if (!fromFile.exists()) {
            throw new FileNotFoundException(fromPath + "不存在！");
        }
        try (
            FileOutputStream outputStream = new FileOutputStream(toFile);
            CheckedOutputStream checkedOutputStream = new CheckedOutputStream(outputStream, new CRC32());
            ZipOutputStream zipOutputStream = new ZipOutputStream(checkedOutputStream)
        ) {
            String baseDir = "";
            compress(fromFile, zipOutputStream, baseDir);
        }
    }

    /**
     * 文件下载
     *
     * @param fileName 下载文件名称
     * @param delete   下载后是否删除源文件
     * @param response HttpServletResponse
     * @throws Exception Exception
     */
    public static void download(File file, String fileName, Boolean delete,
        HttpServletResponse response) throws Exception {
        if (!file.exists()) {
            throw new Exception("文件未找到");
        }

        String fileType = getFileType(file);
        if (!fileTypeIsValid(fileType, FebsConstant.VALID_FILE_TYPE)) {
            throw new Exception("暂不支持该类型文件下载");
        }

        response.setHeader("content-type", "application/octet-stream;charset=UTF-8");
        response.setContentType("application/octet-stream;charset=UTF-8");
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
            "attachment;fileName=" + java.net.URLEncoder.encode(fileName, FebsConstant.UTF8));
        response.setCharacterEncoding(FebsConstant.UTF8);
        try (InputStream inputStream = new FileInputStream(file); OutputStream os = response.getOutputStream()) {
            byte[] b = new byte[2048];
            int length;
            while ((length = inputStream.read(b)) > 0) {
                os.write(b, 0, length);
            }
            os.flush();
        } finally {
            if (delete) {
                FileSystemUtils.deleteRecursively(file);
            }
        }
    }

    public static void download(InputStream inputStream, String fileName,
        HttpServletResponse response) throws Exception {
        response.setHeader("content-type", "application/octet-stream;charset=UTF-8");
        response.setContentType("application/octet-stream;charset=UTF-8");
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
            "attachment;fileName=" + java.net.URLEncoder.encode(fileName, FebsConstant.UTF8));
        response.setCharacterEncoding(FebsConstant.UTF8);
        try (OutputStream os = response.getOutputStream()) {
            byte[] b = new byte[2048];
            int length;
            while ((length = inputStream.read(b)) > 0) {
                os.write(b, 0, length);
            }
            os.flush();
        } finally {
            inputStream.close();
        }
    }

    public static void download(String filePath, String fileName, Boolean delete,
        HttpServletResponse response) throws Exception {
        download(new File(filePath), fileName, delete, response);
    }

    /**
     * 下载文件到本地
     */
    public static String downloadToLocal(String url) {
        LocalDateTime now = LocalDateTime.now();
        String path = TEMP_DIR + now.getYear() + "/" + now.getMonthValue() + "/" + now.getDayOfMonth() + "/";
//        String filename = cn.hutool.core.io.FileUtil.getName(url);
//        if (filename.contains(".jpg") || filename.contains(".png")) {
//            if (filename.contains("?")) {
//                filename = filename.split("\\?")[0];
//            }
//        } else {
//            filename = UUID.randomUUID() + ".jpg";
//        }
        String filename = UUID.randomUUID() + ".jpg";
        try {
            HttpUtil.downloadFile(url, path + filename);
        } catch (Exception e) {
            log.error("下载文件异常：【{}】", url, e);
        }
        return path + filename;
    }

    /**
     * 验证上传的文件是否为满足的文件类型
     *
     * @param file               上传的文件数据
     * @param checkFileTypeArray 验证的文件类型数组
     * @return boolean
     * @author ZYJ
     * @date 2021/8/23 16:18
     */
    public static boolean checkMultipartFile(MultipartFile file, String[] checkFileTypeArray) {
        Preconditions.checkNotNull(file);
        String fileName = file.getOriginalFilename();
        // 获取文件后缀
        String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
        return fileTypeIsValid(suffix, checkFileTypeArray);
    }

    /**
     * 获取文件类型
     *
     * @param file 文件
     * @return 文件类型
     * @throws Exception Exception
     */
    private static String getFileType(File file) throws Exception {
        Preconditions.checkNotNull(file);
        if (file.isDirectory()) {
            throw new Exception("file不是文件");
        }
        String fileName = file.getName();
        return fileName.substring(fileName.lastIndexOf(".") + 1);
    }


    /**
     * 校验文件类型是否是允许下载的类型 （出于安全考虑：https://github.com/wuyouzhuguli/FEBS-Shiro/issues/40）
     *
     * @param fileType           fileType
     * @param checkFileTypeArray 验证的文件类型数组
     * @return Boolean
     */
    private static Boolean fileTypeIsValid(String fileType, String[] checkFileTypeArray) {
        Preconditions.checkNotNull(fileType);
        fileType = StringUtils.lowerCase(fileType);
        return ArrayUtils.contains(checkFileTypeArray, fileType);
    }

    private static void compress(File file, ZipOutputStream zipOut, String baseDir) throws IOException {
        if (file.isDirectory()) {
            compressDirectory(file, zipOut, baseDir);
        } else {
            compressFile(file, zipOut, baseDir);
        }
    }

    private static void compressDirectory(File dir, ZipOutputStream zipOut, String baseDir) throws IOException {
        File[] files = dir.listFiles();
        if (files != null && ArrayUtils.isNotEmpty(files)) {
            for (File file : files) {
                compress(file, zipOut, baseDir + dir.getName() + File.separator);
            }
        }
    }

    private static void compressFile(File file, ZipOutputStream zipOut, String baseDir) throws IOException {
        if (!file.exists()) {
            return;
        }
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) {
            ZipEntry entry = new ZipEntry(baseDir + file.getName());
            zipOut.putNextEntry(entry);
            int count;
            byte[] data = new byte[BUFFER];
            while ((count = bis.read(data, 0, BUFFER)) != -1) {
                zipOut.write(data, 0, count);
            }
        }
    }

    public static String getRandomPath(String suffix) {
        LocalDate now = LocalDate.now();
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(System.getProperty("user.dir"))
            .append("/")
            .append("temp")
            .append("/")
            .append(now.getYear())
            .append("/")
            .append(now.getMonthValue())
            .append("/")
            .append(now.getDayOfMonth())
            .append("/")
            .append(StrUtil.uuid().replace("-", ""))
            .append(".")
            .append(suffix);
        return stringBuilder.toString();
    }

    public static Boolean isVideo(String suffix) {
        if (StrUtil.isBlank(suffix)) {
            return false;
        }
        if (VIDEO_TYPE.contains(suffix.toLowerCase(Locale.ROOT))) {
            return true;
        }
        return false;
    }

    /**
     * 文件转为MultipartFile
     *
     * @param file
     * @return
     * @throws IOException
     */
    public static MultipartFile fileToMultipartFile(File file) throws IOException {
        MultipartFile multipartFile = null;
        FileItem fileItem = new DiskFileItem("file", Files.probeContentType(file.toPath()), false,
            file.getName(), (int) file.length(), file.getParentFile());
        byte[] buffer = new byte[4096];
        int n;
        try (InputStream inputStream = new FileInputStream(file); OutputStream os = fileItem.getOutputStream()) {
            while ((n = inputStream.read(buffer, 0, 4096)) != -1) {
                os.write(buffer, 0, n);
            }
            //也可以用IOUtils.copy(inputStream,os);
            multipartFile = new CommonsMultipartFile(fileItem);
            return multipartFile;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return multipartFile;
    }

    /**
     * 图片转base64
     *
     * @param img
     * @return
     */
    public static String convertFileToBase64(File img) {
        byte[] data = null;
        // 读取图片字节数组
        try {
            InputStream in = new FileInputStream(img);
            System.out.println("文件大小（字节）=" + in.available());
            data = new byte[in.available()];
            in.read(data);
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 对字节数组进行Base64编码，得到Base64编码的字符串
        BASE64Encoder encoder = new BASE64Encoder();
        String base64Str = encoder.encode(data);
        return base64Str;
    }

    /**
     * InputStream输入流转临时文件
     *
     * @param in InputStream输入流
     * @return java.io.File
     * @author ZYJ
     * @date 2021/8/24 16:45
     */
    public static File inputStreamToFile(InputStream in) throws IOException {
        File tempFile = File.createTempFile("template" + System.currentTimeMillis(), ".tmp");
        tempFile.deleteOnExit();
        FileOutputStream out = new FileOutputStream(tempFile);
        IOUtils.copy(in, out);
        return tempFile;
    }

}
