package com.xinelu.common.utils.file;

import com.xinelu.common.config.XinELuConfig;
import com.xinelu.common.core.domain.AjaxResult;
import com.xinelu.common.exception.ServiceException;
import com.xinelu.common.utils.DateUtils;
import com.xinelu.common.utils.StringUtils;
import com.xinelu.common.utils.uuid.IdUtils;
import it.sauronsoftware.jave.Encoder;
import it.sauronsoftware.jave.MultimediaInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 文件处理工具类
 *
 * @author xinelu
 */
@Slf4j
public class FileUtils {
    public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";

    /**
     * 匹配content中的<img />标签
     **/
    private static final String PATTEN_IMG = "<(img|IMG)(.*?)(/>|></img>|>)";

    /**
     * 匹配<img />标签中的src
     **/
    private static final String PATTEN_SRC = "(src|SRC)=(\"|\')(.*?)(\"|\')";

    /**
     * 输出指定文件的byte数组
     *
     * @param filePath 文件路径
     * @param os       输出流
     * @return
     */
    public static void writeBytes(String filePath, OutputStream os) throws IOException {
        FileInputStream fis = null;
        try {
            File file = new File(filePath);
            if (!file.exists()) {
                throw new FileNotFoundException(filePath);
            }
            fis = new FileInputStream(file);
            byte[] b = new byte[1024];
            int length;
            while ((length = fis.read(b)) > 0) {
                os.write(b, 0, length);
            }
        } catch (IOException e) {
            throw e;
        } finally {
            IOUtils.close(os);
            IOUtils.close(fis);
        }
    }

    /**
     * 写数据到文件中
     *
     * @param data 数据
     * @return 目标文件
     * @throws IOException IO异常
     */
    public static String writeImportBytes(byte[] data) throws IOException {
        return writeBytes(data, XinELuConfig.getImportPath());
    }

    /**
     * 写数据到文件中
     *
     * @param data      数据
     * @param uploadDir 目标文件
     * @return 目标文件
     * @throws IOException IO异常
     */
    public static String writeBytes(byte[] data, String uploadDir) throws IOException {
        FileOutputStream fos = null;
        String pathName = "";
        try {
            String extension = getFileExtendName(data);
            pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension;
            File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName);
            fos = new FileOutputStream(file);
            fos.write(data);
        } finally {
            IOUtils.close(fos);
        }
        return FileUploadUtils.getPathFileName(uploadDir, pathName);
    }

    /**
     * 删除文件
     *
     * @param filePath 文件
     * @return
     */
    public static boolean deleteFile(String filePath) {
        boolean flag = false;
        File file = new File(filePath);
        // 路径为文件且不为空则进行删除
        if (file.isFile() && file.exists()) {
            file.delete();
            flag = true;
        }
        return flag;
    }

    /**
     * 文件名称验证
     *
     * @param filename 文件名称
     * @return true 正常 false 非法
     */
    public static boolean isValidFilename(String filename) {
        return filename.matches(FILENAME_PATTERN);
    }

    /**
     * 检查文件是否可下载
     *
     * @param resource 需要下载的文件
     * @return true 正常 false 非法
     */
    public static boolean checkAllowDownload(String resource) {
        // 禁止目录上跳级别
        if (StringUtils.contains(resource, "..")) {
            return false;
        }

        // 检查允许下载的文件规则
        if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) {
            return true;
        }

        // 不在允许下载的文件规则
        return false;
    }

    /**
     * 下载文件名重新编码
     *
     * @param request  请求对象
     * @param fileName 文件名
     * @return 编码后的文件名
     */
    public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException {
        final String agent = request.getHeader("USER-AGENT");
        String filename = fileName;
        if (agent.contains("MSIE")) {
            // IE浏览器
            filename = URLEncoder.encode(filename, "utf-8");
            filename = filename.replace("+", " ");
        } else if (agent.contains("Firefox")) {
            // 火狐浏览器
            filename = new String(fileName.getBytes(), "ISO8859-1");
        } else if (agent.contains("Chrome")) {
            // google浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        } else {
            // 其它浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        }
        return filename;
    }

    /**
     * 下载文件名重新编码
     *
     * @param response     响应对象
     * @param realFileName 真实文件名
     */
    public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException {
        String percentEncodedFileName = percentEncode(realFileName);

        StringBuilder contentDispositionValue = new StringBuilder();
        contentDispositionValue.append("attachment; filename=")
                .append(percentEncodedFileName)
                .append(";")
                .append("filename*=")
                .append("utf-8''")
                .append(percentEncodedFileName);

        response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
        response.setHeader("Content-disposition", contentDispositionValue.toString());
        response.setHeader("download-filename", percentEncodedFileName);
    }

    /**
     * 百分号编码工具方法
     *
     * @param s 需要百分号编码的字符串
     * @return 百分号编码后的字符串
     */
    public static String percentEncode(String s) throws UnsupportedEncodingException {
        String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
        return encode.replaceAll("\\+", "%20");
    }

    /**
     * 获取图像后缀
     *
     * @param photoByte 图像数据
     * @return 后缀名
     */
    public static String getFileExtendName(byte[] photoByte) {
        String strFileExtendName = "jpg";
        if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56)
                && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) {
            strFileExtendName = "gif";
        } else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) {
            strFileExtendName = "jpg";
        } else if ((photoByte[0] == 66) && (photoByte[1] == 77)) {
            strFileExtendName = "bmp";
        } else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) {
            strFileExtendName = "png";
        }
        return strFileExtendName;
    }

    /**
     * 获取文件名称 /profile/upload/2022/04/16/xinyilu.png -- xinyilu.png
     *
     * @param fileName 路径名称
     * @return 没有文件路径的名称
     */
    public static String getName(String fileName) {
        if (fileName == null) {
            return null;
        }
        int lastUnixPos = fileName.lastIndexOf('/');
        int lastWindowsPos = fileName.lastIndexOf('\\');
        int index = Math.max(lastUnixPos, lastWindowsPos);
        return fileName.substring(index + 1);
    }


    /**
     * 获取img标签中的src值
     *
     * @param content img的内容
     * @return List<String>
     */
    public static List<String> getImgSrc(String content) {
        if (StringUtils.isBlank(content)) {
            return Lists.newArrayList();
        }
        List<String> list = new ArrayList<>();
        //开始匹配content中的<img />标签
        Pattern pImg = Pattern.compile(PATTEN_IMG);
        Matcher mImg = pImg.matcher(content);
        boolean resultImg = mImg.find();
        if (resultImg) {
            while (resultImg) {
                //获取到匹配的<img />标签中的内容
                String strImg = mImg.group(2);
                //开始匹配<img />标签中的src
                Pattern pSrc = Pattern.compile(PATTEN_SRC);
                Matcher mSrc = pSrc.matcher(strImg);
                if (mSrc.find()) {
                    String strSrc = mSrc.group(3);
                    if (StringUtils.isNotBlank(strSrc)) {
                        list.add(strSrc);
                    }
                }
                //匹配content中是否存在下一个<img />标签，有则继续以上步骤匹配<img />标签中的src
                resultImg = mImg.find();
            }
        }
        return list;
    }

    /**
     * 删除系统富文本图片方法
     *
     * @param databaseUrl 原有图片路径
     * @param afferentUrl 传值图片路径
     */
    public static void deleteTextUrl(String databaseUrl, String afferentUrl) {
        //数据库中的
        List<String> databaseImgSrc = Lists.newArrayList();
        if (StringUtils.isNotBlank(databaseUrl)) {
            databaseImgSrc = FileUtils.getImgSrc(databaseUrl);
        }
        //前端传的
        List<String> afferentImgSrc = Lists.newArrayList();
        if (org.apache.commons.lang3.StringUtils.isNotBlank(afferentUrl)) {
            afferentImgSrc = FileUtils.getImgSrc(afferentUrl);
        }
        //差集不同的
        List<String> subtractImgSrc = new ArrayList<>(CollectionUtils.subtract(databaseImgSrc, afferentImgSrc));
        if (CollectionUtils.isEmpty(subtractImgSrc)) {
            return;
        }
        for (String pictureUrl : subtractImgSrc) {
            if (org.apache.commons.lang3.StringUtils.isBlank(pictureUrl)) {
                continue;
            }
            //修改路径
            String substring = pictureUrl.substring(pictureUrl.indexOf("/profile"));
            //删除商品富文本图片
            deleteSystemFile(substring);
        }
    }

    /**
     * 根据文件路径删除系统本地文件
     *
     * @param filePath 文件路径
     */
    public static void deleteSystemFile(String filePath) {
        if (StringUtils.isBlank(filePath)) {
            return;
        }
        String picture = XinELuConfig.getProfile() + filePath.replaceAll("/profile", "");
        File checkReportNameFile = new File(picture);
        if (checkReportNameFile.exists()) {
            boolean delete = checkReportNameFile.delete();
            if (BooleanUtils.isFalse(delete)) {
                throw new ServiceException("删除系统文件失败！");
            }
        }
    }

    /**
     * 根据视频文件地址获取对应的视频时长
     *
     * @param videoPath 视频地址
     * @return 视频时长
     */
    public static LocalTime getVideoDuration(String videoPath) {
        LocalTime duration;
        File source = new File(videoPath);
        if (source.length() > 0) {
            Encoder encoder = new Encoder();
            try {
                MultimediaInfo m = encoder.getInfo(source);
                long ls = m.getDuration() / 1000;
                int hour = (int) (ls / 3600);
                int minute = (int) (ls % 3600) / 60;
                int second = (int) (ls - hour * 3600 - minute * 60);
                String hourStr = hour < 10 ? "0" + hour : String.valueOf(hour);
                String minuteStr = minute < 10 ? "0" + minute : String.valueOf(minute);
                String secondStr = second < 10 ? "0" + second : String.valueOf(second);
                String durationStr = hourStr + ":" + minuteStr + ":" + secondStr;
                duration = LocalTime.parse(DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalTime.parse(durationStr)));
                return duration;
            } catch (Exception e) {
                log.error("计算视频时长异常，异常信息为：{}", e.getMessage());
                return null;
            }
        }
        return null;
    }

    /**
     * 图片上传接口
     *
     * @param uploadPathUrl 路径
     * @param multipartFile 文件
     * @return AjaxResult
     * @throws Exception 异常信息
     **/
    public static AjaxResult uploadImages(String uploadPathUrl, MultipartFile multipartFile) throws Exception {
        String pictureName = FileUploadUtils.uploadNurseStationPath(uploadPathUrl, multipartFile, MimeTypeUtils.IMAGE_EXTENSION);
        if (org.apache.commons.lang3.StringUtils.isBlank(pictureName)) {
            throw new ServiceException("图片上传失败，请联系管理员！");
        }
        AjaxResult ajax = AjaxResult.success("上传成功！");
        ajax.put("imgUrl", pictureName);
        return ajax;
    }

    //将图片转换成Base64格式的数据集
    public static String PicToBase64(String filePath) {
        Base64.Encoder encoder = Base64.getEncoder();
        byte[] ImgContainer = null;
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(filePath);
            ImgContainer = new byte[fileInputStream.available()];
            fileInputStream.read(ImgContainer);
            String Base64ImgData = encoder.encodeToString(ImgContainer);
            fileInputStream.close();
            return "data:image/png" + ";base64," + Base64ImgData;
        } catch (FileNotFoundException e) {
            return "找不到指定文件!";
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 视频上传接口
     *
     * @param uploadPathUrl 路径
     * @param multipartFile 文件
     * @return AjaxResult
     * @throws Exception 异常信息
     **/
    public static AjaxResult uploadVideo(String uploadPathUrl, MultipartFile multipartFile) throws Exception {
        String pictureName = FileUploadUtils.uploadNurseStationPath(uploadPathUrl, multipartFile, MimeTypeUtils.VIDEO_EXTENSION);
        if (org.apache.commons.lang3.StringUtils.isBlank(pictureName)) {
            throw new ServiceException("视频上传失败，请联系管理员！");
        }
        AjaxResult ajax = AjaxResult.success("上传成功！");
        ajax.put("imgUrl", pictureName);
        return ajax;
    }
}
