package com.njcn.gather.device.util;

import org.junit.Test;
import org.springframework.mock.web.MockMultipartFile;

import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

public class PqDevImageSupportTest {

    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";

    @Test
    public void processUploadConvertsPngToJpeg() throws Exception {
        byte[] pngBytes = createImageBytes("png", 16, 10, BufferedImage.TYPE_INT_ARGB);
        MockMultipartFile image = new MockMultipartFile("image", "test.png", "image/png", pngBytes);

        byte[] result = PqDevImageSupport.processUploadToJpeg(image);

        assertEquals("image/jpeg", PqDevImageSupport.detectContentType(result));
        BufferedImage decoded = ImageIO.read(new ByteArrayInputStream(result));
        assertNotNull(decoded);
        assertEquals(16, decoded.getWidth());
        assertEquals(10, decoded.getHeight());
    }

    @Test
    public void processUploadConvertsTransparentPngToWhiteJpegBackground() throws Exception {
        byte[] pngBytes = createTransparentImageBytes();
        MockMultipartFile image = new MockMultipartFile("image", "transparent.png", "image/png", pngBytes);

        byte[] result = PqDevImageSupport.processUploadToJpeg(image);

        BufferedImage decoded = ImageIO.read(new ByteArrayInputStream(result));
        assertNotNull(decoded);
        Color background = new Color(decoded.getRGB(0, 0));
        assertTrue(background.getRed() >= 245);
        assertTrue(background.getGreen() >= 245);
        assertTrue(background.getBlue() >= 245);
    }

    @Test
    public void processUploadKeepsJpegReadable() throws Exception {
        byte[] jpegBytes = createImageBytes("jpg", 12, 8, BufferedImage.TYPE_INT_RGB);
        MockMultipartFile image = new MockMultipartFile("image", "test.jpeg", "image/jpeg", jpegBytes);

        byte[] result = PqDevImageSupport.processUploadToJpeg(image);

        assertEquals("image/jpeg", PqDevImageSupport.detectContentType(result));
        BufferedImage decoded = ImageIO.read(new ByteArrayInputStream(result));
        assertNotNull(decoded);
        assertEquals(12, decoded.getWidth());
        assertEquals(8, decoded.getHeight());
    }

    @Test
    public void processUploadRejectsInvalidBytes() throws Exception {
        MockMultipartFile image = new MockMultipartFile("image", "test.png", "image/png", new byte[]{1, 2, 3});

        assertIllegalArgumentMessage(INVALID_IMAGE_CONTENT, () -> PqDevImageSupport.processUploadToJpeg(image));
    }

    @Test
    public void processUploadRejectsNullImage() {
        assertIllegalArgumentMessage(INVALID_IMAGE_CONTENT, () -> PqDevImageSupport.processUploadToJpeg(null));
    }

    @Test
    public void processUploadRejectsEmptyImage() {
        MockMultipartFile image = new MockMultipartFile("image", "empty.png", "image/png", new byte[0]);

        assertIllegalArgumentMessage(INVALID_IMAGE_CONTENT, () -> PqDevImageSupport.processUploadToJpeg(image));
    }

    @Test
    public void processUploadRejectsOversizedImage() {
        MockMultipartFile image = new MockMultipartFile(
                "image",
                "large.jpg",
                "image/jpeg",
                new byte[(int) PqDevImageSupport.MAX_FILE_SIZE + 1]
        );

        assertIllegalArgumentMessage(IMAGE_TOO_LARGE, () -> PqDevImageSupport.processUploadToJpeg(image));
    }

    @Test
    public void processUploadRejectsUnsupportedType() throws Exception {
        byte[] pngBytes = createImageBytes("png", 2, 2, BufferedImage.TYPE_INT_RGB);
        MockMultipartFile image = new MockMultipartFile("image", "test.gif", "image/gif", pngBytes);

        assertIllegalArgumentMessage(UNSUPPORTED_IMAGE_TYPE, () -> PqDevImageSupport.processUploadToJpeg(image));
    }

    @Test
    public void processUploadRejectsUnsupportedContentTypeEvenWithAllowedFileName() throws Exception {
        byte[] pngBytes = createImageBytes("png", 2, 2, BufferedImage.TYPE_INT_RGB);
        MockMultipartFile image = new MockMultipartFile("image", "test.png", "image/gif", pngBytes);

        assertIllegalArgumentMessage(UNSUPPORTED_IMAGE_TYPE, () -> PqDevImageSupport.processUploadToJpeg(image));
    }

    @Test
    public void processUploadRejectsUnsupportedFileNameEvenWithAllowedContentType() throws Exception {
        byte[] pngBytes = createImageBytes("png", 2, 2, BufferedImage.TYPE_INT_RGB);
        MockMultipartFile image = new MockMultipartFile("image", "test.gif", "image/png", pngBytes);

        assertIllegalArgumentMessage(UNSUPPORTED_IMAGE_TYPE, () -> PqDevImageSupport.processUploadToJpeg(image));
    }

    @Test
    public void processUploadRejectsUnsupportedImageBytes() throws Exception {
        byte[] gifBytes = new byte[]{0x47, 0x49, 0x46, 0x38, 0x39, 0x61};
        MockMultipartFile image = new MockMultipartFile("image", "test.png", "image/png", gifBytes);

        assertIllegalArgumentMessage(UNSUPPORTED_IMAGE_TYPE, () -> PqDevImageSupport.processUploadToJpeg(image));
    }

    @Test
    public void processUploadScalesLargeImage() throws Exception {
        byte[] jpegBytes = createImageBytes("jpg", 3200, 1200, BufferedImage.TYPE_INT_RGB);
        MockMultipartFile image = new MockMultipartFile("image", "large.jpg", "image/jpeg", jpegBytes);

        byte[] result = PqDevImageSupport.processUploadToJpeg(image);

        BufferedImage decoded = ImageIO.read(new ByteArrayInputStream(result));
        assertNotNull(decoded);
        assertEquals(PqDevImageSupport.MAX_EDGE, Math.max(decoded.getWidth(), decoded.getHeight()));
        assertEquals(1600, decoded.getWidth());
        assertEquals(600, decoded.getHeight());
    }

    @Test
    public void detectContentTypeRejectsInvalidImageBytes() {
        assertIllegalArgumentMessage(INVALID_IMAGE_CONTENT, () -> PqDevImageSupport.detectContentType(new byte[]{1, 2, 3}));
    }

    @Test
    public void detectContentTypeRejectsUnsupportedImageBytes() {
        assertIllegalArgumentMessage(UNSUPPORTED_IMAGE_TYPE, () -> PqDevImageSupport.detectContentType(new byte[]{1, 2, 3, 4}));
    }

    private byte[] createImageBytes(String format, int width, int height, int type) throws Exception {
        BufferedImage image = new BufferedImage(width, height, type);
        Graphics2D graphics = image.createGraphics();
        try {
            graphics.setColor(new Color(255, 0, 0, type == BufferedImage.TYPE_INT_ARGB ? 120 : 255));
            graphics.fillRect(0, 0, width, height);
            graphics.setColor(Color.BLUE);
            graphics.fillRect(width / 2, height / 2, Math.max(1, width / 2), Math.max(1, height / 2));
        } finally {
            graphics.dispose();
        }

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        assertTrue(ImageIO.write(image, format, outputStream));
        return outputStream.toByteArray();
    }

    private byte[] createTransparentImageBytes() throws Exception {
        BufferedImage image = new BufferedImage(4, 4, BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics = image.createGraphics();
        try {
            graphics.setComposite(java.awt.AlphaComposite.Clear);
            graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
            graphics.setComposite(java.awt.AlphaComposite.SrcOver);
            graphics.setColor(new Color(0, 0, 255, 128));
            graphics.fillRect(2, 2, 2, 2);
        } finally {
            graphics.dispose();
        }

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        assertTrue(ImageIO.write(image, "png", outputStream));
        return outputStream.toByteArray();
    }

    private void assertIllegalArgumentMessage(String expectedMessage, ThrowingRunnable runnable) {
        try {
            runnable.run();
        } catch (IllegalArgumentException ex) {
            assertEquals(expectedMessage, ex.getMessage());
            return;
        } catch (Exception ex) {
            throw new AssertionError("Expected IllegalArgumentException", ex);
        }
        throw new AssertionError("Expected IllegalArgumentException");
    }

    private interface ThrowingRunnable {
        void run() throws Exception;
    }
}
