package de.winniepat.citrus.cosmetics.capes.sync;

import com.google.gson.*;
import de.winniepat.citrus.Client;
import de.winniepat.citrus.cosmetics.capes.CapeHandler;
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.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.*;

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

    public static final Map<UUID, String> SYNCED_CAPE_CACHE = new ConcurrentHashMap<>();
    private static final Map<UUID, Long> LAST_SYNC_TIME = new ConcurrentHashMap<>();

    private static final Map<UUID, String> PENDING_UPDATES = new ConcurrentHashMap<>();

    private static final Set<UUID> PENDING_FETCHES = ConcurrentHashMap.newKeySet();

    private static boolean hasSentInitialCape = false;
    private static boolean isInitializing = false;

    private static boolean serverReachable = true;
    private static long lastServerCheck = 0;
    private static final long SERVER_CHECK_INTERVAL = 30000;

    private static boolean autoSyncEnabled = true;
    private static long lastAutoSyncTime = 0;
    private static final long AUTO_SYNC_INTERVAL = 5000;

    private static boolean serverHealthy = false;

    private static Timer syncTimer;

    public static void init() {
        Client.debugLog("Initializing cape sync...");
        isInitializing = true;

        startAutoSync();

        checkServerHealth().thenAccept(healthy -> {
            serverReachable = healthy;
            if (healthy) {
                Client.debugLog("Cape sync server is online");
                scheduleInitialSync();
            } else {
                Client.logger.error("Cape sync server is offline");
                isInitializing = false;
            }
        });
    }

    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 cape sync for " + playerName + " (" + uuid + ")");

        fetchCapeFromServer(uuid, playerName, true).thenAccept(serverCape -> {
            if (serverCape != null) {
                Client.debugLog("Server has cape: " + serverCape);
                applyServerCapeToLocal(uuid, serverCape);
            } else {
                String localCape = CapeHandler.getCurrentCapeForLocalPlayer();
                if (localCape != null && !"none".equals(localCape)) {
                    Client.debugLog("Sending local cape to server: " + localCape);
                    sendCapeToServer(uuid, localCape, playerName, true).thenAccept(success -> {
                        if (success) {
                            Client.debugLog("Initial cape sync completed");
                        }
                    });
                } else {
                    Client.debugLog("No local cape to sync");
                }
            }
            hasSentInitialCape = true;
            isInitializing = false;
        });
    }

    public static String getSyncedCape(UUID uuid, String playerName) {

        if (!ClientRegistrationManager.isPlayerOnlineWithCitrus(uuid)) {
            return null;
        }

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

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

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

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

        return null;
    }

    private static CompletableFuture<String> fetchCapeFromServer(UUID uuid, String playerName, boolean isInitial) {
        PENDING_FETCHES.add(uuid);

        return CompletableFuture.supplyAsync(() -> {
            HttpURLConnection conn = null;
            try {
                String urlString = API_BASE_URL + "/cape/" + uuid.toString();
                Client.debugLog("Fetching from: " + urlString);

                URL url = new URI(urlString).toURL();
                conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(10000);
                conn.setReadTimeout(10000);

                conn.setRequestProperty("User-Agent", "Minecraft/Citrus");
                conn.setRequestProperty("Accept", "application/json");
                conn.setRequestProperty("Accept-Charset", "UTF-8");
                conn.setRequestProperty("x-daily-key", SecurityUtils.getDailyKey());

                conn.setRequestProperty("Connection", "close");

                Client.debugLog("Opening connection with headers: " + conn.getRequestProperties());

                int responseCode = conn.getResponseCode();
                Client.debugLog("Response code: " + responseCode);

                if (responseCode == 200) {
                    serverReachable = true;
                    try (InputStream is = conn.getInputStream()) {
                        byte[] response = is.readAllBytes();
                        String responseBody = new String(response, StandardCharsets.UTF_8);
                        Client.debugLog("Response: " + responseBody);

                        JsonObject json = GSON.fromJson(responseBody, JsonObject.class);

                        if (json.has("cape")) {
                            String cape = json.get("cape").getAsString();
                            Client.debugLog("Successfully got cape: " + cape);

                            if (!"none".equals(cape)) {
                                SYNCED_CAPE_CACHE.put(uuid, cape);
                                LAST_SYNC_TIME.put(uuid, System.currentTimeMillis());
                                return cape;
                            }
                        }
                    }
                } else {
                    try (InputStream es = conn.getErrorStream()) {
                        if (es != null) {
                            String errorBody = new String(es.readAllBytes(), StandardCharsets.UTF_8);
                            Client.logger.error("Error response {}", errorBody);
                        }
                    }
                    serverReachable = false;
                    Client.logger.error("Server returned error code {}", responseCode);
                }

            } catch (Exception e) {
                serverReachable = false;
                Client.logger.error("Exception type {}: {}", e.getClass().getName(), e);

                switch (e) {
                    case ConnectException connectException ->
                            Client.logger.error("Connection refused. Is the server running?");
                    case SocketTimeoutException socketTimeoutException ->
                            Client.logger.error("Connection timeout. Check firewall/network.");
                    case UnknownHostException unknownHostException ->
                            Client.logger.error("Unknown host. Check DNS/network.");
                    default -> Client.logger.error("Error Conneccting to Cape Server {}", String.valueOf(e));
                }

            } finally {
                if (conn != null) {
                    conn.disconnect();
                }
                PENDING_FETCHES.remove(uuid);
            }
            return null;
        }, Util.getMainWorkerExecutor());
    }

    private static CompletableFuture<Boolean> sendCapeToServer(UUID uuid, String cape, String playerName, boolean isInitial) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                URL url = new URI(API_BASE_URL + "/cape").toURL();
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("POST");
                conn.setRequestProperty("Content-Type", "application/json");
                conn.setRequestProperty("User-Agent", "Citrus/1.0");
                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("cape", cape == null ? "none" : cape);
                json.addProperty("playerName", playerName);

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

                int responseCode = conn.getResponseCode();

                if (responseCode == 200) {
                    serverReachable = true;
                    if (isInitial) {
                        hasSentInitialCape = true;
                    }

                    SYNCED_CAPE_CACHE.put(uuid, cape);
                    LAST_SYNC_TIME.put(uuid, System.currentTimeMillis());

                    PENDING_UPDATES.remove(uuid);

                    if (!isInitial) {
                        Client.debugLog("Successfully synced cape to server: " + cape);
                    }
                    return true;
                } else {
                    serverReachable = false;
                    if (!isInitial) {
                        Client.logger.error("Failed to sync cape to server. HTTP {}", responseCode);
                    }
                    PENDING_UPDATES.put(uuid, cape);
                    return false;
                }
            } catch (Exception e) {
                serverReachable = false;
                if (!isInitial) {
                    Client.logger.error("Failed to send cape to server", e);
                }
                PENDING_UPDATES.put(uuid, cape);
                return false;
            }
        }, Util.getMainWorkerExecutor());
    }

    public static void sendCapeToServer(UUID uuid, String cape, String playerName) {
        sendCapeToServer(uuid, cape, playerName, false);
    }

    private static void applyServerCapeToLocal(UUID uuid, String cape) {
        MinecraftClient.getInstance().execute(() -> {
            CapeHandler.setLocalPlayerCape(cape, true);
            Client.debugLog("Applied server cape: " + cape);
        });
    }

    public static CompletableFuture<Boolean> checkServerHealth() {
        lastServerCheck = System.currentTimeMillis();

        return CompletableFuture.supplyAsync(() -> {
            try {
                URL url = new URI(API_BASE_URL + "/health").toURL();
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setRequestProperty("x-daily-key", SecurityUtils.getDailyKey());
                conn.setConnectTimeout(3000);

                boolean healthy = conn.getResponseCode() == 200;
                serverHealthy = conn.getResponseCode() == 200;
                conn.disconnect();
                return healthy;
            } catch (Exception e) {
                serverHealthy = false;
                return false;
            }
        }, Util.getMainWorkerExecutor());
    }

    private static boolean shouldTryServer() {
        if (!serverReachable) {
            long timeSinceLastCheck = System.currentTimeMillis() - lastServerCheck;
            return timeSinceLastCheck > SERVER_CHECK_INTERVAL;
        }
        return true;
    }

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

    public static void batchFetchCapes(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 + "/capes/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);

                List<String> uuidStrings = new ArrayList<>();
                for (UUID uuid : uuids) {
                    uuidStrings.add(uuid.toString());
                }

                JsonObject json = new JsonObject();
                json.add("uuids", GSON.toJsonTree(uuidStrings));

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

                if (conn.getResponseCode() == 200) {
                    serverReachable = true;
                    try (InputStream is = conn.getInputStream()) {
                        byte[] response = is.readAllBytes();
                        JsonObject result = GSON.fromJson(new String(response), JsonObject.class);

                        for (String uuidStr : result.keySet()) {
                            try {
                                UUID uuid = UUID.fromString(uuidStr);
                                JsonObject capeData = result.getAsJsonObject(uuidStr);
                                String cape = capeData.get("cape").getAsString();

                                if (!"none".equals(cape)) {
                                    CapeHandler.cachePlayerCape(uuid, cape);
                                }
                            } catch (Exception e) {
                                Client.logger.error("Invalid UUID in response: {}", uuidStr, e);
                            }
                        }

                        Client.debugLog("Fetched capes for " + result.size() + " Citrus users");
                    }
                }

                conn.disconnect();
            } catch (Exception e) {
                Client.logger.error("Batch fetch failed", e);
            }
        }, Util.getMainWorkerExecutor());
    }

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

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

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

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

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

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

    public static Map<UUID, String> getAllCapeAssignments() {
        return new HashMap<>(SYNCED_CAPE_CACHE);
    }

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

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

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

        syncTimer = new Timer("CapeAutoSync", true);
        syncTimer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                if (autoSyncEnabled && System.currentTimeMillis() - lastAutoSyncTime >= AUTO_SYNC_INTERVAL) {
                    performAutoSync();
                }
            }
        }, 2000, AUTO_SYNC_INTERVAL);
    }

    private static void performAutoSync() {
        MinecraftClient client = MinecraftClient.getInstance();
        if (client.player == null || client.world == null) {
            return;
        }

        lastAutoSyncTime = System.currentTimeMillis();

        syncLocalPlayerCape();

        syncNearbyPlayers();

        retryPendingUpdates();
    }

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

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

        if (currentCape != null && !"none".equals(currentCape)) {
            Long lastSync = LAST_SYNC_TIME.get(uuid);
            if (lastSync == null || System.currentTimeMillis() - lastSync > 30000) {
                sendCapeToServer(uuid, currentCape, playerName, false);
            }
        }
    }

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

        List<UUID> nearbyCitrusUsers = new ArrayList<>();

        for (net.minecraft.entity.player.PlayerEntity player : client.world.getPlayers()) {
            if (player == client.player) {
                continue;
            }

            if (ClientRegistrationManager.isPlayerOnlineWithCitrus(player.getUuid())) {
                nearbyCitrusUsers.add(player.getUuid());
            }
        }

        if (!nearbyCitrusUsers.isEmpty()) {
            batchFetchCapes(nearbyCitrusUsers);
        }
    }

    public static void setAutoSyncEnabled(boolean enabled) {
        autoSyncEnabled = enabled;
        if (enabled) {
            startAutoSync();
            Client.debugLog("Auto-sync enabled (every 5 seconds)");
        } else {
            if (syncTimer != null) {
                syncTimer.cancel();
                syncTimer = null;
            }
            Client.debugLog("Auto-sync disabled");
        }
    }

    public static boolean isAutoSyncEnabled() {
        return autoSyncEnabled;
    }

    public static void forceSyncNow() {
        lastAutoSyncTime = System.currentTimeMillis() - AUTO_SYNC_INTERVAL;
        performAutoSync();
    }

    public static String getAutoSyncStatus() {
        if (!autoSyncEnabled) return "disabled";

        long timeSinceLastSync = System.currentTimeMillis() - lastAutoSyncTime;
        long timeUntilNextSync = Math.max(0, AUTO_SYNC_INTERVAL - timeSinceLastSync);

        if (timeSinceLastSync < 1000) {
            return "just synced";
        } else {
            return String.format("next sync in %.1fs", timeUntilNextSync / 1000.0);
        }
    }

    public static boolean isIsHealthy() {
        return serverHealthy;
    }
}