package com.ycwl.basic.utils;

import cn.hutool.core.codec.Base64;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

@Slf4j
public class ImageUtils {
    public static MultipartFile base64ToMultipartFile(String base64) {
        String[] baseStrs = base64.split(",");
        byte[] b;
        b = Base64.decode(baseStrs[0]);
        for (int i = 0; i < b.length; ++i) {
            if (b[i] < 0) {
                b[i] += (byte) 256;
            }
        }
        return new Base64DecodedMultipartFile(b, baseStrs[0]);
    }

    public static MultipartFile cropImage(MultipartFile file, int x, int y, int w, int h) throws IOException {
        BufferedImage image = null;
        BufferedImage targetImage = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            image = ImageIO.read(file.getInputStream());
            log.info("图片宽高：{}", image.getWidth() + "x" + image.getHeight());
            log.info("图片裁切：{}@{}", w + "x" + h, x + "," + y);
            if (image.getWidth() < w) {
                w = image.getWidth();
            }
            if (image.getHeight() < h) {
                h = image.getHeight();
            }
            int targetX = x;
            if (x < 0) {
                targetX = 0;
            } else if ((x + w) > image.getWidth()) {
                targetX = image.getWidth() - w;
            }
            int targetY = y;
            if (y < 0) {
                targetY = 0;
            } else if ((y + h) > image.getHeight()) {
                targetY = image.getHeight() - h;
            }
            log.info("图片实际裁切：{}@{}", w + "x" + h, targetX + "," + targetY);
            targetImage = image.getSubimage(targetX, targetY, w, h);
            ImageIO.write(targetImage, "jpg", baos);
            return new Base64DecodedMultipartFile(baos.toByteArray(), "image/jpeg");
        } finally {
            // 修复内存泄漏：显式释放图片资源
            if (image != null) {
                image.flush();
                image = null;
            }
            if (targetImage != null) {
                targetImage.flush();
                targetImage = null;
            }
            try {
                baos.close();
            } catch (IOException e) {
                log.warn("关闭ByteArrayOutputStream失败", e);
            }
            // 建议JVM进行垃圾回收
            System.gc();
        }
    }

    /**
     * 判断图片是否为横图(宽度大于高度)
     *
     * @param file 图片文件
     * @return true表示横图,false表示竖图
     * @throws IOException 读取文件失败
     */
    public static boolean isLandscape(File file) throws IOException {
        BufferedImage image = null;
        try {
            image = ImageIO.read(file);
            if (image == null) {
                throw new IOException("无法读取图片文件: " + file.getPath());
            }
            return image.getWidth() > image.getHeight();
        } finally {
            if (image != null) {
                image.flush();
            }
        }
    }

    /**
     * 判断图片是否为横图(宽度大于高度)
     * 支持URL字符串或文件路径
     *
     * @param imageSource URL字符串或文件路径
     * @return true表示横图,false表示竖图
     * @throws IOException 读取图片失败
     */
    public static boolean isLandscape(String imageSource) throws IOException {
        BufferedImage image = null;
        try {
            image = loadImage(imageSource);
            if (image == null) {
                throw new IOException("无法读取图片: " + imageSource);
            }
            return image.getWidth() > image.getHeight();
        } finally {
            if (image != null) {
                image.flush();
            }
        }
    }

    /**
     * 旋转图片90度(顺时针)
     *
     * @param sourceFile 源图片文件
     * @param targetFile 目标图片文件
     * @throws IOException 读取或写入文件失败
     */
    public static void rotateImage90(File sourceFile, File targetFile) throws IOException {
        BufferedImage sourceImage = null;
        BufferedImage rotatedImage = null;
        try {
            sourceImage = ImageIO.read(sourceFile);
            if (sourceImage == null) {
                throw new IOException("无法读取图片文件: " + sourceFile.getPath());
            }

            int width = sourceImage.getWidth();
            int height = sourceImage.getHeight();

            // 创建旋转后的图片(宽高互换)
            rotatedImage = new BufferedImage(height, width, sourceImage.getType());
            Graphics2D g2d = rotatedImage.createGraphics();

            // 设置旋转变换
            AffineTransform transform = new AffineTransform();
            transform.translate(height / 2.0, width / 2.0);
            transform.rotate(Math.PI / 2);
            transform.translate(-width / 2.0, -height / 2.0);

            g2d.setTransform(transform);
            g2d.drawImage(sourceImage, 0, 0, null);
            g2d.dispose();

            // 保存旋转后的图片
            ImageIO.write(rotatedImage, "jpg", targetFile);
            log.info("图片旋转成功,原始尺寸: {}x{}, 旋转后尺寸: {}x{}", width, height, height, width);
        } finally {
            if (sourceImage != null) {
                sourceImage.flush();
            }
            if (rotatedImage != null) {
                rotatedImage.flush();
            }
        }
    }

    /**
     * 旋转图片270度(逆时针90度)
     *
     * @param sourceFile 源图片文件
     * @param targetFile 目标图片文件
     * @throws IOException 读取或写入文件失败
     */
    public static void rotateImage270(File sourceFile, File targetFile) throws IOException {
        BufferedImage sourceImage = null;
        BufferedImage rotatedImage = null;
        try {
            sourceImage = ImageIO.read(sourceFile);
            if (sourceImage == null) {
                throw new IOException("无法读取图片文件: " + sourceFile.getPath());
            }

            int width = sourceImage.getWidth();
            int height = sourceImage.getHeight();

            // 创建旋转后的图片(宽高互换)
            rotatedImage = new BufferedImage(height, width, sourceImage.getType());
            Graphics2D g2d = rotatedImage.createGraphics();

            // 设置旋转变换
            AffineTransform transform = new AffineTransform();
            transform.translate(height / 2.0, width / 2.0);
            transform.rotate(-Math.PI / 2);
            transform.translate(-width / 2.0, -height / 2.0);

            g2d.setTransform(transform);
            g2d.drawImage(sourceImage, 0, 0, null);
            g2d.dispose();

            // 保存旋转后的图片
            ImageIO.write(rotatedImage, "jpg", targetFile);
            log.info("图片旋转成功,原始尺寸: {}x{}, 旋转后尺寸: {}x{}", width, height, height, width);
        } finally {
            if (sourceImage != null) {
                sourceImage.flush();
            }
            if (rotatedImage != null) {
                rotatedImage.flush();
            }
        }
    }

    /**
     * 旋转图片180度
     *
     * @param sourceFile 源图片文件
     * @param targetFile 目标图片文件
     * @throws IOException 读取或写入文件失败
     */
    public static void rotateImage180(File sourceFile, File targetFile) throws IOException {
        BufferedImage sourceImage = null;
        BufferedImage rotatedImage = null;
        try {
            sourceImage = ImageIO.read(sourceFile);
            if (sourceImage == null) {
                throw new IOException("无法读取图片文件: " + sourceFile.getPath());
            }

            int width = sourceImage.getWidth();
            int height = sourceImage.getHeight();

            // 创建旋转后的图片(宽高不变)
            rotatedImage = new BufferedImage(width, height, sourceImage.getType());
            Graphics2D g2d = rotatedImage.createGraphics();

            // 设置旋转变换
            AffineTransform transform = new AffineTransform();
            transform.translate(width / 2.0, height / 2.0);
            transform.rotate(Math.PI);
            transform.translate(-width / 2.0, -height / 2.0);

            g2d.setTransform(transform);
            g2d.drawImage(sourceImage, 0, 0, null);
            g2d.dispose();

            // 保存旋转后的图片
            ImageIO.write(rotatedImage, "jpg", targetFile);
            log.info("图片旋转180度成功,尺寸保持: {}x{}", width, height);
        } finally {
            if (sourceImage != null) {
                sourceImage.flush();
            }
            if (rotatedImage != null) {
                rotatedImage.flush();
            }
        }
    }

    /**
     * 智能裁切图片以填充目标尺寸,支持自动旋转以减少裁切损失
     *
     * @param imageSource 图片源,可以是URL字符串或File对象
     * @param targetWidth 目标宽度
     * @param targetHeight 目标高度
     * @return 处理后的临时文件
     * @throws IOException 读取或处理图片失败
     * @throws IllegalArgumentException 参数无效
     */
    public static File smartCropAndFill(Object imageSource, int targetWidth, int targetHeight) throws IOException {
        if (targetWidth <= 0 || targetHeight <= 0) {
            throw new IllegalArgumentException("目标宽高必须大于0");
        }

        BufferedImage originalImage = loadImage(imageSource);
        if (originalImage == null) {
            throw new IOException("无法加载图片: " + imageSource);
        }

        File resultFile = null;
        BufferedImage processedImage = null;
        try {
            // 计算最优方案(是否需要旋转以及如何裁切)
            CropStrategy strategy = calculateOptimalCropStrategy(
                originalImage.getWidth(), 
                originalImage.getHeight(), 
                targetWidth, 
                targetHeight
            );

            log.info("图片处理策略: 原始尺寸={}x{}, 目标尺寸={}x{}, 旋转={}, 裁切损失={}像素", 
                originalImage.getWidth(), originalImage.getHeight(),
                targetWidth, targetHeight,
                strategy.rotationDegrees, strategy.pixelsLost);

            // 如果需要旋转,先旋转图片
            BufferedImage workingImage = originalImage;
            if (strategy.rotationDegrees != 0) {
                workingImage = rotateImage(originalImage, strategy.rotationDegrees);
            }

            // 执行居中裁切并缩放
            processedImage = cropAndResize(workingImage, targetWidth, targetHeight);

            // 保存到临时文件
            resultFile = File.createTempFile("smartcrop_", ".jpg");
            ImageIO.write(processedImage, "jpg", resultFile);
            
            log.info("图片处理完成,输出文件: {}", resultFile.getAbsolutePath());
            return resultFile;

        } finally {
            if (originalImage != null) {
                originalImage.flush();
            }
            if (processedImage != null) {
                processedImage.flush();
            }
        }
    }

    /**
     * 从URL或File加载图片
     */
    private static BufferedImage loadImage(Object imageSource) throws IOException {
        if (imageSource instanceof String) {
            String urlStr = (String) imageSource;
            if (urlStr.startsWith("http://") || urlStr.startsWith("https://")) {
                // 从URL加载
                java.net.URL url = new java.net.URL(urlStr.replace("oss.zhentuai.com", "frametour-assets.oss-cn-shanghai-internal.aliyuncs.com"));
                return ImageIO.read(url);
            } else {
                // 作为文件路径处理
                return ImageIO.read(new File(urlStr));
            }
        } else if (imageSource instanceof File) {
            return ImageIO.read((File) imageSource);
        } else {
            throw new IllegalArgumentException("图片源必须是String(URL或路径)或File对象,实际类型: " + 
                (imageSource != null ? imageSource.getClass().getName() : "null"));
        }
    }

    /**
     * 计算最优裁切策略
     */
    private static CropStrategy calculateOptimalCropStrategy(
            int srcWidth, int srcHeight, int targetWidth, int targetHeight) {
        
        CropStrategy best = new CropStrategy();
        best.rotationDegrees = 0;
        best.pixelsLost = Integer.MAX_VALUE;

        // 测试两种情况: 不旋转、旋转270度
        int[][] scenarios = {
            {0, srcWidth, srcHeight},           // 不旋转
            {90, srcHeight, srcWidth}          // 旋转270度
        };

        for (int[] scenario : scenarios) {
            int rotation = scenario[0];
            int currentWidth = scenario[1];
            int currentHeight = scenario[2];

            // 计算裁切损失
            double srcRatio = (double) currentWidth / currentHeight;
            double targetRatio = (double) targetWidth / targetHeight;

            int pixelsLost;
            if (srcRatio > targetRatio) {
                // 源图更宽,需要裁掉左右两边
                int neededWidth = (int) Math.ceil(currentHeight * targetRatio);
                pixelsLost = (currentWidth - neededWidth) * currentHeight;
            } else {
                // 源图更高,需要裁掉上下两边
                int neededHeight = (int) Math.ceil(currentWidth / targetRatio);
                pixelsLost = (currentHeight - neededHeight) * currentWidth;
            }

            if (pixelsLost < best.pixelsLost) {
                best.rotationDegrees = rotation;
                best.pixelsLost = pixelsLost;
            }
        }

        return best;
    }

    /**
     * 旋转图片
     */
    private static BufferedImage rotateImage(BufferedImage source, int degrees) {
        if (degrees == 0) {
            return source;
        }

        int width = source.getWidth();
        int height = source.getHeight();

        // 90度会交换宽高
        BufferedImage rotated = new BufferedImage(height, width, source.getType());
        Graphics2D g2d = null;

        try {
            g2d = rotated.createGraphics();

            AffineTransform transform = new AffineTransform();
            transform.translate(height / 2.0, width / 2.0);
            transform.rotate(Math.PI / 2);
            transform.translate(-width / 2.0, -height / 2.0);

            g2d.setTransform(transform);
            g2d.drawImage(source, 0, 0, null);

            return rotated;
        } finally {
            if (g2d != null) {
                g2d.dispose();
            }
        }
    }

    /**
     * 居中裁切并缩放到目标尺寸
     */
    private static BufferedImage cropAndResize(BufferedImage source, int targetWidth, int targetHeight) {
        int srcWidth = source.getWidth();
        int srcHeight = source.getHeight();

        // 计算裁切区域(居中)
        double srcRatio = (double) srcWidth / srcHeight;
        double targetRatio = (double) targetWidth / targetHeight;

        int cropX, cropY, cropWidth, cropHeight;

        if (srcRatio > targetRatio) {
            // 源图更宽,裁掉左右
            cropHeight = srcHeight;
            cropWidth = (int) Math.round(srcHeight * targetRatio);
            cropX = (srcWidth - cropWidth) / 2;
            cropY = 0;
        } else {
            // 源图更高,裁掉上下
            cropWidth = srcWidth;
            cropHeight = (int) Math.round(srcWidth / targetRatio);
            cropX = 0;
            cropY = (srcHeight - cropHeight) / 2;
        }

        // 裁切
        BufferedImage cropped = source.getSubimage(cropX, cropY, cropWidth, cropHeight);

        // 如果裁切后的尺寸与目标尺寸不完全一致,进行缩放
        if (cropWidth != targetWidth || cropHeight != targetHeight) {
            BufferedImage resized = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
            Graphics2D g2d = resized.createGraphics();
            try {
                g2d.drawImage(cropped, 0, 0, targetWidth, targetHeight, null);
                return resized;
            } finally {
                g2d.dispose();
                cropped.flush();
            }
        }

        return cropped;
    }

    /**
     * 为图片添加白边框并向上偏移内容
     * 用于拼图打印场景,避免打印机偏移问题
     *
     * @param sourceFile 源图片文件
     * @param targetFile 目标图片文件
     * @param horizontalBorder 左右白边框宽度(像素)
     * @param verticalBorder 上下白边框高度(像素)
     * @param upwardShift 内容向上偏移的像素数
     * @throws IOException 读取或写入文件失败
     */
    public static void addBorderAndShiftUp(File sourceFile, File targetFile,
                                           int horizontalBorder, int verticalBorder, int upwardShift) throws IOException {
        BufferedImage sourceImage = null;
        BufferedImage resultImage = null;
        try {
            sourceImage = ImageIO.read(sourceFile);
            if (sourceImage == null) {
                throw new IOException("无法读取图片文件: " + sourceFile.getPath());
            }

            int srcWidth = sourceImage.getWidth();
            int srcHeight = sourceImage.getHeight();

            // 计算新图片尺寸(原图 + 左右边框 + 上下边框)
            int newWidth = srcWidth + horizontalBorder * 2;
            int newHeight = srcHeight + verticalBorder * 2;

            // 创建新图片
            resultImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
            Graphics2D g2d = resultImage.createGraphics();

            try {
                // 填充白色背景
                g2d.setColor(java.awt.Color.WHITE);
                g2d.fillRect(0, 0, newWidth, newHeight);

                // 绘制原图到新图中
                // 原图应该绘制在: x=horizontalBorder, y=verticalBorder-upwardShift 的位置
                // 这样图片内容会向上偏移upwardShift像素
                int drawX = horizontalBorder;
                int drawY = verticalBorder - upwardShift;

                g2d.drawImage(sourceImage, drawX, drawY, null);

                log.info("图片添加白边框并向上偏移: 原始尺寸={}x{}, 边框=(左右{}px,上下{}px), 向上偏移={}px, 结果尺寸={}x{}",
                        srcWidth, srcHeight, horizontalBorder, verticalBorder, upwardShift, newWidth, newHeight);
            } finally {
                g2d.dispose();
            }

            // 保存处理后的图片
            ImageIO.write(resultImage, "png", targetFile);
        } finally {
            if (sourceImage != null) {
                sourceImage.flush();
            }
            if (resultImage != null) {
                resultImage.flush();
            }
        }
    }

    /**
     * 向上偏移图片以避免打印机偏移问题
     * 舍弃顶部指定像素,整体向上移动,并在底部补充白底
     *
     * @param sourceFile 源图片文件
     * @param targetFile 目标图片文件
     * @param offsetPixels 向上偏移的像素数(舍弃顶部的像素数,底部补充相同像素的白底)
     * @throws IOException 读取或写入文件失败
     * @deprecated 使用 addBorderAndShiftUp 代替
     */
    @Deprecated
    public static void shiftImageUp(File sourceFile, File targetFile, int offsetPixels) throws IOException {
        BufferedImage sourceImage = null;
        BufferedImage shiftedImage = null;
        try {
            sourceImage = ImageIO.read(sourceFile);
            if (sourceImage == null) {
                throw new IOException("无法读取图片文件: " + sourceFile.getPath());
            }

            int width = sourceImage.getWidth();
            int height = sourceImage.getHeight();

            if (offsetPixels <= 0 || offsetPixels >= height) {
                throw new IllegalArgumentException("偏移像素必须大于0且小于图片高度,当前值: " + offsetPixels + ", 图片高度: " + height);
            }

            // 创建新图片,保持原始宽度和高度
            shiftedImage = new BufferedImage(width, height, sourceImage.getType());
            Graphics2D g2d = shiftedImage.createGraphics();

            try {
                // 先填充白色背景
                g2d.setColor(java.awt.Color.WHITE);
                g2d.fillRect(0, 0, width, height);

                // 从源图的offsetPixels位置开始截取到底部,绘制到目标图的顶部
                // 源图: 从(0, offsetPixels)到(width, height)的区域
                // 目标图: 绘制到(0, 0)到(width, height-offsetPixels)的区域
                g2d.drawImage(sourceImage, 0, 0, width, height - offsetPixels,
                             0, offsetPixels, width, height, null);

                // 底部的offsetPixels像素保持白色(已通过fillRect填充)
            } finally {
                g2d.dispose();
            }

            // 保存处理后的图片
            ImageIO.write(shiftedImage, "png", targetFile);
            log.info("图片向上偏移成功,原始尺寸: {}x{}, 偏移: {}px, 结果尺寸: {}x{} (底部补充{}px白底)",
                    width, height, offsetPixels, width, height, offsetPixels);
        } finally {
            if (sourceImage != null) {
                sourceImage.flush();
            }
            if (shiftedImage != null) {
                shiftedImage.flush();
            }
        }
    }

    /**
     * 裁切策略内部类
     */
    private static class CropStrategy {
        int rotationDegrees;  // 0, 90, 或 270
        int pixelsLost;       // 损失的像素数
    }

    public static class Base64DecodedMultipartFile implements MultipartFile {

        private final byte[] imgContent;
        private final String header;

        public Base64DecodedMultipartFile(byte[] imgContent, String header) {
            this.imgContent = imgContent;
            this.header = header.split(";")[0];
        }

        @Override
        public String getName() {
            return System.currentTimeMillis() + Math.random() + "." + header.split("/")[1];
        }

        @Override
        public String getOriginalFilename() {
            return System.currentTimeMillis() + "." + header.split("/")[1];
        }

        @Override
        public String getContentType() {
            return "";
        }

        @Override
        public boolean isEmpty() {
            return imgContent == null || imgContent.length == 0;
        }

        @Override
        public long getSize() {
            return imgContent.length;
        }

        @Override
        public byte[] getBytes() {
            return imgContent;
        }

        @Override
        public InputStream getInputStream() {
            return new ByteArrayInputStream(imgContent);
        }

        @Override
        public void transferTo(File dest) throws IOException, IllegalStateException {
            new FileOutputStream(dest).write(imgContent);
        }

    }

}
