package de.winniepat.citrus.cosmetics;

import com.google.gson.*;
import de.winniepat.citrus.Client;
import de.winniepat.citrus.managers.ClientRegistrationManager;
import de.winniepat.citrus.utils.SecurityUtils;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.Util;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;

public class CosmeticSyncManager {
    private static final String API_BASE_URL = Client.API_URL;
    private static final Gson GSON = new GsonBuilder().create();

    public static final Map<UUID, SyncedCosmeticData> SYNCED_COSMETIC_CACHE = new ConcurrentHashMap<>();
    private static final Map<UUID, Long> LAST_SYNC_TIME = new ConcurrentHashMap<>();
    private static final Map<UUID, SyncedCosmeticData> PENDING_UPDATES = new ConcurrentHashMap<>();
    private static final Set<UUID> PENDING_FETCHES = ConcurrentHashMap.newKeySet();

    private static boolean hasSentInitialCosmetics = false;
    private static boolean isInitializing = false;
    private static boolean autoSyncEnabled = true;
    private static Timer syncTimer;

    public static class SyncedCosmeticData {
        public String wingType = "none";
        public boolean wingsVisible = true;
        public String hatType = "none";
        public boolean hatVisible = true;
        public String haloType = "none";
        public boolean haloVisible = true;

        public JsonObject toJson() {
            JsonObject json = new JsonObject();
            json.addProperty("wingType", wingType);
            json.addProperty("wingsVisible", wingsVisible);
            json.addProperty("hatType", hatType);
            json.addProperty("hatVisible", hatVisible);
            json.addProperty("haloType", haloType);
            json.addProperty("haloVisible", haloVisible);
            return json;
        }

        public static SyncedCosmeticData fromJson(JsonObject json) {
            SyncedCosmeticData data = new SyncedCosmeticData();
            if (json.has("wingType")) data.wingType = json.get("wingType").getAsString();
            if (json.has("wingsVisible")) data.wingsVisible = json.get("wingsVisible").getAsBoolean();
            if (json.has("hatType")) data.hatType = json.get("hatType").getAsString();
            if (json.has("hatVisible")) data.hatVisible = json.get("hatVisible").getAsBoolean();
            if (json.has("haloType")) data.haloType = json.get("haloType").getAsString();
            if (json.has("haloVisible")) data.haloVisible = json.get("haloVisible").getAsBoolean();
            return data;
        }

        public static SyncedCosmeticData fromClientCosmeticData(ClientCosmeticManager.PlayerCosmeticData clientData) {
            SyncedCosmeticData data = new SyncedCosmeticData();
            data.wingType = clientData.activeWings;
            data.wingsVisible = clientData.wingsVisible;
            data.hatType = clientData.activeHat;
            data.hatVisible = clientData.hatVisible;
            data.haloType = clientData.activeHalo;
            data.haloVisible = clientData.haloVisible;
            return data;
        }

        public ClientCosmeticManager.PlayerCosmeticData toClientCosmeticData() {
            ClientCosmeticManager.PlayerCosmeticData data = new ClientCosmeticManager.PlayerCosmeticData();
            data.activeWings = this.wingType;
            data.wingsVisible = this.wingsVisible;
            data.activeHat = this.hatType;
            data.hatVisible = this.hatVisible;
            data.activeHalo = this.haloType;
            data.haloVisible = this.haloVisible;
            return data;
        }
    }

    public static void init() {
        Client.debugLog("Initializing cosmetic sync...");
        isInitializing = true;
        startAutoSync();
        scheduleInitialSync();
    }

    private static void scheduleInitialSync() {
        MinecraftClient client = MinecraftClient.getInstance();
        if (client.player != null) {
            performInitialSync();
        } else {
            Util.getMainWorkerExecutor().execute(() -> {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                performInitialSync();
            });
        }
    }

    private static void performInitialSync() {
        MinecraftClient client = MinecraftClient.getInstance();
        if (client.player == null) {
            isInitializing = false;
            return;
        }

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

        Client.debugLog("Performing initial cosmetic sync for " + playerName);
        fetchCosmeticsFromServer(uuid, playerName, true).thenAccept(serverData -> {
            if (serverData != null) {
                applyServerCosmeticsToLocal(uuid, serverData);
            } else {
                ClientCosmeticManager.PlayerCosmeticData localData = ClientCosmeticManager.getData(uuid);
                if (localData != null) {
                    SyncedCosmeticData syncData = SyncedCosmeticData.fromClientCosmeticData(localData);
                    sendCosmeticsToServer(uuid, syncData, playerName, true);
                }
            }
            hasSentInitialCosmetics = true;
            isInitializing = false;
        });
    }

    public static SyncedCosmeticData getSyncedCosmetics(UUID uuid, String playerName) {
        if (!ClientRegistrationManager.isPlayerOnlineWithCitrus(uuid)) {
            return null;
        }

        if (isInitializing && isLocalPlayer(uuid)) {
            return null;
        }

        if (SYNCED_COSMETIC_CACHE.containsKey(uuid)) {
            long lastSync = LAST_SYNC_TIME.getOrDefault(uuid, 0L);
            if (System.currentTimeMillis() - lastSync < 300000) {
                return SYNCED_COSMETIC_CACHE.get(uuid);
            }
        }

        if (isLocalPlayer(uuid) && !hasSentInitialCosmetics && !isInitializing) {
            performInitialSync();
            return null;
        }

        if (!PENDING_FETCHES.contains(uuid)) {
            fetchCosmeticsFromServer(uuid, playerName, false);
        }

        return null;
    }

    private static CompletableFuture<SyncedCosmeticData> fetchCosmeticsFromServer(UUID uuid, String playerName, boolean isInitial) {
        PENDING_FETCHES.add(uuid);
        return CompletableFuture.supplyAsync(() -> {
            try {
                String urlString = API_BASE_URL + "/cosmetics/" + uuid.toString();
                URL url = new URI(urlString).toURL();
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(10000);
                conn.setReadTimeout(10000);
                conn.setRequestProperty("x-daily-key", SecurityUtils.getDailyKey());

                if (conn.getResponseCode() == 200) {
                    try (InputStream is = conn.getInputStream()) {
                        String responseBody = new String(is.readAllBytes(), StandardCharsets.UTF_8);
                        JsonObject json = GSON.fromJson(responseBody, JsonObject.class);
                        SyncedCosmeticData cosmeticData = SyncedCosmeticData.fromJson(json);

                        SYNCED_COSMETIC_CACHE.put(uuid, cosmeticData);
                        LAST_SYNC_TIME.put(uuid, System.currentTimeMillis());
                        return cosmeticData;
                    }
                }
            } catch (Exception e) {
                Client.logger.error("Exception fetching cosmetics: {}", e.getMessage());
            } finally {
                PENDING_FETCHES.remove(uuid);
            }
            return null;
        }, Util.getMainWorkerExecutor());
    }

    public static void sendCosmeticsToServer(UUID uuid, SyncedCosmeticData cosmeticData, String playerName, boolean isInitial) {
        CompletableFuture.runAsync(() -> {
            try {
                URL url = new URI(API_BASE_URL + "/cosmetics").toURL();
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("POST");
                conn.setRequestProperty("Content-Type", "application/json");
                conn.setRequestProperty("x-daily-key", SecurityUtils.getDailyKey());
                conn.setDoOutput(true);
                conn.setConnectTimeout(3000);
                conn.setReadTimeout(3000);

                JsonObject json = new JsonObject();
                json.addProperty("uuid", uuid.toString());
                json.addProperty("playerName", playerName);
                json.add("cosmetics", cosmeticData.toJson());

                try (OutputStream os = conn.getOutputStream()) {
                    os.write(GSON.toJson(json).getBytes());
                }

                if (conn.getResponseCode() == 200) {
                    if (isInitial) hasSentInitialCosmetics = true;
                    SYNCED_COSMETIC_CACHE.put(uuid, cosmeticData);
                    LAST_SYNC_TIME.put(uuid, System.currentTimeMillis());
                    PENDING_UPDATES.remove(uuid);
                    Client.debugLog("Successfully synced cosmetics to server for " + playerName);
                } else {
                    PENDING_UPDATES.put(uuid, cosmeticData);
                    Client.logger.error("Failed to sync cosmetics. HTTP {}", conn.getResponseCode());
                }
            } catch (Exception e) {
                Client.logger.error("Failed to send cosmetics to server", e);
                PENDING_UPDATES.put(uuid, cosmeticData);
            }
        }, Util.getMainWorkerExecutor());
    }

    public static void sendCosmeticsToServer(UUID uuid, ClientCosmeticManager.PlayerCosmeticData clientData) {
        MinecraftClient client = MinecraftClient.getInstance();
        if (client.player != null) {
            sendCosmeticsToServer(uuid, clientData, client.player.getName().getString());
        }
    }

    public static void sendCosmeticsToServer(UUID uuid, ClientCosmeticManager.PlayerCosmeticData clientData, String playerName) {
        SyncedCosmeticData syncData = SyncedCosmeticData.fromClientCosmeticData(clientData);
        sendCosmeticsToServer(uuid, syncData, playerName, false);
    }

    private static void applyServerCosmeticsToLocal(UUID uuid, SyncedCosmeticData cosmeticData) {
        MinecraftClient.getInstance().execute(() -> {
            ClientCosmeticManager.PlayerCosmeticData localData = ClientCosmeticManager.getData(uuid);
            ClientCosmeticManager.PlayerCosmeticData serverData = cosmeticData.toClientCosmeticData();

            localData.activeWings = serverData.activeWings;
            localData.wingsVisible = serverData.wingsVisible;
            localData.activeHat = serverData.activeHat;
            localData.hatVisible = serverData.hatVisible;
            localData.activeHalo = serverData.activeHalo;
            localData.haloVisible = serverData.haloVisible;

            ClientCosmeticManager.save();
            Client.debugLog("Applied server cosmetics for " + uuid);
        });
    }

    public static void batchFetchCosmetics(List<UUID> uuids) {
        if (uuids.isEmpty()) return;

        List<UUID> citrusUsers = uuids.stream()
                .filter(ClientRegistrationManager::isPlayerOnlineWithCitrus)
                .toList();

        if (citrusUsers.isEmpty()) return;

        CompletableFuture.runAsync(() -> {
            try {
                URL url = new URI(API_BASE_URL + "/cosmetics/batch").toURL();
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("POST");
                conn.setRequestProperty("Content-Type", "application/json");
                conn.setRequestProperty("x-daily-key", SecurityUtils.getDailyKey());
                conn.setDoOutput(true);
                conn.setConnectTimeout(5000);
                conn.setReadTimeout(5000);

                JsonObject json = new JsonObject();
                JsonArray uuidArray = new JsonArray();
                for (UUID uuid : citrusUsers) {
                    uuidArray.add(uuid.toString());
                }
                json.add("uuids", uuidArray);

                try (OutputStream os = conn.getOutputStream()) {
                    os.write(GSON.toJson(json).getBytes());
                }

                if (conn.getResponseCode() == 200) {
                    try (InputStream is = conn.getInputStream()) {
                        String response = new String(is.readAllBytes(), StandardCharsets.UTF_8);
                        JsonObject result = GSON.fromJson(response, JsonObject.class);

                        for (String uuidStr : result.keySet()) {
                            try {
                                UUID uuid = UUID.fromString(uuidStr);
                                JsonObject cosmeticDataJson = result.getAsJsonObject(uuidStr);
                                SyncedCosmeticData cosmeticData = SyncedCosmeticData.fromJson(cosmeticDataJson);

                                SYNCED_COSMETIC_CACHE.put(uuid, cosmeticData);
                                LAST_SYNC_TIME.put(uuid, System.currentTimeMillis());
                            } catch (Exception e) {
                                Client.logger.error("Invalid UUID in cosmetics response: {}", uuidStr);
                            }
                        }

                        Client.debugLog("Batch fetched cosmetics for " + result.size() + " users");
                    }
                }
            } catch (Exception e) {
                Client.logger.error("Batch cosmetics fetch failed", e);
            }
        }, Util.getMainWorkerExecutor());
    }

    private static void startAutoSync() {
        if (syncTimer != null) {
            syncTimer.cancel();
        }

        syncTimer = new Timer("CosmeticAutoSync", true);
        syncTimer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                if (autoSyncEnabled && MinecraftClient.getInstance().player != null) {
                    performAutoSync();
                }
            }
        }, 5000, 10000);
    }

    private static void performAutoSync() {
        syncLocalPlayerCosmetics();
        retryPendingUpdates();
    }

    private static void syncLocalPlayerCosmetics() {
        MinecraftClient client = MinecraftClient.getInstance();
        if (client.player == null) return;

        UUID uuid = client.player.getUuid();
        ClientCosmeticManager.PlayerCosmeticData localData = ClientCosmeticManager.getData(uuid);

        if (localData != null) {
            SyncedCosmeticData cachedData = SYNCED_COSMETIC_CACHE.get(uuid);
            if (cachedData == null ||
                    !cachedData.wingType.equals(localData.activeWings) ||
                    cachedData.wingsVisible != localData.wingsVisible ||
                    !cachedData.hatType.equals(localData.activeHat) ||
                    cachedData.hatVisible != localData.hatVisible ||
                    !cachedData.haloType.equals(localData.activeHalo) ||
                    cachedData.haloVisible != localData.haloVisible) {

                sendCosmeticsToServer(uuid, localData);
            }
        }
    }

    static void retryPendingUpdates() {
        if (PENDING_UPDATES.isEmpty()) return;

        MinecraftClient client = MinecraftClient.getInstance();
        if (client.player == null) return;

        for (Map.Entry<UUID, SyncedCosmeticData> entry : PENDING_UPDATES.entrySet()) {
            String playerName = entry.getKey().equals(client.player.getUuid())
                    ? client.player.getName().getString()
                    : entry.getKey().toString();

            sendCosmeticsToServer(entry.getKey(), entry.getValue(), playerName, false);
        }
    }

    private static boolean isLocalPlayer(UUID uuid) {
        MinecraftClient client = MinecraftClient.getInstance();
        return client.player != null && client.player.getUuid().equals(uuid);
    }

    public static void clearCache(UUID uuid) {
        SYNCED_COSMETIC_CACHE.remove(uuid);
        LAST_SYNC_TIME.remove(uuid);
    }

    public static void clearAllCache() {
        SYNCED_COSMETIC_CACHE.clear();
        LAST_SYNC_TIME.clear();
        PENDING_UPDATES.clear();
        PENDING_FETCHES.clear();
        hasSentInitialCosmetics = false;
        isInitializing = false;
    }

    public static boolean hasInitialSyncCompleted() {
        return hasSentInitialCosmetics && !isInitializing;
    }

    public static boolean isInitializing() {
        return isInitializing;
    }

    public static void forceInitialSync() {
        if (!hasSentInitialCosmetics) {
            isInitializing = true;
            performInitialSync();
        }
    }

    public static boolean isAutoSyncEnabled() {
        return autoSyncEnabled;
    }

    public static void setAutoSyncEnabled(boolean enabled) {
        autoSyncEnabled = enabled;
        if (enabled) {
            startAutoSync();
        } else if (syncTimer != null) {
            syncTimer.cancel();
            syncTimer = null;
        }
    }

    public static String getSyncStatus() {
        if (!autoSyncEnabled) return "Disabled";
        if (isInitializing) return "Initializing...";
        if (hasSentInitialCosmetics) return "Synced";
        return "Not synced";
    }
}