package net.viper.status.modules.network;

import com.sun.management.OperatingSystemMXBean;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.scheduler.ScheduledTask;
import net.viper.status.module.Module;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.lang.management.ManagementFactory;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * Liefert erweiterte Proxy- und Systeminformationen f\u00fcr API und Ingame-Debug.
 */
public class NetworkInfoModule implements Module {

    private static final String CONFIG_FILE_NAME = "network-guard.properties";

    private Plugin plugin;
    private long startedAtMillis;

    private boolean enabled = true;
    private boolean commandEnabled = true;
    private boolean includePlayerNames = false;

    private boolean webhookEnabled = false;
    private String webhookUrl = "";
    private String webhookUsername = "StatusAPI";
    private String webhookThumbnailUrl = "";
    private boolean webhookNotifyStartStop = true;
    private String webhookEmbedMode = "detailed";
    private int webhookCheckSeconds = 30;
    private int alertMemoryPercent = 90;
    private int alertPlayerPercent = 95;
    private int alertCooldownSeconds = 300;
    private boolean alertTpsEnabled = true;
    private double alertTpsThreshold = 18.0D;
    private boolean attackNotificationsEnabled = true;
    private String attackApiKey = "";
    private String attackDefaultSource = "Viper-Network";
    private long lastMemoryAlertAt = 0L;
    private long lastPlayerAlertAt = 0L;
    private long lastTpsAlertAt = 0L;
    private volatile double currentProxyTps = 20.0D;

    /** FIX: \u00d6ffentlicher Getter damit ScoreboardModule als TPS-Fallback darauf zugreifen kann */
    public double getProxyTps() { return currentProxyTps; }
    private long lastTpsSampleAtMs = 0L;
    private ScheduledTask alertTask;
    private ScheduledTask tpsSamplerTask;

    @Override
    public String getName() {
        return "NetworkInfoModule";
    }

    @Override
    public void onEnable(Plugin plugin) {
        this.plugin = plugin;
        this.startedAtMillis = System.currentTimeMillis();
        ensureModuleConfigExists();
        loadConfig();

        if (!enabled) {

            return;
        }

        if (commandEnabled) {
            ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new NetInfoCommand());
        }

        tpsSamplerTask = ProxyServer.getInstance().getScheduler().schedule(plugin, this::sampleProxyTps, 1, 1, TimeUnit.SECONDS);

        if (webhookEnabled && !webhookUrl.isEmpty()) {
            if (webhookNotifyStartStop) {
                boolean delivered = sendLifecycleStartNotification();
                if (!delivered) {
                    plugin.getLogger().warning("[NetworkInfoModule] Start-Webhook konnte nicht direkt zugestellt werden. Wiederhole in 10 Sekunden.");
                    ProxyServer.getInstance().getScheduler().schedule(plugin, () -> {
                        boolean retryDelivered = sendLifecycleStartNotification();
                        if (!retryDelivered) {
                            plugin.getLogger().warning("[NetworkInfoModule] Start-Webhook auch beim zweiten Versuch fehlgeschlagen.");
                        }
                    }, 10L, TimeUnit.SECONDS);
                }
            }
            int interval = Math.max(10, webhookCheckSeconds);
            alertTask = ProxyServer.getInstance().getScheduler().schedule(plugin, this::evaluateAndSendAlerts, interval, interval, TimeUnit.SECONDS);
        }


    }

    @Override
    public void onDisable(Plugin plugin) {
        if (alertTask != null) {
            alertTask.cancel();
            alertTask = null;
        }
        if (tpsSamplerTask != null) {
            tpsSamplerTask.cancel();
            tpsSamplerTask = null;
        }

        if (enabled && webhookEnabled && webhookNotifyStartStop && webhookUrl != null && !webhookUrl.isEmpty()) {
            boolean delivered = sendLifecycleStopNotification();
            if (!delivered) {
                plugin.getLogger().warning("[NetworkInfoModule] Stop-Webhook konnte nicht zugestellt werden.");
            }
        }
    }

    private boolean sendLifecycleStartNotification() {
        if (isCompactEmbedMode()) {
            return sendWebhookEmbed(
                    webhookUrl,
                    "✅ NetworkInfo gestartet",
                    "Proxy: **" + ProxyServer.getInstance().getName() + "**\n\u00dcberwachung und Webhook-Alerts sind jetzt aktiv.",
                    0x2ECC71,
                    null,
                    false
            );
        }

        StringBuilder fields = new StringBuilder();
        appendEmbedField(fields, "Proxy", ProxyServer.getInstance().getName(), true);
        appendEmbedField(fields, "Modus", "Detailed", true);
        appendEmbedField(fields, "Check-Intervall", Math.max(10, webhookCheckSeconds) + "s", true);
        return sendWebhookEmbed(
                webhookUrl,
                "✅ NetworkInfo gestartet",
                "\u00dcberwachung und Webhook-Alerts sind jetzt aktiv.",
                0x2ECC71,
                fields.toString(),
                false
        );
    }

    private boolean sendLifecycleStopNotification() {
        if (isCompactEmbedMode()) {
            return sendWebhookEmbed(
                    webhookUrl,
                    "🛑 NetworkInfo gestoppt",
                    "Die NetworkInfo-\u00dcberwachung wurde gestoppt.\nKeine weiteren Auto-Alerts bis zum n\u00e4chsten Start.",
                    0xE74C3C,
                    null,
                    false
            );
        }

        StringBuilder fields = new StringBuilder();
        appendEmbedField(fields, "Proxy", ProxyServer.getInstance().getName(), true);
        appendEmbedField(fields, "Modus", "Detailed", true);
        appendEmbedField(fields, "Status", "Monitoring pausiert", false);
        return sendWebhookEmbed(
                webhookUrl,
                "🛑 NetworkInfo gestoppt",
                "Die NetworkInfo-\u00dcberwachung wurde gestoppt.",
                0xE74C3C,
                fields.toString(),
                false
        );
    }

    public boolean isEnabled() {
        return enabled;
    }

    public boolean isAttackNotificationsEnabled() {
        return enabled && attackNotificationsEnabled;
    }

    public boolean isAttackApiKeyValid(String providedKey) {
        if (attackApiKey == null || attackApiKey.isEmpty()) {
            return true;
        }
        return providedKey != null && attackApiKey.equals(providedKey.trim());
    }

    public boolean sendAttackNotification(String eventType,
                                          Integer connectionsPerSecond,
                                          Integer blockedIps,
                                          Long blockedConnections,
                                          String source) {
        if (!isAttackNotificationsEnabled()) {
            return false;
        }

        String usedSource = (source == null || source.trim().isEmpty()) ? attackDefaultSource : source.trim();
        String type = eventType == null ? "detected" : eventType.trim().toLowerCase(Locale.ROOT);

        String title;
        String shortText;
        int color;
        if ("stopped".equals(type) || "mitigated".equals(type)) {
            title = "✅ Attack Stopped";
            shortText = "Traffic hat sich normalisiert.";
            color = 0x2ECC71;
        } else {
            title = "🚨 Attack Detected";
            shortText = "Ungew\u00f6hnlich hoher Verbindungs-Traffic erkannt.";
            color = 0xE74C3C;
        }

        StringBuilder fields = new StringBuilder();
        appendEmbedField(fields, "Source", usedSource, true);
        appendEmbedField(fields, "Event", type.toUpperCase(Locale.ROOT), true);

        if (connectionsPerSecond != null && connectionsPerSecond >= 0) {
            appendEmbedField(fields, "Connections / Second", String.valueOf(connectionsPerSecond), true);
        }
        if (blockedIps != null && blockedIps >= 0) {
            appendEmbedField(fields, "Blocked IPs", String.valueOf(blockedIps), true);
        }
        if (blockedConnections != null && blockedConnections >= 0L) {
            appendEmbedField(fields, "Blocked Connections", String.valueOf(blockedConnections), true);
        }

        sendWebhookAttackEmbed(webhookUrl, title, shortText, color, fields.toString());
        return true;
    }

    public Map<String, Object> buildSnapshot() {
        Map<String, Object> out = new LinkedHashMap<String, Object>();

        long now = System.currentTimeMillis();
        long uptimeMs = Math.max(0L, now - startedAtMillis);

        Runtime rt = Runtime.getRuntime();
        long maxMemory = rt.maxMemory();
        long totalMemory = rt.totalMemory();
        long freeMemory = rt.freeMemory();
        long usedMemory = totalMemory - freeMemory;

        Map<String, Object> memory = new LinkedHashMap<String, Object>();
        memory.put("used_mb", bytesToMb(usedMemory));
        memory.put("free_mb", bytesToMb(freeMemory));
        memory.put("total_mb", bytesToMb(totalMemory));
        memory.put("max_mb", bytesToMb(maxMemory));
        memory.put("usage_percent", percent(usedMemory, Math.max(1L, maxMemory)));

        int onlinePlayers = ProxyServer.getInstance().getPlayers().size();
        int maxPlayers = ProxyServer.getInstance().getConfig().getPlayerLimit();

        // getPlayerLimit() liefert -1 wenn kein globales Limit gesetzt ist.
        // In diesem Fall den Listener-Wert (angezeigte Max-Spielerzahl im Server-Ping) nutzen.
        if (maxPlayers <= 0) {
            try {
                java.util.Iterator<net.md_5.bungee.api.config.ListenerInfo> listenerIt =
                        ProxyServer.getInstance().getConfig().getListeners().iterator();
                if (listenerIt.hasNext()) {
                    int listenerMax = listenerIt.next().getMaxPlayers();
                    if (listenerMax > 0) {
                        maxPlayers = listenerMax;
                    }
                }
            } catch (Exception ignored) {}
        }

        Map<String, Object> ping = buildPingSummary(ProxyServer.getInstance().getPlayers());

        Map<String, Object> players = new LinkedHashMap<String, Object>();
        players.put("online", onlinePlayers);
        players.put("max", maxPlayers);
        players.put("occupancy_percent", percent(onlinePlayers, Math.max(1, maxPlayers)));
        players.put("bedrock_online", countBedrockPlayers());
        players.put("ping", ping);

        List<Map<String, Object>> backend = buildBackendDistribution();

        Map<String, Object> system = new LinkedHashMap<String, Object>();
        system.put("java_version", System.getProperty("java.version"));
        system.put("java_vendor", System.getProperty("java.vendor"));
        system.put("os_name", System.getProperty("os.name"));
        system.put("os_arch", System.getProperty("os.arch"));
        system.put("available_processors", Runtime.getRuntime().availableProcessors());
        system.put("system_load_percent", getSystemLoadPercent());
        system.put("proxy_tps", roundDouble(currentProxyTps, 2));

        out.put("enabled", true);
        out.put("timestamp_unix", now / 1000L);
        out.put("uptime_seconds", uptimeMs / 1000L);
        out.put("uptime_human", formatDuration(uptimeMs));
        out.put("players", players);
        out.put("backend_servers", backend);
        out.put("memory", memory);
        out.put("system", system);
        out.put("features", buildFeatureSummary());

        if (includePlayerNames) {
            List<Map<String, Object>> playerNames = new ArrayList<Map<String, Object>>();
            for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
                Map<String, Object> entry = new LinkedHashMap<String, Object>();
                entry.put("name", p.getName());
                try { entry.put("uuid", p.getUniqueId().toString()); } catch (Exception ignored) {}
                try {
                    if (p.getServer() != null && p.getServer().getInfo() != null) {
                        entry.put("server", p.getServer().getInfo().getName());
                    }
                } catch (Exception ignored) {}
                playerNames.add(entry);
            }
            out.put("player_names", playerNames);
        }

        return out;
    }

    private Map<String, Object> buildPingSummary(Collection<ProxiedPlayer> players) {
        Map<String, Object> ping = new LinkedHashMap<String, Object>();
        if (players.isEmpty()) {
            ping.put("avg_ms", 0);
            ping.put("min_ms", 0);
            ping.put("max_ms", 0);
            return ping;
        }

        long sum = 0L;
        int min = Integer.MAX_VALUE;
        int max = Integer.MIN_VALUE;

        for (ProxiedPlayer p : players) {
            int ms = Math.max(0, p.getPing());
            sum += ms;
            if (ms < min) min = ms;
            if (ms > max) max = ms;
        }

        ping.put("avg_ms", Math.round((double) sum / (double) players.size()));
        ping.put("min_ms", min == Integer.MAX_VALUE ? 0 : min);
        ping.put("max_ms", max == Integer.MIN_VALUE ? 0 : max);
        return ping;
    }

    private List<Map<String, Object>> buildBackendDistribution() {
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        for (Map.Entry<String, ServerInfo> entry : ProxyServer.getInstance().getServers().entrySet()) {
            ServerInfo info = entry.getValue();

            Map<String, Object> row = new LinkedHashMap<String, Object>();
            row.put("name", entry.getKey());
            row.put("online_players", info.getPlayers().size());
            row.put("address", String.valueOf(info.getAddress()));
            list.add(row);
        }
        return list;
    }

    private Map<String, Object> buildFeatureSummary() {
        Map<String, Object> features = new LinkedHashMap<String, Object>();
        features.put("luckperms", ProxyServer.getInstance().getPluginManager().getPlugin("LuckPerms") != null);
        features.put("floodgate", isFloodgateAvailable());
        return features;
    }

    private boolean isFloodgateAvailable() {
        try {
            Class.forName("org.geysermc.floodgate.api.FloodgateApi");
            return true;
        } catch (Throwable ignored) {
            return false;
        }
    }

    private int countBedrockPlayers() {
        try {
            Class<?> apiClass = Class.forName("org.geysermc.floodgate.api.FloodgateApi");
            Object api = apiClass.getMethod("getInstance").invoke(null);
            int count = 0;
            for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
                Boolean isBedrock = (Boolean) api.getClass().getMethod("isBedrockPlayer", UUID.class).invoke(api, p.getUniqueId());
                if (Boolean.TRUE.equals(isBedrock)) {
                    count++;
                }
            }
            return count;
        } catch (Throwable ignored) {
            return 0;
        }
    }

    private Integer getSystemLoadPercent() {
        try {
            java.lang.management.OperatingSystemMXBean bean = ManagementFactory.getOperatingSystemMXBean();
            if (bean instanceof OperatingSystemMXBean) {
                double load = ((OperatingSystemMXBean) bean).getSystemCpuLoad();
                if (load >= 0D) {
                    return (int) Math.round(load * 100D);
                }
            }
        } catch (Throwable ignored) {
        }
        return null;
    }

    private int bytesToMb(long bytes) {
        return (int) (bytes / (1024L * 1024L));
    }

    private int percent(long value, long max) {
        if (max <= 0L) return 0;
        return (int) Math.min(100L, Math.round((value * 100.0D) / max));
    }

    private String formatDuration(long ms) {
        long totalSeconds = ms / 1000L;
        long days = totalSeconds / 86400L;
        long hours = (totalSeconds % 86400L) / 3600L;
        long minutes = (totalSeconds % 3600L) / 60L;
        long seconds = totalSeconds % 60L;
        return String.format(Locale.ROOT, "%dd %02dh %02dm %02ds", days, hours, minutes, seconds);
    }

    private void loadConfig() {
        File file = new File(plugin.getDataFolder(), CONFIG_FILE_NAME);
        if (!file.exists()) {
            enabled = true;
            commandEnabled = true;
            includePlayerNames = false;
            webhookEnabled = false;
            webhookUrl = "";
            webhookUsername = "StatusAPI";
            webhookThumbnailUrl = "";
            webhookNotifyStartStop = true;
            webhookEmbedMode = "detailed";
            webhookCheckSeconds = 30;
            alertMemoryPercent = 90;
            alertPlayerPercent = 95;
            alertCooldownSeconds = 300;
            alertTpsEnabled = true;
            alertTpsThreshold = 18.0D;
            attackNotificationsEnabled = true;
            attackApiKey = "";
            attackDefaultSource = "BetterBungee";
            return;
        }

        Properties props = new Properties();
        try (FileInputStream in = new FileInputStream(file)) {
            props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
            enabled = Boolean.parseBoolean(props.getProperty("networkinfo.enabled", "true"));
            commandEnabled = Boolean.parseBoolean(props.getProperty("networkinfo.command.enabled", "true"));
            includePlayerNames = Boolean.parseBoolean(props.getProperty("networkinfo.include_player_names", "false"));

            webhookEnabled = Boolean.parseBoolean(props.getProperty("networkinfo.webhook.enabled", "false"));
            webhookUrl = props.getProperty("networkinfo.webhook.url", "").trim();
            webhookUsername = props.getProperty("networkinfo.webhook.username", "StatusAPI").trim();
            webhookThumbnailUrl = props.getProperty("networkinfo.webhook.thumbnail_url", "").trim();
            webhookNotifyStartStop = Boolean.parseBoolean(props.getProperty("networkinfo.webhook.notify_start_stop", "true"));
            webhookEmbedMode = props.getProperty("networkinfo.webhook.embed_mode", "detailed").trim();
            webhookCheckSeconds = parseInt(props.getProperty("networkinfo.webhook.check_seconds", "30"), 30);
            alertMemoryPercent = parseInt(props.getProperty("networkinfo.alert.memory_percent", "90"), 90);
            alertPlayerPercent = parseInt(props.getProperty("networkinfo.alert.player_percent", "95"), 95);
            alertCooldownSeconds = parseInt(props.getProperty("networkinfo.alert.cooldown_seconds", "300"), 300);
            alertTpsEnabled = Boolean.parseBoolean(props.getProperty("networkinfo.alert.tps_enabled", "true"));
            alertTpsThreshold = parseDouble(props.getProperty("networkinfo.alert.tps_threshold", "18.0"), 18.0D);
            attackNotificationsEnabled = Boolean.parseBoolean(props.getProperty("networkinfo.attack.enabled", "true"));
            attackApiKey = props.getProperty("networkinfo.attack.api_key", "").trim();
            attackDefaultSource = props.getProperty("networkinfo.attack.source", "BetterBungee").trim();
        } catch (Exception e) {
            plugin.getLogger().warning("[NetworkInfoModule] Fehler beim Laden von " + CONFIG_FILE_NAME + ": " + e.getMessage());
            enabled = true;
            commandEnabled = true;
            includePlayerNames = false;
            webhookEnabled = false;
            webhookUrl = "";
            webhookUsername = "StatusAPI";
            webhookThumbnailUrl = "";
            webhookNotifyStartStop = true;
            webhookEmbedMode = "detailed";
            webhookCheckSeconds = 30;
            alertMemoryPercent = 90;
            alertPlayerPercent = 95;
            alertCooldownSeconds = 300;
            alertTpsEnabled = true;
            alertTpsThreshold = 18.0D;
            attackNotificationsEnabled = true;
            attackApiKey = "";
            attackDefaultSource = "BetterBungee";
        }
    }

    private void ensureModuleConfigExists() {
        File target = new File(plugin.getDataFolder(), CONFIG_FILE_NAME);
        if (target.exists()) {
            return;
        }

        if (!plugin.getDataFolder().exists()) {
            plugin.getDataFolder().mkdirs();
        }

        try (InputStream in = plugin.getResourceAsStream(CONFIG_FILE_NAME);
             OutputStream out = new FileOutputStream(target)) {
            if (in == null) {
                plugin.getLogger().warning("[NetworkInfoModule] Standarddatei " + CONFIG_FILE_NAME + " nicht im JAR gefunden.");
                return;
            }
            byte[] buffer = new byte[4096];
            int read;
            while ((read = in.read(buffer)) != -1) {
                out.write(buffer, 0, read);
            }

        } catch (Exception e) {
            plugin.getLogger().warning("[NetworkInfoModule] Konnte " + CONFIG_FILE_NAME + " nicht erstellen: " + e.getMessage());
        }
    }

    private void evaluateAndSendAlerts() {
        if (!enabled || !webhookEnabled || webhookUrl == null || webhookUrl.isEmpty()) {
            return;
        }

        long now = System.currentTimeMillis();
        Map<String, Object> snapshot = buildSnapshot();

        @SuppressWarnings("unchecked")
        Map<String, Object> memory = (Map<String, Object>) snapshot.get("memory");
        @SuppressWarnings("unchecked")
        Map<String, Object> players = (Map<String, Object>) snapshot.get("players");

        int memoryPercent = toInt(memory.get("usage_percent"));
        int playerPercent = toInt(players.get("occupancy_percent"));
        double proxyTps = currentProxyTps;

        if (memoryPercent >= Math.max(1, alertMemoryPercent) && canSend(lastMemoryAlertAt, now)) {
            lastMemoryAlertAt = now;
            if (isCompactEmbedMode()) {
                sendWebhookEmbed(
                        webhookUrl,
                        "⚠️ Hohe RAM-Auslastung",
                        "Aktuell: **" + memoryPercent + "%**\nVerbrauch: **" + memory.get("used_mb") + " MB / " + memory.get("max_mb") + " MB**\nSchwelle: **" + alertMemoryPercent + "%**",
                        0xF39C12
                );
            } else {
                StringBuilder fields = new StringBuilder();
                appendEmbedField(fields, "RAM-Nutzung", memoryPercent + "%", true);
                appendEmbedField(fields, "Schwelle", alertMemoryPercent + "%", true);
                appendEmbedField(fields, "Verbrauch", memory.get("used_mb") + " MB / " + memory.get("max_mb") + " MB", false);
                sendWebhookEmbed(
                        webhookUrl,
                        "⚠️ Hohe RAM-Auslastung",
                        "Ein Schwellwert wurde \u00fcberschritten.",
                        0xF39C12,
                        fields.toString()
                );
            }
        }

        if (playerPercent >= Math.max(1, alertPlayerPercent) && canSend(lastPlayerAlertAt, now)) {
            lastPlayerAlertAt = now;
            if (isCompactEmbedMode()) {
                sendWebhookEmbed(
                        webhookUrl,
                        "📈 Hohe Spieler-Auslastung",
                        "Auslastung: **" + playerPercent + "%**\nSpieler: **" + players.get("online") + "/" + players.get("max") + "**\nSchwelle: **" + alertPlayerPercent + "%**",
                        0x3498DB
                );
            } else {
                StringBuilder fields = new StringBuilder();
                appendEmbedField(fields, "Auslastung", playerPercent + "%", true);
                appendEmbedField(fields, "Schwelle", alertPlayerPercent + "%", true);
                appendEmbedField(fields, "Spieler", players.get("online") + "/" + players.get("max"), true);
                sendWebhookEmbed(
                        webhookUrl,
                        "📈 Hohe Spieler-Auslastung",
                        "Die Spielerlast ist aktuell sehr hoch.",
                        0x3498DB,
                        fields.toString()
                );
            }
        }

        if (alertTpsEnabled && proxyTps > 0D && proxyTps < Math.max(1D, alertTpsThreshold) && canSend(lastTpsAlertAt, now)) {
            lastTpsAlertAt = now;
            String tpsText = String.format(Locale.ROOT, "%.2f", proxyTps);
            String thresholdText = String.format(Locale.ROOT, "%.2f", Math.max(1D, alertTpsThreshold));

            if (isCompactEmbedMode()) {
                sendWebhookEmbed(
                        webhookUrl,
                        "🟥 Niedrige Proxy-TPS",
                        "Aktuell: **" + tpsText + " TPS**\nSchwelle: **" + thresholdText + " TPS**",
                        0xE74C3C
                );
            } else {
                StringBuilder fields = new StringBuilder();
                appendEmbedField(fields, "Proxy TPS", tpsText, true);
                appendEmbedField(fields, "Schwelle", thresholdText, true);
                appendEmbedField(fields, "Check-Intervall", Math.max(10, webhookCheckSeconds) + "s", true);
                sendWebhookEmbed(
                        webhookUrl,
                        "🟥 Niedrige Proxy-TPS",
                        "Die gemessene Proxy-TPS liegt unter der konfigurierten Schwelle.",
                        0xE74C3C,
                        fields.toString()
                );
            }
        }
    }

    private void sampleProxyTps() {
        long now = System.currentTimeMillis();
        if (lastTpsSampleAtMs <= 0L) {
            lastTpsSampleAtMs = now;
            currentProxyTps = 20.0D;
            return;
        }

        long deltaMs = now - lastTpsSampleAtMs;
        lastTpsSampleAtMs = now;
        if (deltaMs <= 0L) {
            return;
        }

        // 1s Scheduler-Tick sollte etwa 20 TPS entsprechen. Abweichung zeigt Main-Thread-Lag.
        double instantTps = (1000.0D / (double) deltaMs) * 20.0D;
        instantTps = Math.max(0.1D, Math.min(20.0D, instantTps));
        currentProxyTps = (currentProxyTps * 0.7D) + (instantTps * 0.3D);
    }

    private boolean isCompactEmbedMode() {
        return "compact".equalsIgnoreCase(webhookEmbedMode == null ? "" : webhookEmbedMode.trim());
    }

    private boolean canSend(long lastSentAt, long now) {
        long cooldownMs = Math.max(10, alertCooldownSeconds) * 1000L;
        return (now - lastSentAt) >= cooldownMs;
    }

    private int parseInt(String s, int fallback) {
        try {
            return Integer.parseInt(s == null ? "" : s.trim());
        } catch (Exception ignored) {
            return fallback;
        }
    }

    private double parseDouble(String s, double fallback) {
        try {
            return Double.parseDouble(s == null ? "" : s.trim());
        } catch (Exception ignored) {
            return fallback;
        }
    }

    private int toInt(Object o) {
        if (o instanceof Number) {
            return ((Number) o).intValue();
        }
        try {
            return Integer.parseInt(String.valueOf(o));
        } catch (Exception ignored) {
            return 0;
        }
    }

    private double roundDouble(double value, int digits) {
        double factor = Math.pow(10D, Math.max(0, digits));
        return Math.round(value * factor) / factor;
    }

    private boolean sendWebhookEmbed(String targetWebhookUrl, String title, String description, int color) {
        return sendWebhookEmbed(targetWebhookUrl, title, description, color, null, true);
    }

    private boolean sendWebhookEmbed(String targetWebhookUrl, String title, String description, int color, String fieldsJson) {
        return sendWebhookEmbed(targetWebhookUrl, title, description, color, fieldsJson, true);
    }

    private boolean sendWebhookEmbed(String targetWebhookUrl, String title, String description, int color, String fieldsJson, boolean async) {
        if (targetWebhookUrl == null || targetWebhookUrl.isEmpty()) {
            return false;
        }

        StringBuilder embed = new StringBuilder();
        embed.append("{\"username\":\"").append(escapeJson(webhookUsername)).append("\",")
                .append("\"embeds\":[{\"title\":\"").append(escapeJson(title)).append("\",")
                .append("\"description\":\"").append(escapeJson(description)).append("\",")
                .append("\"color\":").append(color).append(",");

        if (fieldsJson != null && !fieldsJson.trim().isEmpty()) {
            embed.append("\"fields\":[").append(fieldsJson).append("],");
        }

        embed.append("\"footer\":{\"text\":\"StatusPulse • NetworkInfo\"},")
                .append("\"timestamp\":\"").append(Instant.now().toString()).append("\"");

        if (webhookThumbnailUrl != null && !webhookThumbnailUrl.isEmpty()) {
            embed.append(",\"thumbnail\":{\"url\":\"").append(escapeJson(webhookThumbnailUrl)).append("\"}");
        }

        embed.append("}]}");
        return postWebhookPayload(targetWebhookUrl, embed.toString(), async);
    }

    private void sendWebhookAttackEmbed(String targetWebhookUrl,
                                        String title,
                                        String description,
                                        int color,
                                        String fieldsJson) {
        if (targetWebhookUrl == null || targetWebhookUrl.isEmpty()) {
            return;
        }

        StringBuilder embed = new StringBuilder();
        embed.append("{\"username\":\"").append(escapeJson(webhookUsername)).append("\",")
                .append("\"embeds\":[{\"title\":\"").append(escapeJson(title)).append("\",")
                .append("\"description\":\"").append(escapeJson(description)).append("\",")
                .append("\"color\":").append(color).append(",")
                .append("\"fields\":[").append(fieldsJson).append("],")
                .append("\"footer\":{\"text\":\"StatusPulse • Network Guard\"},")
                .append("\"timestamp\":\"").append(Instant.now().toString()).append("\"");

        if (webhookThumbnailUrl != null && !webhookThumbnailUrl.isEmpty()) {
            embed.append(",\"thumbnail\":{\"url\":\"").append(escapeJson(webhookThumbnailUrl)).append("\"}");
        }

        embed.append("}]}");
        postWebhookPayload(targetWebhookUrl, embed.toString(), true);
    }

    private void appendEmbedField(StringBuilder out, String name, String value, boolean inline) {
        if (value == null || value.trim().isEmpty()) {
            return;
        }
        if (out.length() > 0) {
            out.append(",");
        }
        out.append("{\"name\":\"").append(escapeJson(name)).append("\",")
                .append("\"value\":\"").append(escapeJson(value.trim())).append("\",")
                .append("\"inline\":").append(inline ? "true" : "false")
                .append("}");
    }

    private boolean postWebhookPayload(String targetWebhookUrl, String payload, boolean async) {
        if (async) {
            ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> executeWebhookPost(targetWebhookUrl, payload));
            return true;
        }
        return executeWebhookPost(targetWebhookUrl, payload);
    }

    private boolean executeWebhookPost(String targetWebhookUrl, String payload) {
        HttpURLConnection conn = null;
        try {
            byte[] bytes = payload.getBytes(StandardCharsets.UTF_8);

            conn = (HttpURLConnection) new URL(targetWebhookUrl).openConnection();
            conn.setRequestMethod("POST");
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(8000);
            conn.setDoOutput(true);
            conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
            conn.setRequestProperty("Content-Length", String.valueOf(bytes.length));

            try (OutputStream os = conn.getOutputStream()) {
                os.write(bytes);
            }

            int code = conn.getResponseCode();
            if (code >= 200 && code < 300) {
                return true;
            }

            plugin.getLogger().warning("[NetworkInfoModule] Discord Webhook HTTP " + code + ": " + readErrorBody(conn));
            return false;
        } catch (Exception e) {
            plugin.getLogger().warning("[NetworkInfoModule] Discord Webhook Fehler: " + e.getMessage());
            return false;
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
    }

    private String readErrorBody(HttpURLConnection conn) {
        if (conn == null) {
            return "";
        }
        try (InputStream errorStream = conn.getErrorStream()) {
            if (errorStream == null) {
                return "";
            }
            byte[] bytes = new byte[1024];
            int read = errorStream.read(bytes);
            if (read <= 0) {
                return "";
            }
            return new String(bytes, 0, read, StandardCharsets.UTF_8).trim();
        } catch (Exception ignored) {
            return "";
        }
    }

    private String escapeJson(String s) {
        if (s == null) return "";
        return s.replace("\\", "\\\\")
                .replace("\"", "\\\"")
                .replace("\n", "\\n")
                .replace("\r", "\\r");
    }

    private class NetInfoCommand extends Command {

        NetInfoCommand() {
            super("netinfo", "statusapi.netinfo");
        }

        @Override
        public void execute(CommandSender sender, String[] args) {
            if (!enabled) {
                sender.sendMessage(ChatColor.RED + "NetworkInfoModule ist deaktiviert.");
                return;
            }

            Map<String, Object> snapshot = buildSnapshot();
            @SuppressWarnings("unchecked")
            Map<String, Object> players = (Map<String, Object>) snapshot.get("players");
            @SuppressWarnings("unchecked")
            Map<String, Object> memory = (Map<String, Object>) snapshot.get("memory");
            @SuppressWarnings("unchecked")
            Map<String, Object> system = (Map<String, Object>) snapshot.get("system");
            @SuppressWarnings("unchecked")
            Map<String, Object> ping = (Map<String, Object>) players.get("ping");

            sender.sendMessage(ChatColor.GOLD + "----- StatusAPI NetworkInfo -----");
            sender.sendMessage(ChatColor.YELLOW + "Uptime: " + ChatColor.WHITE + snapshot.get("uptime_human"));
            sender.sendMessage(ChatColor.YELLOW + "Spieler: " + ChatColor.WHITE + players.get("online") + "/" + players.get("max") + ChatColor.GRAY + " (Bedrock: " + players.get("bedrock_online") + ")");
            sender.sendMessage(ChatColor.YELLOW + "Ping: " + ChatColor.WHITE + "avg " + ping.get("avg_ms") + "ms, min " + ping.get("min_ms") + "ms, max " + ping.get("max_ms") + "ms");
            sender.sendMessage(ChatColor.YELLOW + "RAM: " + ChatColor.WHITE + memory.get("used_mb") + "MB / " + memory.get("max_mb") + "MB" + ChatColor.GRAY + " (" + memory.get("usage_percent") + "%)");
            sender.sendMessage(ChatColor.YELLOW + "Proxy TPS: " + ChatColor.WHITE + system.get("proxy_tps") + ChatColor.GRAY + " (Alert < " + String.format(Locale.ROOT, "%.2f", alertTpsThreshold) + ")");
            sender.sendMessage(ChatColor.YELLOW + "Backends: " + ChatColor.WHITE + ((List<?>) snapshot.get("backend_servers")).size());
        }
    }
}