package net.viper.statusapibridge;

import net.milkbowl.vault.economy.Economy;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityRegainHealthEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;

import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class StatusAPIBridge extends JavaPlugin implements Listener {

    private Economy economy;
    private String  statusApiUrl;
    private int     pushDelayTicks;
    private int     liveSyncIntervalTicks;
    private int     scoreboardSyncIntervalTicks;

    private final Map<UUID, Double>  lastPushedBalance = new ConcurrentHashMap<>();
    private final Map<UUID, Double>  lastPushedHealth  = new ConcurrentHashMap<>();
    private final Map<UUID, String>  lastPushedCompass  = new ConcurrentHashMap<>();
    private final Map<UUID, String>  lastPushedWorld    = new ConcurrentHashMap<>();
    private final Map<UUID, String>  lastPushedData     = new ConcurrentHashMap<>();
    private final Map<UUID, String>  lastPushedStats    = new ConcurrentHashMap<>();
    private String lastPushedTicketGlobal = "";

    // ── PlaceholderAPI ────────────────────────────────────────────────────────
    private final Set<String>        papiTokens      = new java.util.LinkedHashSet<>();
    private final Map<UUID, String>  lastPapiValues  = new ConcurrentHashMap<>();
    private boolean papiEnabled = false;

    // ── Nametag ───────────────────────────────────────────────────────────────
    /** Scoreboard für Nametag-Teams (einmalig pro Server erstellt) */
    private org.bukkit.scoreboard.Scoreboard nametagBoard = null;
    /** Zuletzt gesetzter Prefix pro Spieler (Change-Detection) */
    private final Map<UUID, String> lastNametagPrefix = new ConcurrentHashMap<>();
    /** Feature aktivierbar via config: nametag-enabled */
    private boolean nametagEnabled = true;

    // ── Versions-Detection ────────────────────────────────────────────────────
    // true  = 1.21.x-Modus (Spigot/Paper)
    // false = 26.1.x-Modus (neuere Server-Version, kein NMS-Fallback)
    private boolean isLegacyMode = true;

    private final ExecutorService httpExecutor = Executors.newSingleThreadExecutor(r -> {
        Thread t = new Thread(r, "StatusAPIBridge-HTTP");
        t.setDaemon(true);
        return t;
    });

    @Override
    public void onEnable() {
        saveDefaultConfig();
        detectMinecraftVersion();
        nametagEnabled = getConfig().getBoolean("nametag-enabled", true);
        if (nametagEnabled) {
            // Eigenes Scoreboard für Nametag-Teams erstellen
            nametagBoard = Bukkit.getScoreboardManager().getNewScoreboard();
            getLogger().info("Nametag-Prefix aktiviert (LuckPerms).");
        }
        statusApiUrl                = getConfig().getString("statusapi-url", "http://127.0.0.1:9191").trim();
        pushDelayTicks              = getConfig().getInt("push-delay-ticks", 40);
        liveSyncIntervalTicks       = Math.max(20, getConfig().getInt("live-sync-interval-ticks", 20));
        scoreboardSyncIntervalTicks = Math.max(20, getConfig().getInt("scoreboard-sync-interval-ticks", 20));

        if (!setupEconomy()) {
            getLogger().warning("Vault/Economy nicht gefunden – Economy-Push deaktiviert.");
        } else {
            getLogger().info("Vault Economy gefunden: " + economy.getName());
        }

        Bukkit.getPluginManager().registerEvents(this, this);
        Bukkit.getScheduler().runTaskTimer(this, this::pushChangedBalancesForOnlinePlayers,
                liveSyncIntervalTicks, liveSyncIntervalTicks);
        Bukkit.getScheduler().runTaskTimer(this, this::pushScoreboardData,
                scoreboardSyncIntervalTicks, scoreboardSyncIntervalTicks);

        // TicketSystem-Daten alle 5 Sekunden pushen (100 Ticks)
        Bukkit.getScheduler().runTaskTimerAsynchronously(this, this::pushTicketData, 100L, 100L);

        // PlaceholderAPI-Integration
        papiEnabled = getServer().getPluginManager().getPlugin("PlaceholderAPI") != null;
        if (papiEnabled) {
            // Tokens alle 30s von StatusAPI holen, nur bei Änderung loggen
            Bukkit.getScheduler().runTaskTimerAsynchronously(this, () -> {
                Set<String> before = new java.util.LinkedHashSet<>(papiTokens);
                boolean fetched = fetchPapiTokensFromStatusAPI();
                if (fetched && !papiTokens.equals(before)) {
                    if (papiTokens.isEmpty()) {
                        getLogger().info("[PAPI] Keine Placeholder in der StatusAPI-Config gefunden.");
                    } else {
                        getLogger().info("[PAPI] " + papiTokens.size() + " Placeholder erkannt: " + papiTokens);
                    }
                }
            }, 40L, 600L); // nach 2s starten, alle 30s wiederholen

            // Sync-Task läuft dauerhaft – tut nichts wenn papiTokens leer
            Bukkit.getScheduler().runTaskTimer(this, this::syncPapiValues, scoreboardSyncIntervalTicks, scoreboardSyncIntervalTicks);
        } else {
            getLogger().info("[PAPI] PlaceholderAPI nicht gefunden – Placeholder werden nicht aufgelöst.");
        }

        getLogger().info("StatusAPIBridge gestartet. Ziel: " + statusApiUrl);
    }

    @Override
    public void onDisable() {
        httpExecutor.shutdownNow();
    }

    private boolean setupEconomy() {
        if (getServer().getPluginManager().getPlugin("Vault") == null) return false;
        RegisteredServiceProvider<Economy> rsp =
                getServer().getServicesManager().getRegistration(Economy.class);
        if (rsp == null) return false;
        economy = rsp.getProvider();
        return economy != null;
    }

    // ── Events ────────────────────────────────────────────────────────────────

    @EventHandler(priority = EventPriority.MONITOR)
    public void onJoin(PlayerJoinEvent e) {
        Player player = e.getPlayer();
        Bukkit.getScheduler().runTaskLater(this, () -> {
            if (!player.isOnline()) return;
            if (economy != null) pushEconomy(player);
            pushPlayerScoreboardData(player);
            if (papiEnabled && !papiTokens.isEmpty()) pushPapiValues(player);
            // Nametag: LuckPerms-Prefix über dem Kopf setzen
            if (nametagEnabled) applyNametag(player);
        }, pushDelayTicks);
    }

    @EventHandler(priority = EventPriority.MONITOR)
    public void onQuit(PlayerQuitEvent e) {
        Player player = e.getPlayer();
        UUID   id     = player.getUniqueId();
        if (economy != null) pushEconomyAsync(id, player.getName(), economy.getBalance(player));
        lastPushedBalance.remove(id);
        lastPushedHealth.remove(id);
        lastPushedCompass.remove(id);
        lastPushedWorld.remove(id);
        lastPushedData.remove(id);
        lastPushedStats.remove(id);
        lastPapiValues.remove(id);
        lastNametagPrefix.remove(id);
        // Nametag-Team beim Quit aufräumen
        if (nametagEnabled) removeNametag(player);
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onDamage(EntityDamageEvent e) {
        if (!(e.getEntity() instanceof Player player)) return;
        Bukkit.getScheduler().runTaskLater(this,
                () -> { if (player.isOnline()) pushHealthIfChanged(player); }, 1L);
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onHeal(EntityRegainHealthEvent e) {
        if (!(e.getEntity() instanceof Player player)) return;
        Bukkit.getScheduler().runTaskLater(this,
                () -> { if (player.isOnline()) pushHealthIfChanged(player); }, 1L);
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onMove(PlayerMoveEvent e) {
        // getTo() kann in 1.20.5+ bei reinen Head-Rotationen null sein
        if (e.getTo() == null) return;
        if (e.getFrom().getYaw() == e.getTo().getYaw()) return;
        pushCompassIfChanged(e.getPlayer());
    }

    // ── Periodische Tasks ─────────────────────────────────────────────────────

    private void pushChangedBalancesForOnlinePlayers() {
        if (economy == null) return;
        for (Player player : Bukkit.getOnlinePlayers()) {
            double current = economy.getBalance(player);
            Double last    = lastPushedBalance.get(player.getUniqueId());
            if (last == null || Math.abs(current - last) > 0.000001d)
                pushEconomyAsync(player.getUniqueId(), player.getName(), current);
        }
    }

    private void pushScoreboardData() {
        double tps = getCurrentTps();
        for (Player player : Bukkit.getOnlinePlayers()) {
            pushPlayerScoreboardData(player);
            pushTpsAsync(player.getUniqueId(), tps);
        }
    }

    private void pushPlayerScoreboardData(Player player) {
        pushHealthIfChanged(player);
        pushCompassIfChanged(player);
        pushWorldIfChanged(player);
        pushPlayerDataIfChanged(player);
        pushStatsIfChanged(player);
        // Nametag periodisch aktualisieren (reagiert auf Rang-Änderungen)
        if (nametagEnabled) applyNametag(player);
    }

    // ── Push-Methoden ─────────────────────────────────────────────────────────

    public void pushEconomy(Player player) {
        pushEconomyAsync(player.getUniqueId(), player.getName(), economy.getBalance(player));
    }

    private void pushEconomyAsync(UUID uuid, String name, double balance) {
        httpExecutor.execute(() -> {
            try {
                sendPost(statusApiUrl + "/economy/update",
                    "{\"uuid\":\"" + uuid + "\",\"name\":\"" + escapeName(name)
                    + "\",\"balance\":" + balance + "}");
                lastPushedBalance.put(uuid, balance);
            } catch (Exception e) {
                getLogger().warning("Economy-Push fehlgeschlagen fuer " + name + ": " + e.getMessage());
            }
        });
    }

    private void pushHealthIfChanged(Player player) {
        double health = player.getHealth();
        Double last   = lastPushedHealth.get(player.getUniqueId());
        if (last != null && Math.abs(health - last) < 0.01) return;
        lastPushedHealth.put(player.getUniqueId(), health);
        UUID uuid = player.getUniqueId(); String name = player.getName();
        httpExecutor.execute(() -> {
            try {
                sendPost(statusApiUrl + "/scoreboard/health",
                    "{\"uuid\":\"" + uuid + "\",\"name\":\"" + escapeName(name)
                    + "\",\"health\":" + health + "}");
            } catch (Exception e) {
                getLogger().warning("Health-Push fehlgeschlagen: " + e.getMessage());
            }
        });
    }

    private void pushCompassIfChanged(Player player) {
        // Rohen Yaw normalisieren auf 0..360 (0 = Süden, wie MC-Konvention)
        float rawYaw = player.getLocation().getYaw();
        float normYaw = ((rawYaw % 360) + 360) % 360;
        String yawStr = String.format(java.util.Locale.US, "%.1f", normYaw);

        // Nur senden wenn Änderung >= 0.5° – fein genug für 1-Grad-Slots
        String lastStr = lastPushedCompass.get(player.getUniqueId());
        if (lastStr != null) {
            try {
                float lastYaw = Float.parseFloat(lastStr);
                float diff = Math.abs(normYaw - lastYaw);
                if (diff > 180) diff = 360 - diff;   // kürzester Bogenweg
                if (diff < 0.5f) return;
            } catch (NumberFormatException ignored) {}
        }

        lastPushedCompass.put(player.getUniqueId(), yawStr);
        UUID uuid = player.getUniqueId(); String name = player.getName();
        httpExecutor.execute(() -> {
            try {
                sendPost(statusApiUrl + "/scoreboard/compass",
                    "{\"uuid\":\"" + uuid + "\",\"name\":\"" + escapeName(name)
                    + "\",\"compass\":\"" + yawStr + "\"}");
            } catch (Exception e) {
                getLogger().warning("Compass-Push fehlgeschlagen: " + e.getMessage());
            }
        });
    }

    private void pushWorldIfChanged(Player player) {
        String world = player.getWorld().getName();
        if (world.equals(lastPushedWorld.get(player.getUniqueId()))) return;
        lastPushedWorld.put(player.getUniqueId(), world);
        UUID uuid = player.getUniqueId(); String name = player.getName();
        httpExecutor.execute(() -> {
            try {
                sendPost(statusApiUrl + "/player/world",
                    "{\"uuid\":\"" + uuid + "\",\"name\":\"" + escapeName(name)
                    + "\",\"world\":\"" + escapeName(world) + "\"}");
            } catch (Exception e) {
                getLogger().warning("World-Push fehlgeschlagen: " + e.getMessage());
            }
        });
    }

    private void pushPlayerDataIfChanged(Player player) {
        int    x  = player.getLocation().getBlockX();
        int    y  = player.getLocation().getBlockY();
        int    z  = player.getLocation().getBlockZ();
        String gm = player.getGameMode().name();
        int    exp= player.getLevel();
        int    fd = player.getFoodLevel();
        double sp = player.getWalkSpeed();
        String wld= player.getWorld().getName();
        String key = x+","+y+","+z+","+gm+","+exp+","+fd+","+String.format("%.2f",sp)+","+wld;
        if (key.equals(lastPushedData.get(player.getUniqueId()))) return;
        lastPushedData.put(player.getUniqueId(), key);
        UUID uuid = player.getUniqueId();
        String name = player.getName();
        httpExecutor.execute(() -> {
            try {
                sendPost(statusApiUrl + "/player/data",
                    "{\"uuid\":\"" + uuid + "\",\"name\":\"" + escapeName(name)
                    + "\",\"x\":" + x
                    + ",\"y\":" + y
                    + ",\"z\":" + z
                    + ",\"gamemode\":\"" + gm + "\""
                    + ",\"exp\":" + exp
                    + ",\"food\":" + fd
                    + ",\"speed\":" + String.format(java.util.Locale.US, "%.4f", sp)
                    + ",\"world\":\"" + escapeName(wld) + "\"}");
            } catch (Exception e) {
                getLogger().warning("PlayerData-Push fehlgeschlagen: " + e.getMessage());
            }
        });
    }

    private void pushStatsIfChanged(Player player) {
        int kills  = player.getStatistic(org.bukkit.Statistic.PLAYER_KILLS);
        int deaths = player.getStatistic(org.bukkit.Statistic.DEATHS);
        // Playtime in Ticks aus Minecraft-Statistik → umrechnen in Sekunden
        long playtimeTicks = player.getStatistic(org.bukkit.Statistic.PLAY_ONE_MINUTE); // tatsächlich in Ticks
        long playtimeSecs  = playtimeTicks / 20;

        String key = kills + "," + deaths + "," + playtimeSecs;
        if (key.equals(lastPushedStats.get(player.getUniqueId()))) return;
        lastPushedStats.put(player.getUniqueId(), key);

        UUID uuid = player.getUniqueId();
        String name = player.getName();
        httpExecutor.execute(() -> {
            try {
                sendPost(statusApiUrl + "/stats/update",
                    "{\"uuid\":\"" + uuid + "\",\"name\":\"" + escapeName(name)
                    + "\",\"kills\":" + kills
                    + ",\"deaths\":" + deaths
                    + ",\"playtime\":" + playtimeSecs + "}");
            } catch (Exception e) {
                getLogger().warning("Stats-Push fehlgeschlagen: " + e.getMessage());
            }
        });
    }

    // ── TicketSystem Push ──────────────────────────────────────────────────

    /**
     * Pushed TicketSystem-Daten an die StatusAPI.
     * Globale Werte (offene Tickets, Bewertungen) werden einmal pro Intervall gesendet.
     * Pro Spieler wird die Anzahl eigener aktiver Tickets mitgeschickt.
     *
     * Voraussetzung: TicketSystem muss auf demselben Bukkit-Server laufen.
     */
    private void pushTicketData() {
        try {
            Class<?> pluginClass = Class.forName("de.ticketsystem.TicketPlugin");
            Object tsPlugin = Bukkit.getPluginManager().getPlugin("TicketSystem");
            if (tsPlugin == null) return;

            Object db = pluginClass.getMethod("getDatabaseManager").invoke(tsPlugin);
            if (db == null) return;

            Class<?> dbClass  = db.getClass();
            Class<?> statsClass = Class.forName("de.ticketsystem.database.DatabaseManager$TicketStats");

            Object stats = dbClass.getMethod("getTicketStats").invoke(db);
            int totalOpen    = (int) statsClass.getField("open").get(stats);
            int totalClaimed = (int) statsClass.getField("closed").get(stats); // "closed" im stats-Kontext = bearbeitet

            // CLAIMED direkt zählen via getTicketsByStatus
            Class<?> statusEnum = Class.forName("de.ticketsystem.model.TicketStatus");
            Object claimed = statusEnum.getField("CLAIMED").get(null);
            // Varargs via Reflection: typisiertes Array (TicketStatus[]) erzeugen, kein Object[]
            Object statusArray = java.lang.reflect.Array.newInstance(statusEnum, 1);
            java.lang.reflect.Array.set(statusArray, 0, claimed);
            @SuppressWarnings("unchecked")
            java.util.List<?> claimedTickets = (java.util.List<?>) dbClass
                .getMethod("getTicketsByStatus", statusArray.getClass())
                .invoke(db, statusArray);
            int totalClaimedCount = claimedTickets == null ? 0 : claimedTickets.size();

            int ratGood = (int) statsClass.getField("thumbsUp").get(stats);
            int ratBad  = (int) statsClass.getField("thumbsDown").get(stats);

            // Globale Werte nur senden wenn geändert
            String globalKey = totalOpen + "," + totalClaimedCount + "," + ratGood + "," + ratBad;
            if (!globalKey.equals(lastPushedTicketGlobal)) {
                lastPushedTicketGlobal = globalKey;
                String globalJson = "{\"total_open\":" + totalOpen
                    + ",\"total_claimed\":" + totalClaimedCount
                    + ",\"rating_good\":" + ratGood
                    + ",\"rating_bad\":" + ratBad + "}";
                sendPost(statusApiUrl + "/ticket/update", globalJson);
            }

            // Pro Spieler: eigene aktive Tickets
            for (org.bukkit.entity.Player player : Bukkit.getOnlinePlayers()) {
                int myOpen = (int) dbClass
                    .getMethod("countOpenTicketsByPlayer", java.util.UUID.class)
                    .invoke(db, player.getUniqueId());
                String playerJson = "{\"uuid\":\"" + player.getUniqueId() + "\",\"my_open\":" + myOpen + "}";
                sendPost(statusApiUrl + "/ticket/update", playerJson);
            }
        } catch (ClassNotFoundException e) {
            // TicketSystem nicht installiert – kein Fehler loggen
        } catch (Exception e) {
            getLogger().warning("[TicketPush] Fehler: " + e.getMessage());
        }
    }

    // ── PlaceholderAPI ────────────────────────────────────────────────────────

    private boolean fetchPapiTokensFromStatusAPI() {
        try {
            @SuppressWarnings("deprecation")
            java.net.URL url = new java.net.URI(statusApiUrl + "/papi/tokens").toURL();
            java.net.HttpURLConnection c = (java.net.HttpURLConnection) url.openConnection();
            c.setRequestMethod("GET");
            c.setConnectTimeout(3000);
            c.setReadTimeout(3000);
            if (c.getResponseCode() != 200) { c.disconnect(); return false; }
            java.io.InputStream is = c.getInputStream();
            StringBuilder sb = new StringBuilder();
            int ch; while ((ch = is.read()) != -1) sb.append((char) ch);
            c.disconnect();
            String body = sb.toString().trim();
            papiTokens.clear();
            if (body.startsWith("[") && body.endsWith("]")) {
                String inner = body.substring(1, body.length() - 1).trim();
                if (!inner.isEmpty()) {
                    int i = 0;
                    while (i < inner.length()) {
                        while (i < inner.length() && inner.charAt(i) != '"') i++;
                        if (i >= inner.length()) break;
                        i++;
                        StringBuilder token = new StringBuilder();
                        while (i < inner.length() && inner.charAt(i) != '"') {
                            char c2 = inner.charAt(i++);
                            if (c2 == '\\' && i < inner.length()) c2 = inner.charAt(i++);
                            token.append(c2);
                        }
                        i++;
                        if (token.length() > 0) papiTokens.add(token.toString());
                    }
                }
            }
            return true;
        } catch (Exception e) { return false; }
    }

    private void syncPapiValues() {
        if (!papiEnabled || papiTokens.isEmpty()) return;
        for (Player p : Bukkit.getOnlinePlayers()) pushPapiValues(p);
    }

    private void pushPapiValues(Player p) {
        try {
            Class<?> papiClass = Class.forName("me.clip.placeholderapi.PlaceholderAPI");
            java.lang.reflect.Method setPlaceholders = papiClass.getMethod("setPlaceholders", Player.class, String.class);
            StringBuilder jsonValues = new StringBuilder();
            for (String token : papiTokens) {
                String resolved = (String) setPlaceholders.invoke(null, p, "%" + token + "%");
                if (resolved == null) resolved = "";
                if (jsonValues.length() > 0) jsonValues.append(",");
                jsonValues.append("\"").append(esc(token)).append("\":\"").append(esc(resolved)).append("\"");
            }
            String snapshot = jsonValues.toString();
            if (snapshot.equals(lastPapiValues.get(p.getUniqueId()))) return;
            lastPapiValues.put(p.getUniqueId(), snapshot);
            String json = "{\"uuid\":\"" + p.getUniqueId() + "\",\"placeholders\":{" + snapshot + "}}";
            httpExecutor.execute(() -> {
                try { sendPost(statusApiUrl + "/player/papi", json); }
                catch (Exception e) { getLogger().warning("[PAPI] Push fehlgeschlagen: " + e.getMessage()); }
            });
        } catch (ClassNotFoundException ignored) {
        } catch (Exception e) { getLogger().warning("[PAPI] Fehler: " + e.getMessage()); }
    }

    private static String esc(String s) {
        if (s == null) return "";
        return s.replace("\\", "\\\\").replace("\"", "\\\"");
    }

    private void pushTpsAsync(UUID uuid, double tps) {
        httpExecutor.execute(() -> {
            try {
                sendPost(statusApiUrl + "/scoreboard/tps",
                    "{\"uuid\":\"" + uuid + "\",\"tps\":" + tps + "}");
            } catch (Exception ignored) {}
        });
    }

    // ── Nametag-Methoden ──────────────────────────────────────────────────────

    /**
     * Setzt den LuckPerms-Prefix als Nametag über dem Spieler-Kopf.
     * Nutzt die Bukkit Scoreboard Team API – zuverlässig auf allen Spigot/Paper-Versionen.
     * Wird bei Join und periodisch (scoreboard-sync) aufgerufen.
     */
    @SuppressWarnings("deprecation")
    private void applyNametag(Player player) {
        if (!nametagEnabled || nametagBoard == null) return;
        String prefix = getLuckPermsPrefix(player);

        // Change-Detection: nicht neu setzen wenn Prefix gleich geblieben
        String last = lastNametagPrefix.get(player.getUniqueId());
        if (prefix.equals(last)) return;
        lastNametagPrefix.put(player.getUniqueId(), prefix);

        // Team-Name: "vnt_" + erste 12 Zeichen der UUID (ohne Bindestriche)
        // Minecraft-Limit: 16 Zeichen für Teamnamen
        String teamName = "vnt" + player.getUniqueId().toString().replace("-", "").substring(0, 13);

        try {
            // Bestehendes Team holen oder neu erstellen
            org.bukkit.scoreboard.Team team = nametagBoard.getTeam(teamName);
            if (team == null) {
                team = nametagBoard.registerNewTeam(teamName);
            }

            // Prefix setzen (Bukkit konvertiert §-Codes automatisch)
            String coloredPrefix = org.bukkit.ChatColor.translateAlternateColorCodes('&', prefix) + " ";
            team.setPrefix(coloredPrefix);
            team.setSuffix("");

            // Spieler dem Team zuweisen
            team.addEntry(player.getName());

            // Scoreboard dem Spieler zuweisen
            player.setScoreboard(nametagBoard);

            // Alle anderen Spieler auf dasselbe Scoreboard setzen damit sie den Prefix sehen
            for (Player other : Bukkit.getOnlinePlayers()) {
                if (!other.equals(player) && other.getScoreboard() != nametagBoard) {
                    other.setScoreboard(nametagBoard);
                }
            }
        } catch (Exception e) {
            getLogger().warning("[Nametag] Fehler beim Setzen des Prefixes für " + player.getName() + ": " + e.getMessage());
        }
    }

    /**
     * Entfernt den Spieler aus seinem Nametag-Team beim Disconnect.
     */
    private void removeNametag(Player player) {
        if (nametagBoard == null) return;
        String teamName = "vnt" + player.getUniqueId().toString().replace("-", "").substring(0, 13);
        try {
            org.bukkit.scoreboard.Team team = nametagBoard.getTeam(teamName);
            if (team != null) {
                team.removeEntry(player.getName());
                // Team löschen wenn leer
                if (team.getEntries().isEmpty()) team.unregister();
            }
        } catch (Exception ignored) {}
    }

    /**
     * Holt den LuckPerms-Prefix eines Spielers via Reflection (keine harte Dependency).
     * Gibt leeren String zurück wenn LuckPerms nicht vorhanden oder kein Prefix gesetzt.
     */
    private String getLuckPermsPrefix(Player player) {
        try {
            Class<?> provClass = Class.forName("net.luckperms.api.LuckPermsProvider");
            Object api = provClass.getMethod("get").invoke(null);
            Object um  = api.getClass().getMethod("getUserManager").invoke(api);
            Object usr = um.getClass().getMethod("getUser", UUID.class).invoke(um, player.getUniqueId());
            if (usr == null) return "";
            Class<?> qoClass = Class.forName("net.luckperms.api.query.QueryOptions");
            Object opts  = qoClass.getMethod("defaultContextualOptions").invoke(null);
            Object cache = usr.getClass().getMethod("getCachedData").invoke(usr);
            Object meta  = cache.getClass().getMethod("getMetaData", qoClass).invoke(cache, opts);
            Object pfx   = meta.getClass().getMethod("getPrefix").invoke(meta);
            if (pfx != null && !pfx.toString().isEmpty()) return pfx.toString();
        } catch (ClassNotFoundException ignored) {
            // LuckPerms nicht installiert
        } catch (Exception e) {
            getLogger().warning("[Nametag] LuckPerms-Prefix konnte nicht gelesen werden: " + e.getMessage());
        }
        return "";
    }

    // ── Hilfsmethoden ─────────────────────────────────────────────────────────

    /**
     * Erkennt beim Start die Server-Version und setzt den internen Modus.
     * Sichtbar im Server-Log als [StatusAPIBridge] Versions-Modus: ...
     */
    private void detectMinecraftVersion() {
        String bukkitVersion = Bukkit.getBukkitVersion(); // z.B. "1.21.1-R0.1-SNAPSHOT" oder "26.1.2-R0.1-SNAPSHOT"
        // Alles ab 26.x gilt als "neuer Modus" ohne NMS-Fallback
        try {
            String major = bukkitVersion.split("\\.")[0];
            int majorVersion = Integer.parseInt(major);
            isLegacyMode = majorVersion < 26;
        } catch (Exception e) {
            isLegacyMode = true; // Fallback: sicherer Legacy-Modus
        }
        getLogger().info("Versions-Modus: "
            + (isLegacyMode ? "1.21.x-Modus (NMS-Fallback aktiv)" : "26.1.x-Modus (kein NMS-Fallback)")
            + " | BukkitVersion: " + bukkitVersion);
    }

    /**
     * TPS auslesen – kompatibel mit Paper 1.21+, Spigot 1.21+, Java 17/21.
     * Reihenfolge:
     *  1. Paper-API: getTPS() direkt auf dem Server (sauberster Weg)
     *  2. Spigot-Reflection: recentTps-Feld auf dem NMS-MinecraftServer
     *  3. Fallback: 20.0
     */
    private double getCurrentTps() {
        // 1. Bevorzugt: Bukkit.getTPS() – funktioniert auf beiden Versionen
        try {
            double[] tps = (double[]) Bukkit.getServer().getClass()
                    .getMethod("getTPS").invoke(Bukkit.getServer());
            if (tps != null && tps.length > 0) return Math.min(20.0, tps[0]);
        } catch (Exception ignored) {}

        // 2. NMS-Reflection-Fallback – nur im 1.21.x-Modus
        //    Auf 26.1.x schlägt recentTps fehl → wird bewusst übersprungen
        if (isLegacyMode) {
            try {
                Object nmsServer = Bukkit.getServer().getClass()
                        .getMethod("getServer").invoke(Bukkit.getServer());
                for (String fieldName : new String[]{"recentTps", "tps"}) {
                    try {
                        java.lang.reflect.Field f = nmsServer.getClass().getField(fieldName);
                        Object val = f.get(nmsServer);
                        if (val instanceof double[]) {
                            double[] tps = (double[]) val;
                            if (tps.length > 0) return Math.min(20.0, tps[0]);
                        }
                    } catch (NoSuchFieldException ignored2) {}
                }
            } catch (Exception ignored) {}
        }

        return 20.0;
    }

    private void sendPost(String urlStr, String json) throws Exception {
        @SuppressWarnings("deprecation")
        URL url = new java.net.URI(urlStr).toURL();
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setDoOutput(true);
        conn.setConnectTimeout(3000);
        conn.setReadTimeout(3000);
        conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
        byte[] body = json.getBytes(StandardCharsets.UTF_8);
        conn.setRequestProperty("Content-Length", String.valueOf(body.length));
        try (OutputStream os = conn.getOutputStream()) { os.write(body); }
        int code = conn.getResponseCode();
        if (code != 200)
            getLogger().warning("StatusAPI antwortete mit Code " + code + " fuer " + urlStr);
        conn.disconnect();
    }

    private String escapeName(String name) {
        if (name == null) return "";
        return name.replace("\\", "\\\\").replace("\"", "\\\"");
    }
}