package com.njcn.gather.device.util;

import org.springframework.web.multipart.MultipartFile;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.Locale;

public final class PqDevImageSupport {

    public static final long MAX_FILE_SIZE = 10L * 1024L * 1024L;
    public static final int MAX_EDGE = 1600;
    private static final String INVALID_IMAGE_CONTENT = "图片内容无效";
    private static final String UNSUPPORTED_IMAGE_TYPE = "仅支持 JPG、JPEG、PNG 图片";
    private static final String IMAGE_TOO_LARGE = "图片大小不能超过 10MB";
    private static final float JPEG_QUALITY = 0.82f;

    private PqDevImageSupport() {
    }

    public static byte[] processUploadToJpeg(MultipartFile image) throws IOException {
        if (image == null || image.isEmpty()) {
            throw new IllegalArgumentException(INVALID_IMAGE_CONTENT);
        }
        if (image.getSize() > MAX_FILE_SIZE) {
            throw new IllegalArgumentException(IMAGE_TOO_LARGE);
        }
        if (!hasAllowedDeclaredType(image)) {
            throw new IllegalArgumentException(UNSUPPORTED_IMAGE_TYPE);
        }

        byte[] originalBytes = image.getBytes();
        detectContentType(originalBytes);

        BufferedImage source = ImageIO.read(new ByteArrayInputStream(originalBytes));
        if (source == null) {
            throw new IllegalArgumentException(INVALID_IMAGE_CONTENT);
        }

        return writeJpeg(scaleIfNeeded(source));
    }

    public static String detectContentType(byte[] imageBytes) {
        if (imageBytes == null || imageBytes.length < 4) {
            throw new IllegalArgumentException(INVALID_IMAGE_CONTENT);
        }
        if (isPng(imageBytes)) {
            return "image/png";
        }
        if (isJpeg(imageBytes)) {
            return "image/jpeg";
        }
        throw new IllegalArgumentException(UNSUPPORTED_IMAGE_TYPE);
    }

    private static boolean hasAllowedDeclaredType(MultipartFile image) {
        String normalizedName = normalize(image.getOriginalFilename());
        String normalizedContentType = normalize(image.getContentType());
        boolean hasContentType = !normalizedContentType.isEmpty();
        boolean hasKnownExtension = normalizedName.endsWith(".jpg")
                || normalizedName.endsWith(".jpeg")
                || normalizedName.endsWith(".png")
                || normalizedName.matches(".*\\.[^.]+$");
        boolean contentTypeAllowed = normalizedContentType.equals("image/jpeg")
                || normalizedContentType.equals("image/jpg")
                || normalizedContentType.equals("image/png");
        boolean extensionAllowed = normalizedName.endsWith(".jpg")
                || normalizedName.endsWith(".jpeg")
                || normalizedName.endsWith(".png");

        if (hasContentType && !contentTypeAllowed) {
            return false;
        }
        if (hasKnownExtension && !extensionAllowed) {
            return false;
        }
        return contentTypeAllowed || extensionAllowed;
    }

    private static BufferedImage scaleIfNeeded(BufferedImage source) {
        int width = source.getWidth();
        int height = source.getHeight();
        int max = Math.max(width, height);
        if (max <= MAX_EDGE) {
            return source;
        }

        double scale = (double) MAX_EDGE / max;
        int targetWidth = Math.max(1, (int) Math.round(width * scale));
        int targetHeight = Math.max(1, (int) Math.round(height * scale));
        BufferedImage target = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics = target.createGraphics();
        try {
            graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            graphics.drawImage(source, 0, 0, targetWidth, targetHeight, null);
        } finally {
            graphics.dispose();
        }
        return target;
    }

    private static byte[] writeJpeg(BufferedImage image) throws IOException {
        BufferedImage rgbImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = rgbImage.createGraphics();
        try {
            graphics.setColor(Color.WHITE);
            graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
            graphics.drawImage(image, 0, 0, null);
        } finally {
            graphics.dispose();
        }

        Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
        if (!writers.hasNext()) {
            throw new IOException("JPG image writer not found");
        }
        ImageWriter writer = writers.next();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try (ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(outputStream)) {
            writer.setOutput(imageOutputStream);
            ImageWriteParam writeParam = writer.getDefaultWriteParam();
            if (writeParam.canWriteCompressed()) {
                writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                writeParam.setCompressionQuality(JPEG_QUALITY);
            }
            writer.write(null, new IIOImage(rgbImage, null, null), writeParam);
        } finally {
            writer.dispose();
        }
        return outputStream.toByteArray();
    }

    private static boolean isPng(byte[] imageBytes) {
        return imageBytes.length >= 8
                && (imageBytes[0] & 0xFF) == 0x89
                && imageBytes[1] == 0x50
                && imageBytes[2] == 0x4E
                && imageBytes[3] == 0x47
                && imageBytes[4] == 0x0D
                && imageBytes[5] == 0x0A
                && imageBytes[6] == 0x1A
                && imageBytes[7] == 0x0A;
    }

    private static boolean isJpeg(byte[] imageBytes) {
        return imageBytes.length >= 3
                && (imageBytes[0] & 0xFF) == 0xFF
                && (imageBytes[1] & 0xFF) == 0xD8
                && (imageBytes[2] & 0xFF) == 0xFF;
    }

    private static String normalize(String value) {
        return value == null ? "" : value.toLowerCase(Locale.ROOT);
    }
}
