package de.winniepat.citrus.cosmetics.capes;

import de.winniepat.citrus.Client;
import de.winniepat.citrus.cosmetics.capes.sync.CapeSyncManager;
import de.winniepat.citrus.managers.ClientRegistrationManager;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.client.texture.*;
import net.minecraft.client.util.SkinTextures;
import net.minecraft.util.*;
import org.w3c.dom.*;

import javax.xml.parsers.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.*;
import java.util.concurrent.*;

public class CapeHandler {
    private static final Map<String, CapeEntry> CAPE_CACHE = new ConcurrentHashMap<>();
    private static final Map<UUID, String> PLAYER_CAPES = new ConcurrentHashMap<>();
    private static final Set<String> LOADING_CAPES = ConcurrentHashMap.newKeySet();
    private static final Set<String> FAILED_CAPES = ConcurrentHashMap.newKeySet();
    private static String localCapeOverride = null;
    private static boolean overrideFromSync = false;

    private static final String CDN_BASE_URL = Client.CDN_URL + "/capes/";
    private static List<String> AVAILABLE_CAPES = new ArrayList<>();
    private static boolean capesListFetched = false;
    private static boolean isInitialized = false;

    private static final String MOD_ID = "citrus";

    private static class CapeEntry {
        final Identifier textureId;
        final NativeImageBackedTexture texture;
        final long loadTime;

        CapeEntry(Identifier textureId, NativeImageBackedTexture texture) {
            this.textureId = textureId;
            this.texture = texture;
            this.loadTime = System.currentTimeMillis();
        }
    }

    public static void fetchCapeListFromCDN() {
        Client.debugLog("Fetching cape list from CDN...");

        CompletableFuture.runAsync(() -> {
            try {
                URL url = new URI(CDN_BASE_URL).toURL();
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET");
                connection.setConnectTimeout(10000);
                connection.setReadTimeout(10000);

                if (connection.getResponseCode() == 200) {
                    try (InputStream stream = connection.getInputStream()) {
                        byte[] xmlData = stream.readAllBytes();
                        parseCapeListFromXML(xmlData);
                    }
                } else {
                    Client.logger.error("Failed to fetch cape list. HTTP {}", connection.getResponseCode());
                    loadDefaultCapes();
                }

                connection.disconnect();

            } catch (Exception e) {
                Client.logger.error("Error fetching cape list", e);
                loadDefaultCapes();
            }
        }, Util.getMainWorkerExecutor());
    }

    private static void parseCapeListFromXML(byte[] xmlData) {
        try {
            List<String> discoveredCapes = new ArrayList<>();

            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.parse(new ByteArrayInputStream(xmlData));

            NodeList keyNodes = doc.getElementsByTagName("Key");

            for (int i = 0; i < keyNodes.getLength(); i++) {
                Node node = keyNodes.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    String keyValue = node.getTextContent().trim();

                    if (keyValue.toLowerCase().endsWith(".png")) {
                        String capeName = keyValue.substring(0, keyValue.length() - 4);
                        discoveredCapes.add(capeName);

                        Client.debugLog("Found cape: " + capeName + " (file: " + keyValue + ")");
                    }
                }
            }

            if (discoveredCapes.isEmpty()) {
                Client.logger.error("No PNG files found in CDN response");
                loadDefaultCapes();
            } else {
                AVAILABLE_CAPES = discoveredCapes;
                capesListFetched = true;

                Client.debugLog("Successfully loaded " + discoveredCapes.size() + " capes from CDN");

                preloadLocalPlayerCape();
            }

        } catch (Exception e) {
            Client.logger.error("Error parsing cape list from XML", e);
            loadDefaultCapes();
        }
    }

    private static void loadDefaultCapes() {
        Client.debugLog("Using default cape list");

        AVAILABLE_CAPES = Arrays.asList("strick", "cat");
        capesListFetched = true;
        preloadLocalPlayerCape();
    }

    private static void ensureCapeListFetched() {
        if (!capesListFetched && !isInitialized) {
            fetchCapeListFromCDN();
        }
    }

    public static SkinTextures getCapes(SkinTextures original, AbstractClientPlayerEntity player) {
        if (player == null) return original;

        if (!ClientRegistrationManager.isPlayerOnlineWithCitrus(player.getUuid())) {
            return original;
        }

        UUID playerUUID = player.getUuid();
        String playerName = player.getName().getString();

        String capeType = getCapeForPlayer(playerUUID, playerName);

        if (capeType == null || "none".equals(capeType) || FAILED_CAPES.contains(capeType)) {
            return original;
        }

        CapeEntry capeEntry = CAPE_CACHE.get(capeType);
        if (capeEntry != null) {
            return new SkinTextures(
                    original.texture(),
                    original.textureUrl(),
                    capeEntry.textureId,
                    capeEntry.textureId,
                    original.model(),
                    original.secure()
            );
        } else {
            if (!LOADING_CAPES.contains(capeType) && !FAILED_CAPES.contains(capeType)) {
                startCapeLoading(capeType);
            }
            return original;
        }
    }

    private static String getCapeForPlayer(UUID uuid, String name) {
        if (!ClientRegistrationManager.isPlayerOnlineWithCitrus(uuid)) {
            return null;
        }

        MinecraftClient client = MinecraftClient.getInstance();
        if (client.player != null && client.player.getUuid().equals(uuid) && localCapeOverride != null) {
            return localCapeOverride.equals("none") ? null : localCapeOverride;
        }

        if (PLAYER_CAPES.containsKey(uuid)) {
            return PLAYER_CAPES.get(uuid);
        }

        String syncedCape = CapeSyncManager.getSyncedCape(uuid, name);
        if (syncedCape != null) {
            PLAYER_CAPES.put(uuid, syncedCape);
            return syncedCape;
        }

        if (AVAILABLE_CAPES.isEmpty()) {
            ensureCapeListFetched();
            return null;
        }

        int hash = Math.abs((uuid + name).hashCode());
        int index = hash % AVAILABLE_CAPES.size();
        String capeType = AVAILABLE_CAPES.get(index);

        PLAYER_CAPES.put(uuid, capeType);
        return capeType;
    }

    private static void startCapeLoading(String capeType) {
        if (LOADING_CAPES.contains(capeType)) return;

        LOADING_CAPES.add(capeType);

        CompletableFuture.runAsync(() -> {
            try {
                byte[] imageData = downloadCapeImage(capeType);

                if (imageData.length == 0) {
                    Client.logger.error("Empty image data for cape {}", capeType);
                    markCapeAsFailed(capeType, "Empty data");
                    return;
                }

                if (!isValidPng(imageData)) {
                    Client.logger.error("Invalid PNG signature for cape {}", capeType);
                    Client.logger.error("First 8 bytes: {}", bytesToHex(imageData, 8));
                    markCapeAsFailed(capeType, "Invalid PNG signature");
                    return;
                }

                NativeImage nativeImage = null;
                try {
                    nativeImage = NativeImage.read(new ByteArrayInputStream(imageData));

                    if (nativeImage.getWidth() > 512 || nativeImage.getHeight() > 512) {

                        Client.logger.error("Cape image too large: {} ({}x{})", capeType, nativeImage.getWidth(), nativeImage.getHeight());
                        nativeImage.close();
                        markCapeAsFailed(capeType, "Image too large");
                        return;
                    }

                    scheduleTextureCreation(capeType, nativeImage);

                } catch (Exception e) {
                    Client.logger.error("Failed to parse PNG for cape {}", capeType, e);
                    if (nativeImage != null) {
                        nativeImage.close();
                    }
                    markCapeAsFailed(capeType, "PNG parsing failed: " + e.getMessage());
                }

            } catch (Exception e) {
                Client.logger.error("Failed to load cape {}", capeType, e);
                markCapeAsFailed(capeType, "Download failed: " + e.getMessage());
            }
        }, Util.getMainWorkerExecutor());
    }

    private static byte[] downloadCapeImage(String capeType) throws Exception {
        String capeUrl = CDN_BASE_URL + capeType + ".png";

        Client.debugLog("Downloading cape from: " + capeUrl);

        URL url = new URI(capeUrl).toURL();
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestProperty("User-Agent", "Minecraft Mod");
        connection.setConnectTimeout(10000);
        connection.setReadTimeout(10000);
        connection.setInstanceFollowRedirects(true);

        try {
            int responseCode = connection.getResponseCode();

            Client.debugLog("Response code for " + capeType + ": " + responseCode);

            if (responseCode != 200) {
                try (InputStream errorStream = connection.getErrorStream()) {
                    if (errorStream != null) {
                        byte[] errorBytes = errorStream.readAllBytes();
                        Client.logger.error("Error response {}", new String(errorBytes));
                    }
                }
                throw new IOException("HTTP " + responseCode + " for " + capeUrl);
            }

            try (InputStream stream = connection.getInputStream()) {
                return stream.readAllBytes();
            }

        } finally {
            connection.disconnect();
        }
    }

    private static boolean isValidPng(byte[] data) {
        if (data.length < 8) return false;

        return (data[0] & 0xFF) == 0x89 &&
                data[1] == 0x50 &&
                data[2] == 0x4E &&
                data[3] == 0x47 &&
                data[4] == 0x0D &&
                data[5] == 0x0A &&
                data[6] == 0x1A &&
                data[7] == 0x0A;
    }

    private static String bytesToHex(byte[] bytes, int length) {
        StringBuilder hex = new StringBuilder();
        for (int i = 0; i < Math.min(bytes.length, length); i++) {
            hex.append(String.format("%02X ", bytes[i] & 0xFF));
        }
        return hex.toString().trim();
    }

    private static void markCapeAsFailed(String capeType, String reason) {
        FAILED_CAPES.add(capeType);
        LOADING_CAPES.remove(capeType);
        Client.logger.error("Marked cape '{}' as failed: {}", capeType, reason);
    }

    private static void scheduleTextureCreation(String capeType, NativeImage nativeImage) {
        MinecraftClient.getInstance().execute(() -> {
            try {
                if (CAPE_CACHE.containsKey(capeType)) {
                    nativeImage.close();
                    LOADING_CAPES.remove(capeType);
                    return;
                }

                Identifier textureId = Identifier.of(MOD_ID, "capes/" + capeType);
                NativeImageBackedTexture texture = new NativeImageBackedTexture(nativeImage);
                MinecraftClient.getInstance().getTextureManager().registerTexture(textureId, texture);

                CAPE_CACHE.put(capeType, new CapeEntry(textureId, texture));
                FAILED_CAPES.remove(capeType);

                Client.debugLog("Successfully loaded cape: " + capeType + " (" + nativeImage.getWidth() + "x" + nativeImage.getHeight() + ")");

            } catch (Exception e) {
                Client.logger.error("Failed to create texture for cape {}", capeType, e);
                nativeImage.close();
                markCapeAsFailed(capeType, "Texture creation failed: " + e.getMessage());
            } finally {
                LOADING_CAPES.remove(capeType);
            }
        });
    }

    private static void preloadLocalPlayerCape() {
        MinecraftClient client = MinecraftClient.getInstance();
        if (client.player != null) {
            String capeType = getCapeForPlayer(client.player.getUuid(), client.player.getName().getString());
            if (capeType != null && !capeType.equals("none")) {
                startCapeLoading(capeType);
            }
        }
    }

    public static List<String> getAvailableCapes() {
        ensureCapeListFetched();
        return new ArrayList<>(AVAILABLE_CAPES);
    }

    public static boolean isCapeLoaded(String capeType) {
        return CAPE_CACHE.containsKey(capeType);
    }

    public static void setLocalPlayerCape(String capeType, boolean fromSync) {
        MinecraftClient client = MinecraftClient.getInstance();
        if (client.player == null) return;

        UUID uuid = client.player.getUuid();
        String playerName = client.player.getName().getString();

        if (fromSync) {
            overrideFromSync = true;
            localCapeOverride = capeType;
            PLAYER_CAPES.put(uuid, capeType);
        } else {
            overrideFromSync = false;
            localCapeOverride = capeType;

            CapeSyncManager.sendCapeToServer(uuid, capeType, playerName);

            PLAYER_CAPES.put(uuid, capeType);
        }

        if (capeType != null && !capeType.equals("none") && !CAPE_CACHE.containsKey(capeType)) {
            startCapeLoading(capeType);
        }

        client.player.getSkinTextures();
    }

    public static void clearLocalPlayerOverride() {
        localCapeOverride = null;
        MinecraftClient client = MinecraftClient.getInstance();
        if (client.player != null) {
            PLAYER_CAPES.remove(client.player.getUuid());
            client.player.getSkinTextures();
        }
    }

    public static String getCurrentCapeForLocalPlayer() {
        MinecraftClient client = MinecraftClient.getInstance();
        if (client.player == null) return null;

        if (localCapeOverride != null) {
            return localCapeOverride.equals("none") ? null : localCapeOverride;
        }

        return PLAYER_CAPES.get(client.player.getUuid());
    }

    public static String getAssignedCapeForLocalPlayer() {
        MinecraftClient client = MinecraftClient.getInstance();
        if (client.player == null) return null;

        UUID uuid = client.player.getUuid();
        if (PLAYER_CAPES.containsKey(uuid)) {
            return PLAYER_CAPES.get(uuid);
        }

        ensureCapeListFetched();
        if (AVAILABLE_CAPES.isEmpty()) return null;

        String name = client.player.getName().getString();
        int hash = Math.abs((uuid.toString() + name).hashCode());
        int index = hash % AVAILABLE_CAPES.size();
        return AVAILABLE_CAPES.get(index);
    }

    public static boolean capeExists(String capeName) {
        ensureCapeListFetched();
        return AVAILABLE_CAPES.contains(capeName);
    }

    public static void preloadSpecificCapes(Collection<String> capeTypes) {
        for (String capeType : capeTypes) {
            if (capeType != null &&
                    !capeType.equals("none") &&
                    !CAPE_CACHE.containsKey(capeType) &&
                    !LOADING_CAPES.contains(capeType) &&
                    !FAILED_CAPES.contains(capeType)) {
                startCapeLoading(capeType);
            }
        }
    }

    public static void reloadCapes() {
        clearCache();
        capesListFetched = false;
        fetchCapeListFromCDN();
    }

    public static void retryFailedCapes() {
        List<String> failed = new ArrayList<>(FAILED_CAPES);
        FAILED_CAPES.clear();
        for (String capeType : failed) {
            startCapeLoading(capeType);
        }
    }

    public static void clearCache() {
        for (CapeEntry entry : CAPE_CACHE.values()) {
            entry.texture.close();
        }
        CAPE_CACHE.clear();
        PLAYER_CAPES.clear();
        LOADING_CAPES.clear();
        FAILED_CAPES.clear();
    }

    public static String getCapeStatus(String capeType) {
        if (CAPE_CACHE.containsKey(capeType)) {
            return "loaded";
        } else if (LOADING_CAPES.contains(capeType)) {
            return "loading";
        } else if (FAILED_CAPES.contains(capeType)) {
            return "failed";
        } else {
            return "not_loaded";
        }
    }

    public static void init() {
        if (isInitialized) return;

        Client.debugLog("Initializing dynamic cape system...");
        isInitialized = true;
        fetchCapeListFromCDN();
    }

    public static void refreshCapeList() {
        capesListFetched = false;
        fetchCapeListFromCDN();
    }

    public static String getLocalCapeOverride() {
        return localCapeOverride;
    }

    public static String getSyncedCapeForPlayer(UUID uuid, String name) {
        String syncedCape = CapeSyncManager.getSyncedCape(uuid, name);
        if (syncedCape != null) {
            return syncedCape;
        }

        MinecraftClient client = MinecraftClient.getInstance();
        if (client.player != null && client.player.getUuid().equals(uuid) && localCapeOverride != null) {
            return localCapeOverride.equals("none") ? null : localCapeOverride;
        }

        if (AVAILABLE_CAPES.isEmpty()) {
            return null;
        }

        int hash = Math.abs((uuid.toString() + name).hashCode());
        int index = hash % AVAILABLE_CAPES.size();
        String capeType = AVAILABLE_CAPES.get(index);

        PLAYER_CAPES.put(uuid, capeType);
        return capeType;
    }

    public static void forceCacheCape(UUID uuid, String capeType) {
        if (capeType == null || "none".equals(capeType)) {
            PLAYER_CAPES.remove(uuid);
        } else {
            PLAYER_CAPES.put(uuid, capeType);
        }
    }

    public static void cachePlayerCape(UUID playerUUID, String capeType) {
        if (capeType == null || "none".equals(capeType)) {
            PLAYER_CAPES.put(playerUUID, null);
        } else {
            PLAYER_CAPES.put(playerUUID, capeType);
        }

        MinecraftClient client = MinecraftClient.getInstance();
        if (client.player != null && client.player.getUuid().equals(playerUUID)) {
            localCapeOverride = capeType;
            overrideFromSync = true;
        }

        Client.debugLog("Cached cape for " + playerUUID + ": " + (capeType == null ? "none" : capeType));
    }
}