package com.fuyuanshen.common.core.utils.file;

import lombok.extern.slf4j.Slf4j;

import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Iterator;

/**
 * 图片压缩工具类
 *
 * @author AprilWind
 */
@Slf4j
public class ImageCompressUtil {

    /**
     * 默认压缩目标大小(100KB)
     */
    private static final int DEFAULT_COMPRESS_SIZE = 100 * 1024;

    /**
     * 默认触发压缩的大小(1MB)
     */
    private static final int DEFAULT_TRIGGER_SIZE = 1024 * 1024;

    /**
     * 压缩图片到指定大小以下(默认100KB)
     *
     * @param imageData 原始图片数据
     * @return 压缩后的图片数据
     */
    public static byte[] compressImage(byte[] imageData) {
        return compressImage(imageData, DEFAULT_COMPRESS_SIZE);
    }

    /**
     * 压缩图片到指定大小以下
     *
     * @param imageData 原始图片数据
     * @param maxSize   最大大小（字节）
     * @return 压缩后的图片数据
     */
    public static byte[] compressImage(byte[] imageData, int maxSize) {
        try {
            // 如果图片本身小于等于最大大小，直接返回
            if (imageData.length <= maxSize) {
                return imageData;
            }

            // 读取原始图片
            BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(imageData));
            if (originalImage == null) {
                log.warn("无法读取图片数据");
                return imageData;
            }

            // 检查图片是否包含透明度
            boolean hasAlpha = hasAlpha(originalImage);
            String formatName = hasAlpha ? "png" : "jpg";

            // 对于小尺寸PNG图片可跳过压缩以保持图像质量
            if ("png".equals(formatName) && imageData.length <= 2 * maxSize) {
                log.debug("PNG图片大小适中({} bytes)，跳过压缩", imageData.length);
                return imageData;
            }

            // 先尝试质量压缩
            byte[] compressedData = compressImageQuality(originalImage, formatName, 0.7f);

            // 如果质量压缩后仍大于目标大小，则进行尺寸压缩
            if (compressedData.length > maxSize) {
                // 计算缩放比例
                double scale = Math.sqrt((double) maxSize / compressedData.length);
                scale = Math.max(scale, 0.2); // 最小缩放到原来的20%

                // 尺寸压缩
                compressedData = compressImageByScale(originalImage, scale, formatName);
            }

            // 如果压缩后还是太大，继续压缩
            int attempts = 0;
            while (compressedData.length > maxSize && attempts < 15) { // 增加尝试次数到15次
                // 优先降低质量
                float quality = Math.max(0.01f, 0.7f - attempts * 0.1f); // 最低质量降至0.01
                compressedData = compressImageQuality(originalImage, formatName, quality);
                
                // 如果质量压缩不够，再缩小尺寸
                if (compressedData.length > maxSize) {
                    double scale = 0.8 - attempts * 0.15; // 更积极地缩小尺寸
                    scale = Math.max(scale, 0.1); // 最小缩放到原来的10%
                    compressedData = compressImageByScale(originalImage, scale, formatName);
                }
                attempts++;
            }

            // 如果经过多次尝试仍然大于目标大小，则强制压缩到目标大小以下
            if (compressedData.length > maxSize) {
                // 强制尺寸压缩到目标大小
                double finalScale = Math.sqrt((double) maxSize / compressedData.length) * 0.8; // 留一些余量
                finalScale = Math.max(finalScale, 0.05); // 至少保留5%的尺寸
                compressedData = compressImageByScale(originalImage, finalScale, formatName);
                
                // 如果仍然太大，强制质量压缩
                if (compressedData.length > maxSize) {
                    // 计算需要的质量值
                    float finalQuality = (float) maxSize / compressedData.length * 0.7f; // 留一些余量
                    finalQuality = Math.max(finalQuality, 0.005f); // 至少保留0.5%的质量
                    compressedData = compressImageQuality(originalImage, formatName, finalQuality);
                }
            }

            log.info("图片压缩完成，原始大小: {} bytes, 压缩后大小: {} bytes, 压缩率: {}%",
                    imageData.length, compressedData.length, 
                    String.format("%.2f", (1.0 - (double) compressedData.length / imageData.length) * 100));

            // 如果压缩后反而变大了，则使用原始数据
            if (compressedData.length >= imageData.length) {
                log.debug("压缩后数据变大，使用原始数据");
                return imageData;
            }

            // 特殊处理：如果目标大小是50KB或更小，确保最终结果符合要求
            if (maxSize <= 50 * 1024 && compressedData.length > maxSize) {
                // 使用更强力的压缩策略
                compressedData = forceCompressToSize(originalImage, formatName, maxSize);
            }

            return compressedData;
        } catch (Exception e) {
            log.error("图片压缩失败: {}", e.getMessage(), e);
            return imageData; // 压缩失败时返回原始数据
        }
    }

    /**
     * 强制压缩到指定大小
     * 
     * @param originalImage 原始图片
     * @param formatName 图片格式
     * @param maxSize 目标大小
     * @return 压缩后的图片数据
     */
    private static byte[] forceCompressToSize(BufferedImage originalImage, String formatName, int maxSize) throws IOException {
        byte[] result = null;
        int width = originalImage.getWidth();
        int height = originalImage.getHeight();
        
        // 通过不断缩小尺寸来达到目标大小
        double scale = 0.9;
        do {
            int newWidth = (int) (width * scale);
            int newHeight = (int) (height * scale);
            
            // 创建缩放后的图片
            Image scaledImage = originalImage.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH);
            BufferedImage bufferedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
            Graphics2D g2d = bufferedImage.createGraphics();
            
            // 绘制缩放后的图片
            g2d.drawImage(scaledImage, 0, 0, null);
            g2d.dispose();
            
            // 以最低质量压缩
            result = compressImageQuality(bufferedImage, formatName, 0.01f);
            
            if (result.length <= maxSize) {
                break;
            }
            
            scale -= 0.1;
        } while (scale > 0.1 && result.length > maxSize);
        
        // 如果还是太大，强制调整大小
        if (result.length > maxSize) {
            // 计算精确的缩放比例
            double targetScale = Math.sqrt((double) maxSize / result.length) * 0.9;
            int newWidth = Math.max((int) (width * targetScale), 5);
            int newHeight = Math.max((int) (height * targetScale), 5);
            
            Image scaledImage = originalImage.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH);
            BufferedImage bufferedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
            Graphics2D g2d = bufferedImage.createGraphics();
            g2d.drawImage(scaledImage, 0, 0, null);
            g2d.dispose();
            
            result = compressImageQuality(bufferedImage, formatName, 0.005f);
        }
        
        return result;
    }

    /**
     * 按比例缩放图片
     *
     * @param originalImage 原始图片
     * @param scale         缩放比例
     * @param formatName    图片格式
     * @return 缩放后的图片数据
     * @throws IOException IO异常
     */
    private static byte[] compressImageByScale(BufferedImage originalImage, double scale, String formatName) throws IOException {
        int width = (int) (originalImage.getWidth() * scale);
        int height = (int) (originalImage.getHeight() * scale);

        // 确保最小尺寸不小于5像素
        width = Math.max(width, 5);
        height = Math.max(height, 5);

        // 创建缩放后的图片
        Image scaledImage = originalImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = bufferedImage.createGraphics();
        
        // 绘制缩放后的图片
        g2d.drawImage(scaledImage, 0, 0, null);
        g2d.dispose();

        // 输出为指定格式
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(bufferedImage, formatName, baos);
        return baos.toByteArray();
    }

    /**
     * 按质量压缩图片
     *
     * @param originalImage 原始图片
     * @param formatName    图片格式
     * @param quality       压缩质量(0.1-1.0)
     * @return 压缩后的图片数据
     * @throws IOException IO异常
     */
    private static byte[] compressImageQuality(BufferedImage originalImage, String formatName, float quality) throws IOException {
        // 创建压缩参数
        Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatName);
        if (!writers.hasNext()) {
            log.warn("找不到合适的图片写入器");
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(originalImage, formatName, baos);
            return baos.toByteArray();
        }

        ImageWriter writer = writers.next();
        ImageWriteParam param = writer.getDefaultWriteParam();

        // 设置压缩参数
        if (param.canWriteCompressed()) {
            param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
            param.setCompressionQuality(quality);
        }

        // 写入压缩后的图片数据
        ByteArrayOutputStream compressedOutputStream = new ByteArrayOutputStream();
        writer.setOutput(ImageIO.createImageOutputStream(compressedOutputStream));
        writer.write(null, new javax.imageio.IIOImage(originalImage, null, null), param);
        writer.dispose();

        return compressedOutputStream.toByteArray();
    }

    /**
     * 检查图片是否包含透明度
     *
     * @param image 图片
     * @return 是否包含透明度
     */
    private static boolean hasAlpha(BufferedImage image) {
        return image.getType() == BufferedImage.TYPE_4BYTE_ABGR ||
                image.getType() == BufferedImage.TYPE_INT_ARGB ||
                image.getColorModel().hasAlpha();
    }

    /**
     * 判断图片是否需要压缩(超过1MB)
     *
     * @param imageData 图片数据
     * @return 是否需要压缩
     */
    public static boolean needCompress(byte[] imageData) {
        return imageData.length > DEFAULT_TRIGGER_SIZE;
    }
}