package net.viper.status.modules.network;

import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.model.user.User;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.types.PermissionNode;
import net.viper.status.StatusAPI;
import net.viper.status.module.Module;
import net.viper.status.modules.antibot.AntiBotModule;

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

/**
 * MultiAccountGuard
 *
 * Features:
 *  - IP-Check: blockiert zweiten Account von gleicher IP
 *  - Bypass NUR \u00fcber LuckPerms (OP z\u00e4hlt nicht)
 *  - Persistentes Log in multiaccountguard.log
 *  - Staff-Benachrichtigung ingame (Permission: statusapi.staff.notify)
 *  - Tempor\u00e4rer IP-Bann nach X Versuchen (Integration mit AntiBotModule)
 *  - Discord-Webhook bei Konflikt
 */
public class MultiAccountGuard implements Module, Listener {

    private static final String CONFIG_FILE    = "network-guard.properties";
    private static final String LOG_FILE       = "multiaccountguard.log";
    public  static final String BYPASS_PERM    = "statusapi.multiaccountguard.bypass";
    public  static final String STAFF_PERM     = "statusapi.staff.notify";

    private static final DateTimeFormatter LOG_FMT =
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());

    private Plugin plugin;
    private Logger log;
    private File   logFile;

    // Config
    private boolean enabled        = true;
    private boolean checkIp        = true;
    private boolean kickExisting   = false;
    private String  kickMessage    = "&cDu bist bereits mit einem anderen Account online!\n&7Bitte trenne deinen anderen Account zuerst.";

    // Staff-Benachrichtigung
    private boolean staffNotifyEnabled = true;
    private String  staffNotifyFormat  = "&8[&cMAG&8] &e{blocked} &7wurde blockiert &8(2. Account von &e{existing}&8) &7| IP: &f{ip}";

    // Tempor\u00e4rer IP-Bann
    private boolean tempBanEnabled      = true;
    private int     tempBanMaxAttempts  = 3;
    private int     tempBanDurationSecs = 300;
    /** IP → Anzahl Konflikte seit letztem Reset */
    private final Map<String, Integer> attemptsByIp = new ConcurrentHashMap<>();

    // Webhook
    private boolean webhookEnabled      = true;
    private String  webhookUrl          = "";
    private String  webhookUsername     = "StatusAPI";
    private String  webhookThumbnailUrl = "";

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

    // -------------------------------------------------------------------------
    // Enable / Disable
    // -------------------------------------------------------------------------

    @Override
    public void onEnable(Plugin plugin) {
        this.plugin  = plugin;
        this.log     = plugin.getLogger();
        this.logFile = new File(plugin.getDataFolder(), LOG_FILE);

        loadConfig();

        if (!enabled) {
            log.info("[MultiAccountGuard] Deaktiviert.");
            return;
        }

        ProxyServer.getInstance().getPluginManager().registerListener(plugin, this);

        log.info("[MultiAccountGuard] Aktiv | IP-Check=" + checkIp
                + " | kickExisting=" + kickExisting
                + " | staffNotify=" + staffNotifyEnabled
                + " | tempBan=" + tempBanEnabled + "(max=" + tempBanMaxAttempts + ", " + tempBanDurationSecs + "s)"
                + " | Webhook=" + (webhookEnabled && !webhookUrl.isEmpty()));
        log.info("[MultiAccountGuard] Bypass NUR via LuckPerms: /lp user <Name> permission set " + BYPASS_PERM + " true");
        log.info("[MultiAccountGuard] Log-Datei: " + logFile.getAbsolutePath());
    }

    @Override
    public void onDisable(Plugin plugin) {}

    // -------------------------------------------------------------------------
    // Event
    // -------------------------------------------------------------------------

    @EventHandler(priority = EventPriority.HIGHEST)
    public void onPostLogin(PostLoginEvent event) {
        if (!enabled) return;

        ProxiedPlayer joining = event.getPlayer();

        if (hasBypass(joining)) {
            log.info("[MultiAccountGuard] " + joining.getName() + " hat Bypass (LuckPerms) – \u00fcbersprungen.");
            return;
        }

        UUID   joiningUuid = joining.getUniqueId();
        String joiningIp   = extractIp(joining.getSocketAddress());

        if (joiningIp == null) {
            log.warning("[MultiAccountGuard] Konnte IP von " + joining.getName() + " nicht lesen – \u00fcbersprungen.");
            return;
        }

        log.info("[MultiAccountGuard] Login-Check: " + joining.getName()
                + " | UUID=" + joiningUuid + " | IP=" + joiningIp);

        // Alle anderen Spieler (sich selbst per UUID ausschlie\u00dfen)
        List<ProxiedPlayer> others = new ArrayList<>();
        for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
            if (p.getUniqueId().equals(joiningUuid)) continue;
            others.add(p);
        }

        for (ProxiedPlayer online : others) {
            if (hasBypass(online)) continue;

            String onlineIp = extractIp(online.getSocketAddress());
            if (onlineIp == null) continue;

            if (checkIp && joiningIp.equals(onlineIp)) {
                log.warning("[MultiAccountGuard] KONFLIKT: "
                        + joining.getName() + " (" + joiningUuid + ")"
                        + "  <->  " + online.getName() + " (" + online.getUniqueId() + ")"
                        + "  IP=" + joiningIp);
                handleConflict(joining, online, joiningIp);
                return;
            }
        }

        log.info("[MultiAccountGuard] " + joining.getName() + " – kein Konflikt.");
    }

    // -------------------------------------------------------------------------
    // Konflikt behandeln
    // -------------------------------------------------------------------------

    private void handleConflict(ProxiedPlayer joining, ProxiedPlayer existing, String ip) {

        TextComponent msg = new TextComponent(
                ChatColor.translateAlternateColorCodes('&', kickMessage));

        final String blockedName, allowedName;
        final UUID   blockedUuid, allowedUuid;

        if (kickExisting) {
            existing.disconnect(msg);
            blockedName = existing.getName(); blockedUuid = existing.getUniqueId();
            allowedName = joining.getName();  allowedUuid  = joining.getUniqueId();
            log.warning("[MultiAccountGuard] Bestehender Account " + existing.getName() + " getrennt.");
        } else {
            joining.disconnect(msg);
            blockedName = joining.getName();  blockedUuid = joining.getUniqueId();
            allowedName = existing.getName(); allowedUuid  = existing.getUniqueId();
            log.warning("[MultiAccountGuard] Neuer Account " + joining.getName() + " blockiert.");
        }

        // 1. Persistentes Log
        writeLog(blockedName, blockedUuid, allowedName, allowedUuid, ip);

        // 2. Staff-Benachrichtigung
        if (staffNotifyEnabled) {
            notifyStaff(blockedName, allowedName, ip);
        }

        // 3. Tempor\u00e4rer IP-Bann
        if (tempBanEnabled) {
            int attempts = attemptsByIp.merge(ip, 1, Integer::sum);
            log.info("[MultiAccountGuard] IP " + ip + " hat " + attempts + "/" + tempBanMaxAttempts + " Versuche.");
            if (attempts >= tempBanMaxAttempts) {
                attemptsByIp.remove(ip);
                banIp(ip);
            }
        }

        // 4. Discord-Webhook (async)
        if (webhookEnabled && webhookUrl != null && !webhookUrl.isEmpty()) {
            final String bn = blockedName, an = allowedName;
            final UUID   bu = blockedUuid, au = allowedUuid;
            final int    att = attemptsByIp.getOrDefault(ip, tempBanMaxAttempts);
            ProxyServer.getInstance().getScheduler().runAsync(plugin,
                    () -> sendWebhook(bn, bu, an, au, ip, att));
        }
    }

    // -------------------------------------------------------------------------
    // 1. Persistentes Log
    // -------------------------------------------------------------------------

    private void writeLog(String blockedName, UUID blockedUuid,
                           String allowedName,  UUID allowedUuid, String ip) {
        try {
            if (!logFile.getParentFile().exists()) logFile.getParentFile().mkdirs();

            String line = String.format("[%s] KONFLIKT | Geblockt: %s (%s) | Online: %s (%s) | IP: %s%n",
                    LOG_FMT.format(Instant.now()),
                    blockedName, blockedUuid,
                    allowedName, allowedUuid,
                    ip);

            Files.write(logFile.toPath(), line.getBytes(StandardCharsets.UTF_8),
                    StandardOpenOption.CREATE, StandardOpenOption.APPEND);

        } catch (Exception e) {
            log.warning("[MultiAccountGuard] Log-Fehler: " + e.getMessage());
        }
    }

    // -------------------------------------------------------------------------
    // 2. Staff-Benachrichtigung
    // -------------------------------------------------------------------------

    private void notifyStaff(String blockedName, String existingName, String ip) {
        String raw = staffNotifyFormat
                .replace("{blocked}",  blockedName)
                .replace("{existing}", existingName)
                .replace("{ip}",       ip);
        String formatted = ChatColor.translateAlternateColorCodes('&', raw);
        TextComponent msg = new TextComponent(formatted);

        int notified = 0;
        for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
            if (p.hasPermission(STAFF_PERM)) {
                p.sendMessage(msg);
                notified++;
            }
        }
        log.info("[MultiAccountGuard] Staff-Benachrichtigung gesendet an " + notified + " Spieler.");
    }

    // -------------------------------------------------------------------------
    // 3. Tempor\u00e4rer IP-Bann via AntiBotModule
    // -------------------------------------------------------------------------

    private void banIp(String ip) {
        try {
            StatusAPI statusApi = (StatusAPI) ProxyServer.getInstance()
                    .getPluginManager().getPlugin("StatusAPI");
            if (statusApi == null) {
                log.warning("[MultiAccountGuard] StatusAPI nicht gefunden – IP-Bann nicht m\u00f6glich.");
                return;
            }
            AntiBotModule antiBot = statusApi.getModuleManager().getModule(AntiBotModule.class);
            if (antiBot == null) {
                log.warning("[MultiAccountGuard] AntiBotModule nicht gefunden – IP-Bann nicht m\u00f6glich.");
                return;
            }
            antiBot.blockIpExternal(ip, tempBanDurationSecs);
            log.warning("[MultiAccountGuard] IP " + ip + " f\u00fcr " + tempBanDurationSecs + "s gebannt (zu viele Multi-Account-Versuche).");

            // Staff \u00fcber den Bann informieren
            String banMsg = ChatColor.translateAlternateColorCodes('&',
                    "&8[&cMAG&8] &7IP &f" + ip + " &7wurde f\u00fcr &c" + tempBanDurationSecs + "s &7gebannt &8(zu viele Versuche).");
            for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
                if (p.hasPermission(STAFF_PERM)) {
                    p.sendMessage(new TextComponent(banMsg));
                }
            }

            // In Log schreiben
            try {
                String line = String.format("[%s] IP-BANN | IP: %s | Dauer: %ds%n",
                        LOG_FMT.format(Instant.now()), ip, tempBanDurationSecs);
                Files.write(logFile.toPath(), line.getBytes(StandardCharsets.UTF_8),
                        StandardOpenOption.CREATE, StandardOpenOption.APPEND);
            } catch (Exception ignored) {}

        } catch (Exception e) {
            log.warning("[MultiAccountGuard] IP-Bann Fehler: " + e.getMessage());
        }
    }

    // -------------------------------------------------------------------------
    // 4. Discord-Webhook
    // -------------------------------------------------------------------------

    private void sendWebhook(String blockedName, UUID blockedUuid,
                              String allowedName,  UUID allowedUuid,
                              String ip, int attempts) {
        StringBuilder fields = new StringBuilder();
        appendField(fields, "\uD83D\uDEAB Geblockter Account",
                blockedName + "\n`" + blockedUuid + "`", false);
        appendField(fields, "\u2705 Verbundener Account",
                allowedName + "\n`" + allowedUuid + "`", false);
        appendField(fields, "\uD83C\uDF10 IP", "`" + ip + "`", true);
        appendField(fields, "Aktion",
                kickExisting ? "Alter Account getrennt" : "Neuer Account blockiert", true);
        if (tempBanEnabled) {
            appendField(fields, "\u26A0\uFE0F Versuche",
                    attempts + " / " + tempBanMaxAttempts
                    + (attempts >= tempBanMaxAttempts ? " \u2192 IP gebannt!" : ""), true);
        }

        String body = "{\"username\":\"" + esc(webhookUsername) + "\","
                + "\"embeds\":[{"
                + "\"title\":\"\uD83D\uDD12 Multi-Account erkannt\","
                + "\"description\":\"Ein Spieler hat versucht mit einem zweiten Account beizutreten.\","
                + "\"color\":15158332,"
                + "\"fields\":[" + fields + "],"
                + "\"footer\":{\"text\":\"StatusAPI \u2022 MultiAccountGuard\"},"
                + "\"timestamp\":\"" + Instant.now() + "\""
                + (webhookThumbnailUrl != null && !webhookThumbnailUrl.isEmpty()
                    ? ",\"thumbnail\":{\"url\":\"" + esc(webhookThumbnailUrl) + "\"}" : "")
                + "}]}";

        HttpURLConnection conn = null;
        try {
            byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
            conn = (HttpURLConnection) new URL(webhookUrl).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) log.warning("[MultiAccountGuard] Webhook HTTP " + code);
            else log.info("[MultiAccountGuard] Webhook gesendet (HTTP " + code + ")");
        } catch (Exception e) {
            log.warning("[MultiAccountGuard] Webhook-Fehler: " + e.getMessage());
        } finally {
            if (conn != null) conn.disconnect();
        }
    }

    private void appendField(StringBuilder sb, String name, String value, boolean inline) {
        if (sb.length() > 0) sb.append(",");
        sb.append("{\"name\":\"").append(esc(name))
          .append("\",\"value\":\"").append(esc(value))
          .append("\",\"inline\":").append(inline).append("}");
    }

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

    // -------------------------------------------------------------------------
    // Bypass – NUR LuckPerms, kein OP-Fallback
    // -------------------------------------------------------------------------

    private boolean hasBypass(ProxiedPlayer player) {
        try {
            User user = LuckPermsProvider.get().getUserManager().getUser(player.getUniqueId());
            if (user == null) return false;
            for (Node node : user.getNodes()) {
                if (node instanceof PermissionNode) {
                    PermissionNode pn = (PermissionNode) node;
                    if (pn.getPermission().equalsIgnoreCase(BYPASS_PERM) && pn.getValue()) {
                        return true;
                    }
                }
            }
            return false;
        } catch (Exception e) {
            log.warning("[MultiAccountGuard] LuckPerms-Check fehlgeschlagen f\u00fcr " + player.getName() + ": " + e.getMessage());
            return false;
        }
    }

    // -------------------------------------------------------------------------
    // Config
    // -------------------------------------------------------------------------

    private void loadConfig() {
        File file = new File(plugin.getDataFolder(), CONFIG_FILE);
        if (!file.exists()) {
            log.info("[MultiAccountGuard] Config nicht gefunden – Defaults werden verwendet.");
            return;
        }
        Properties p = new Properties();
        try (FileInputStream fis = new FileInputStream(file);
             InputStreamReader r = new InputStreamReader(fis, StandardCharsets.UTF_8)) {
            p.load(r);

            enabled             = Boolean.parseBoolean(p.getProperty("multiaccountguard.enabled",                "true"));
            checkIp             = Boolean.parseBoolean(p.getProperty("multiaccountguard.check_ip",               "true"));
            kickExisting        = Boolean.parseBoolean(p.getProperty("multiaccountguard.kick_existing",          "false"));
            kickMessage         = p.getProperty("multiaccountguard.kick_message", kickMessage);

            staffNotifyEnabled  = Boolean.parseBoolean(p.getProperty("multiaccountguard.staff_notify.enabled",   "true"));
            staffNotifyFormat   = p.getProperty("multiaccountguard.staff_notify.format", staffNotifyFormat);

            tempBanEnabled      = Boolean.parseBoolean(p.getProperty("multiaccountguard.tempban.enabled",        "true"));
            tempBanMaxAttempts  = parseInt(p.getProperty("multiaccountguard.tempban.max_attempts",  "3"),  3);
            tempBanDurationSecs = parseInt(p.getProperty("multiaccountguard.tempban.duration_secs", "300"), 300);

            webhookEnabled      = Boolean.parseBoolean(p.getProperty("multiaccountguard.webhook.enabled",        "true"));
            webhookUrl          = p.getProperty("networkinfo.webhook.url",           "").trim();
            webhookUsername     = p.getProperty("networkinfo.webhook.username",      "StatusAPI").trim();
            webhookThumbnailUrl = p.getProperty("networkinfo.webhook.thumbnail_url", "").trim();

        } catch (Exception e) {
            log.warning("[MultiAccountGuard] Config-Fehler: " + e.getMessage());
        }
    }

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

    // -------------------------------------------------------------------------

    private String extractIp(SocketAddress addr) {
        if (addr instanceof InetSocketAddress)
            return ((InetSocketAddress) addr).getAddress().getHostAddress();
        return null;
    }

    public boolean isEnabled()      { return enabled; }
    public boolean isCheckIp()      { return checkIp; }
    public boolean isKickExisting() { return kickExisting; }
}