package com.ruoyi.common.core.utils;

import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 文件上传安全校验工具类
 * 提供图片和Excel文件的专用校验功能（兼容JDK 1.8）
 */
public final class FileUploadSecurityValidator {

    private static final String EXCEL_UPLOAD = "Excel上传";

    // 图片文件相关配置
    private static final Set<String> ALLOWED_IMAGE_EXTENSIONS;
    private static final long MAX_IMAGE_SIZE = 10 * 1024 * 1024; // 10MB

    // Excel文件相关配置
    private static final Set<String> ALLOWED_EXCEL_EXTENSIONS;
    private static final long MAX_EXCEL_SIZE = 20 * 1024 * 1024; // 20MB

    // 文件头魔术数字映射
    private static final Map<String, List<String>> FILE_MAGIC_NUMBERS = new HashMap<>();

    static {
        // 初始化允许的扩展名集合
        Set<String> imageExtensions = new HashSet<>();
        imageExtensions.add("jpg");
        imageExtensions.add("jpeg");
        imageExtensions.add("png");
        imageExtensions.add("gif");
        imageExtensions.add("bmp");
        imageExtensions.add("webp");
        ALLOWED_IMAGE_EXTENSIONS = Collections.unmodifiableSet(imageExtensions);

        Set<String> excelExtensions = new HashSet<>();
        excelExtensions.add("xls");
        excelExtensions.add("xlsx");
        ALLOWED_EXCEL_EXTENSIONS = Collections.unmodifiableSet(excelExtensions);

        // 初始化文件头魔术数字
        FILE_MAGIC_NUMBERS.put("jpg", Arrays.asList("FFD8FF"));
        FILE_MAGIC_NUMBERS.put("png", Arrays.asList("89504E47"));
        FILE_MAGIC_NUMBERS.put("gif", Arrays.asList("47494638"));
        FILE_MAGIC_NUMBERS.put("bmp", Arrays.asList("424D"));
        FILE_MAGIC_NUMBERS.put("webp", Arrays.asList("52494646"));
        FILE_MAGIC_NUMBERS.put("xls", Arrays.asList("D0CF11E0"));
        FILE_MAGIC_NUMBERS.put("xlsx", Arrays.asList("504B0304"));
    }

    // 不允许出现在文件名中的特殊字符
    private static final String ILLEGAL_FILENAME_CHARS = "[\\\\/:*?\"<>|]";

    private FileUploadSecurityValidator() {
        // 私有构造函数，防止实例化
    }

    /**
     * 校验图片文件安全性
     *
     * @param file    上传的文件
     * @param request HTTP请求对象，用于日志记录
     * @throws ValidationException 校验失败时抛出异常
     */
    public static void validateImageFile(MultipartFile file, HttpServletRequest request) throws ValidationException {
        try {
            // 校验基本文件属性
            validateCommonFileProperties(file, request, "图片上传");

            // 校验文件扩展名
            String extension = getFileExtension(file.getOriginalFilename());
            if (!ALLOWED_IMAGE_EXTENSIONS.contains(extension.toLowerCase())) {
                logValidationFailure(request, "图片上传", "文件扩展名", extension);
                throw new ValidationException("不支持的图片格式: " + extension);
            }

            // 校验文件大小
            if (file.getSize() > MAX_IMAGE_SIZE) {
                logValidationFailure(request, "图片上传", "文件大小", formatFileSize(file.getSize()));
                throw new ValidationException("图片大小不能超过: " + formatFileSize(MAX_IMAGE_SIZE));
            }

            // 校验MIME类型
            if (!validateMimeType(file, extension)) {
                logValidationFailure(request, "图片上传", "MIME类型", file.getContentType());
                throw new ValidationException("无效的图片MIME类型: " + file.getContentType());
            }

            // 校验文件头
            if (!validateFileHeader(file, extension)) {
                logValidationFailure(request, "图片上传", "文件头校验", extension);
                throw new ValidationException("文件内容与扩展名不匹配");
            }

        } catch (IOException e) {
            logValidationFailure(request, "图片上传", "IO异常", e.getMessage());
            throw new ValidationException("文件处理异常: " + e.getMessage(), e);
        }
    }

    /**
     * 校验Excel文件安全性
     *
     * @param file    上传的文件
     * @param request HTTP请求对象，用于日志记录
     * @throws ValidationException 校验失败时抛出异常
     */
    public static void validateExcelFile(MultipartFile file, HttpServletRequest request) throws ValidationException {
        try {
            // 校验基本文件属性
            validateCommonFileProperties(file, request, EXCEL_UPLOAD);

            // 校验文件扩展名
            String extension = getFileExtension(file.getOriginalFilename());
            if (!ALLOWED_EXCEL_EXTENSIONS.contains(extension.toLowerCase())) {
                logValidationFailure(request, EXCEL_UPLOAD, "文件扩展名", extension);
                throw new ValidationException("不支持的Excel格式: " + extension);
            }

            // 校验文件大小
            if (file.getSize() > MAX_EXCEL_SIZE) {
                logValidationFailure(request, EXCEL_UPLOAD, "文件大小", formatFileSize(file.getSize()));
                throw new ValidationException("Excel大小不能超过: " + formatFileSize(MAX_EXCEL_SIZE));
            }

            // 校验MIME类型
            if (!validateMimeType(file, extension)) {
                logValidationFailure(request, EXCEL_UPLOAD, "MIME类型", file.getContentType());
                throw new ValidationException("无效的Excel MIME类型: " + file.getContentType());
            }

            // 校验文件头
            if (!validateFileHeader(file, extension)) {
                logValidationFailure(request, EXCEL_UPLOAD, "文件头校验", extension);
                throw new ValidationException("文件内容与扩展名不匹配");
            }

        } catch (IOException e) {
            logValidationFailure(request, EXCEL_UPLOAD, "IO异常", e.getMessage());
            throw new ValidationException("文件处理异常: " + e.getMessage(), e);
        }
    }

    // 以下为辅助方法

    private static void validateCommonFileProperties(MultipartFile file, HttpServletRequest request, String operation)
            throws ValidationException {
        if (file == null || file.isEmpty()) {
            logValidationFailure(request, operation, "文件为空", null);
            throw new ValidationException("上传文件不能为空");
        }

        String fileName = file.getOriginalFilename();
        if (!validateFileName(fileName)) {
            logValidationFailure(request, operation, "非法文件名", fileName);
            throw new ValidationException("非法文件名: " + fileName);
        }
    }

    private static boolean validateMimeType(MultipartFile file, String extension) {
        String mimeType = file.getContentType();
        if (mimeType == null) {
            return false;
        }

        switch (extension.toLowerCase()) {
            case "jpg":
            case "jpeg":
                return "image/jpeg".equals(mimeType);
            case "png":
                return "image/png".equals(mimeType);
            case "gif":
                return "image/gif".equals(mimeType);
            case "bmp":
                return "image/bmp".equals(mimeType);
            case "webp":
                return "image/webp".equals(mimeType);
            case "xls":
                return "application/vnd.ms-excel".equals(mimeType);
            case "xlsx":
                return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet".equals(mimeType);
            default:
                return false;
        }
    }

    private static boolean validateFileHeader(MultipartFile file, String extension) throws IOException {
        if (!FILE_MAGIC_NUMBERS.containsKey(extension.toLowerCase())) {
            return true; // 没有定义魔术数字的类型默认通过校验
        }

        try (InputStream is = file.getInputStream()) {
            byte[] header = new byte[8];
            int bytesRead = is.read(header);
            if (bytesRead < 4) {
                return false;
            }

            String headerHex = bytesToHex(header);
            return FILE_MAGIC_NUMBERS.get(extension.toLowerCase()).stream()
                    .anyMatch(magic -> headerHex.startsWith(magic));
        }
    }

    private static boolean validateFileName(String fileName) {
        if (fileName == null || fileName.isEmpty()) {
            return false;
        }

        // 检查是否包含非法字符
        if (fileName.matches(ILLEGAL_FILENAME_CHARS)) {
            return false;
        }

        // 检查文件名长度（限制为255个字符）
        if (fileName.length() > 255) {
            return false;
        }

        // 检查是否包含路径遍历字符
        if (fileName.contains("..") || fileName.startsWith("/") || fileName.startsWith("\\")) {
            return false;
        }

        return true;
    }

    private static String formatFileSize(long size) {
        if (size < 1024) {
            return size + " B";
        }
        if (size < 1024 * 1024) {
            return String.format("%.2f KB", size / 1024.0);
        }
        return String.format("%.2f MB", size / (1024.0 * 1024));
    }

    private static String getFileExtension(String fileName) {
        if (fileName == null || fileName.isEmpty()) {
            return "";
        }
        int lastIndex = fileName.lastIndexOf('.');
        return lastIndex >= 0 ? fileName.substring(lastIndex + 1).toLowerCase() : "";
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02X", b));
        }
        return result.toString();
    }

    private static void logValidationFailure(HttpServletRequest request, String operation,
                                             String failedParam, Object paramValue) {
        String user = request.getRemoteUser() != null ? request.getRemoteUser() : "未知用户";
        String ip = request.getRemoteAddr();

        // 使用StringBuilder构建请求参数
        StringBuilder paramBuilder = new StringBuilder();
        Enumeration<String> paramNames = request.getParameterNames();
        while (paramNames.hasMoreElements()) {
            String paramName = paramNames.nextElement();
            paramBuilder.append(paramName).append("=")
                    .append(Arrays.toString(request.getParameterValues(paramName)))
                    .append(", ");
        }
        String params = paramBuilder.length() > 0
                ? paramBuilder.substring(0, paramBuilder.length() - 2)
                : "";

        // 使用SimpleDateFormat替代Java 8的LocalDateTime
        String timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());

        System.err.printf("[%s] ERROR - 用户: %s, IP: %s, 操作: %s, 校验失败参数: %s, 参数值: %s, 请求参数: %s%n",
                timestamp, user, ip, operation, failedParam, paramValue, params);
    }

    /**
     * 文件校验异常
     */
    public static class ValidationException extends Exception {
        public ValidationException(String message) {
            super(message);
        }

        public ValidationException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    /**
     * excel检查
     * @param file
     * @param request
     * @return
     */
    public static String checkExcel(MultipartFile file,
                                    HttpServletRequest request) {
        try {
            // 校验Excel文件
            FileUploadSecurityValidator.validateExcelFile(file, request);
        } catch (ValidationException e) {
            // 校验失败返回400错误
            return e.getMessage();

        } catch (Exception e) {
            // 其他异常返回500错误
            return "上传失败,请检查文件!";
        }
        return null;
    }

    /**
     * 文件检查
     * @param file
     * @param request
     * @return
     */
    public static String checkPng(MultipartFile file,
                                    HttpServletRequest request) {
        try {
            // 校验Excel文件
            FileUploadSecurityValidator.validateImageFile(file, request);
        } catch (ValidationException e) {
            // 校验失败返回400错误
            return e.getMessage();
        } catch (Exception e) {
            // 其他异常返回500错误
            return "上传失败,请检查文件!";
        }
        return null;
    }

}
