package org.example.petshopdesktop.util;

import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import org.example.petshopdesktop.api.ApiClient;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

public final class DesktopImageSupport {

    private static final Map<String, Image> IMAGE_CACHE = new ConcurrentHashMap<>();
    private static final Map<String, Integer> ORIENTATION_CACHE = new ConcurrentHashMap<>();
    private static final AtomicBoolean PLUGINS_SCANNED = new AtomicBoolean(false);

    private DesktopImageSupport() {
    }

    private static void ensurePlugins() {
        if (PLUGINS_SCANNED.compareAndSet(false, true)) {
            ImageIO.scanForPlugins();
        }
    }

    private static boolean isWebP(byte[] data) {
        return data.length >= 12
                && data[0] == 'R' && data[1] == 'I' && data[2] == 'F' && data[3] == 'F'
                && data[8] == 'W' && data[9] == 'E' && data[10] == 'B' && data[11] == 'P';
    }

    private static Image decodeImage(byte[] bytes) {
        if (isWebP(bytes)) {
            ensurePlugins();
            try {
                BufferedImage bi = ImageIO.read(new ByteArrayInputStream(bytes));
                if (bi != null) {
                    return SwingFXUtils.toFXImage(bi, null);
                }
            } catch (Exception ignored) {
            }
            return null;
        }
        Image img = new Image(new ByteArrayInputStream(bytes));
        return img.isError() ? null : img;
    }

    public static void loadImageInto(ImageView imageView, String imageUrl, double width, double height) {
        imageView.setFitWidth(width);
        imageView.setFitHeight(height);
        imageView.setPreserveRatio(true);
        imageView.setSmooth(true);
        imageView.setImage(null);
        imageView.setRotate(0);

        if (imageUrl == null || imageUrl.isBlank()) {
            return;
        }

        if (imageUrl.startsWith("file:")) {
            new Thread(() -> {
                try {
                    byte[] bytes = java.net.URI.create(imageUrl).toURL().openStream().readAllBytes();
                    int orientation = readExifOrientation(bytes);
                    Image image = decodeImage(bytes);
                    if (image != null) {
                        Platform.runLater(() -> {
                            imageView.setImage(image);
                            imageView.setRotate(exifOrientationToAngle(orientation));
                        });
                    }
                } catch (Exception ignored) {
                }
            }, "desktop-image-loader").start();
            return;
        }

        Image cached = IMAGE_CACHE.get(imageUrl);
        if (cached != null) {
            imageView.setImage(cached);
            imageView.setRotate(exifOrientationToAngle(ORIENTATION_CACHE.getOrDefault(imageUrl, 1)));
            return;
        }

        new Thread(() -> {
            try {
                byte[] bytes = ApiClient.getInstance().getBytes(imageUrl);
                int orientation = readExifOrientation(bytes);
                Image image = decodeImage(bytes);
                if (image != null) {
                    IMAGE_CACHE.put(imageUrl, image);
                    ORIENTATION_CACHE.put(imageUrl, orientation);
                    Platform.runLater(() -> {
                        imageView.setImage(image);
                        imageView.setRotate(exifOrientationToAngle(orientation));
                    });
                }
            } catch (Exception ignored) {
            }
        }, "desktop-image-loader").start();
    }

    public static void evict(String imageUrl) {
        if (imageUrl != null && !imageUrl.isBlank()) {
            IMAGE_CACHE.remove(imageUrl);
            ORIENTATION_CACHE.remove(imageUrl);
        }
    }

    private static double exifOrientationToAngle(int orientation) {
        return switch (orientation) {
            case 3 -> 180;
            case 6 -> 90;
            case 8 -> -90;
            default -> 0;
        };
    }

    private static int readExifOrientation(byte[] data) {
        try {
            if (data.length < 2 || (data[0] & 0xFF) != 0xFF || (data[1] & 0xFF) != 0xD8) return 1;
            int offset = 2;
            while (offset + 4 < data.length) {
                if ((data[offset] & 0xFF) != 0xFF) break;
                int marker = data[offset + 1] & 0xFF;
                int segLen = ((data[offset + 2] & 0xFF) << 8) | (data[offset + 3] & 0xFF);
                if (marker == 0xE1 && offset + 9 < data.length
                        && data[offset + 4] == 'E' && data[offset + 5] == 'x'
                        && data[offset + 6] == 'i' && data[offset + 7] == 'f'
                        && data[offset + 8] == 0 && data[offset + 9] == 0) {
                    int tiff = offset + 10;
                    boolean le = data[tiff] == 'I';
                    int ifdOffset = readInt(data, tiff + 4, le);
                    int ifd = tiff + ifdOffset;
                    int entries = readShort(data, ifd, le);
                    for (int i = 0; i < entries; i++) {
                        int e = ifd + 2 + i * 12;
                        if (e + 9 < data.length && readShort(data, e, le) == 0x0112) {
                            return readShort(data, e + 8, le);
                        }
                    }
                }
                if (marker == 0xDA) break;
                offset += 2 + segLen;
            }
        } catch (Exception ignored) {}
        return 1;
    }

    private static int readShort(byte[] data, int offset, boolean le) {
        return le
                ? (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8)
                : ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
    }

    private static int readInt(byte[] data, int offset, boolean le) {
        return le
                ? (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8) | ((data[offset + 2] & 0xFF) << 16) | ((data[offset + 3] & 0xFF) << 24)
                : ((data[offset] & 0xFF) << 24) | ((data[offset + 1] & 0xFF) << 16) | ((data[offset + 2] & 0xFF) << 8) | (data[offset + 3] & 0xFF);
    }
}
