package net.viper.status.modules.chat;

import net.viper.status.ratelimit.GlobalRateLimitFramework;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

/**
 * Chat-Filter: Anti-Spam, Caps-Filter, Wort-Blacklist, Farbcode-Filter.
 *
 * Reihenfolge der Pr\u00fcfungen in processChat():
 *   1. Spam-Cooldown (zu schnell geschrieben?)
 *   2. Gleiche Nachricht wiederholt?
 *   3. Zu viele Gro\u00dfbuchstaben?
 *   4. Verbotene W\u00f6rter → ersetzen durch ****
 *   5. Farbcodes (& Codes) → nur mit Permission erlaubt
 */
public class ChatFilter {

    private final ChatFilterConfig cfg;
    private final GlobalRateLimitFramework rateLimiter = GlobalRateLimitFramework.getInstance();

    // UUID → letzte Nachricht (f\u00fcr Duplikat-Check)
    private final Map<UUID, String> lastMessageText = new ConcurrentHashMap<>();

    // Kompilierte Regex-Pattern f\u00fcr Blacklist-W\u00f6rter
    private final List<Pattern> blacklistPatterns = new ArrayList<>();

    public ChatFilter(ChatFilterConfig cfg) {
        this.cfg = cfg;
        compilePatterns();
    }

    private void compilePatterns() {
        blacklistPatterns.clear();
        for (String word : cfg.blacklistWords) {
            // Case-insensitiv, ganzes Wort oder Teilwort je nach Config
            blacklistPatterns.add(Pattern.compile(
                    "(?i)" + Pattern.quote(word)));
        }
    }

    // ===== Ergebnis-Klasse =====

    public enum FilterResult {
        ALLOWED,      // Nachricht darf durch
        BLOCKED,      // Nachricht blockiert (Spam/Flood)
        MODIFIED      // Nachricht wurde ver\u00e4ndert (W\u00f6rter ersetzt / Caps reduziert)
    }

    public static class FilterResponse {
        public final FilterResult result;
        public final String       message;    // ggf. modifizierte Nachricht
        public final String       denyReason; // Nachricht an den Spieler wenn BLOCKED

        FilterResponse(FilterResult result, String message, String denyReason) {
            this.result     = result;
            this.message    = message;
            this.denyReason = denyReason;
        }
    }

    // ===== Haupt-Filtermethode =====

    /**
     * Wendet alle aktiven Filter auf eine Nachricht an.
     *
     * @param uuid          UUID des sendenden Spielers
     * @param message       Originalnachricht
     * @param isAdmin       true → Farbcodes und Caps-Filter \u00fcberspringen
     * @param hasColorPerm  true → &-Farbcodes erlaubt
     * @param hasFormatPerm true → &l, &o etc. erlaubt
     * @return FilterResponse mit Ergebnis und ggf. modifizierter Nachricht
     */
    public FilterResponse filter(UUID uuid, String message, boolean isAdmin,
                                  boolean hasColorPerm, boolean hasFormatPerm) {

        // ── 1. Spam-Cooldown ──
        if (cfg.antiSpamEnabled && !isAdmin) {
            if (cfg.globalRateLimitEnabled) {
                GlobalRateLimitFramework.Result rl = rateLimiter.check(
                        "chat.message",
                        uuid.toString(),
                        new GlobalRateLimitFramework.Rule(
                                true,
                                cfg.globalRateLimitWindowMs,
                                cfg.globalRateLimitMaxActions,
                                cfg.globalRateLimitBlockMs
                        )
                );
                if (rl.isBlocked()) {
                    return new FilterResponse(FilterResult.BLOCKED, message, cfg.spamMessage);
                }
            }
        }

        // ── 2. Duplikat-Check ──
        if (cfg.duplicateCheckEnabled && !isAdmin) {
            String lastText = lastMessageText.get(uuid);
            if (message.equalsIgnoreCase(lastText)) {
                return new FilterResponse(FilterResult.BLOCKED, message, cfg.duplicateMessage);
            }
            lastMessageText.put(uuid, message);
        }

        String result = message;
        boolean modified = false;

        // ── 3. Blacklist ──
        if (cfg.blacklistEnabled) {
            String filtered = applyBlacklist(result);
            if (!filtered.equals(result)) {
                result   = filtered;
                modified = true;
            }
        }

        // ── 4. Caps-Filter ──
        if (cfg.capsFilterEnabled && !isAdmin) {
            String capped = applyCapsFilter(result);
            if (!capped.equals(result)) {
                result   = capped;
                modified = true;
            }
        }

        // ── 5. Farbcodes filtern (nur wenn keine Permission) ──
        if (!isAdmin) {
            String colorFiltered = applyColorFilter(result, hasColorPerm, hasFormatPerm);
            if (!colorFiltered.equals(result)) {
                result   = colorFiltered;
                modified = true;
            }
        }

        // ── 6. Anti-Werbung ──
        if (cfg.antiAdEnabled && !isAdmin) {
            if (containsAdvertisement(result)) {
                return new FilterResponse(FilterResult.BLOCKED, result, cfg.antiAdMessage);
            }
        }

        return new FilterResponse(
                modified ? FilterResult.MODIFIED : FilterResult.ALLOWED,
                result,
                null
        );
    }

    // ===== Einzelne Filter =====

    private String applyBlacklist(String message) {
        String result = message;
        for (Pattern p : blacklistPatterns) {
            result = p.matcher(result).replaceAll(buildStars(p.pattern()
                    .replace("(?i)", "").replace("\\Q", "").replace("\\E", "").length()));
        }
        return result;
    }

    private String applyCapsFilter(String message) {
        // Z\u00e4hle Gro\u00dfbuchstaben
        int total = 0, upper = 0;
        for (char c : message.toCharArray()) {
            if (Character.isLetter(c)) { total++; if (Character.isUpperCase(c)) upper++; }
        }
        if (total < cfg.capsMinLength) return message; // Kurze Nachrichten ignorieren
        double ratio = total > 0 ? (double) upper / total : 0;
        if (ratio < cfg.capsMaxPercent / 100.0) return message;
        // Zu viele Caps → alles lowercase
        return message.toLowerCase();
    }

    /**
     * Entfernt &-Farbcodes je nach Permission.
     * hasColorPerm  → &0-&9, &a-&f erlaubt
     * hasFormatPerm → &l, &o, &n, &m, &k erlaubt
     * Beide false   → alle &-Codes entfernen
     */
    private String applyColorFilter(String message, boolean hasColorPerm, boolean hasFormatPerm) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < message.length(); i++) {
            char c = message.charAt(i);
            if (c == '&' && i + 1 < message.length()) {
                char next = Character.toLowerCase(message.charAt(i + 1));
                boolean isColor  = (next >= '0' && next <= '9') || (next >= 'a' && next <= 'f');
                boolean isFormat = "lonmkr".indexOf(next) >= 0;
                boolean isHex    = next == '#';

                if (isColor && hasColorPerm)  { sb.append(c); continue; }
                if (isFormat && hasFormatPerm) { sb.append(c); continue; }
                if (isHex && hasColorPerm)     { sb.append(c); continue; }

                // Kein Recht → & und n\u00e4chstes Zeichen \u00fcberspringen
                if (isColor || isFormat) { i++; continue; }
                // Hex: &# + 6 Zeichen \u00fcberspringen (i zeigt auf &, +1 = #, +2..+7 = RRGGBB)
                if (isHex && i + 7 <= message.length()) { i += 7; continue; }
            }
            sb.append(c);
        }
        return sb.toString();
    }

    // ===== Anti-Werbung =====

    // Vorkompilierte Patterns (einmalig beim Classload)
    private static final Pattern PATTERN_IP =
            Pattern.compile("\\b(\\d{1,3}[.,]){3}\\d{1,3}(:\\d{1,5})?\\b");

    private static final Pattern PATTERN_DOMAIN_GENERIC =
            Pattern.compile("(?i)\\b[a-z0-9-]{2,63}\\.[a-z]{2,10}(?:[/:\\d]\\S*)?\\b");

    private static final Pattern PATTERN_URL_PREFIX =
            Pattern.compile("(?i)(https?://|www\\.)\\S+");

    /**
     * Pr\u00fcft ob die Nachricht Werbung enth\u00e4lt (IP, URL, fremde Domain).
     * Domains auf der Whitelist werden ignoriert.
     *
     * Erkennt:
     *  - http:// / https:// / www. Prefixe
     *  - IPv4-Adressen (auch mit Port)
     *  - Domain-Namen mit konfigurierten TLDs (z.B. .net, .de, .com)
     *  - Verschleierungsversuche mit Leerzeichen um Punkte ("play . server . net")
     */
    private boolean containsAdvertisement(String message) {
        // Normalisierung: "play . server . net" → "play.server.net"
        String normalized = message.replaceAll("\\s*\\.\\s*", ".");

        // 1. Explizite URL-Prefixe
        if (PATTERN_URL_PREFIX.matcher(normalized).find()) {
            return !allMatchesWhitelisted(normalized, PATTERN_URL_PREFIX);
        }

        // 2. IP-Adressen (werden nie whitelisted)
        if (PATTERN_IP.matcher(normalized).find()) {
            return true;
        }

        // 3. Domains mit bekannten TLDs
        if (!cfg.antiAdBlockedTlds.isEmpty()) {
            java.util.regex.Matcher m = PATTERN_DOMAIN_GENERIC.matcher(normalized);
            while (m.find()) {
                String match = m.group();
                String tld   = extractTld(match);
                if (cfg.antiAdBlockedTlds.contains(tld.toLowerCase())) {
                    if (!isOnWhitelist(match)) return true;
                }
            }
        }

        return false;
    }

    /** true wenn ALLE Treffer des Patterns auf der Whitelist stehen. */
    private boolean allMatchesWhitelisted(String message, Pattern pattern) {
        java.util.regex.Matcher m = pattern.matcher(message);
        while (m.find()) {
            if (!isOnWhitelist(m.group())) return false;
        }
        return true;
    }

    private boolean isOnWhitelist(String match) {
        String lower = match.toLowerCase();
        for (String entry : cfg.antiAdWhitelist) {
            if (lower.contains(entry.toLowerCase())) return true;
        }
        return false;
    }

    private static String extractTld(String domain) {
        String clean = domain.split("[/:]")[0];
        int dot = clean.lastIndexOf('.');
        return dot >= 0 ? clean.substring(dot + 1) : "";
    }

    private static String buildStars(int length) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < Math.max(length, 4); i++) sb.append('*');
        return sb.toString();
    }

    // ===== Cleanup beim Logout =====

    public void cleanup(UUID uuid) {
        lastMessageText.remove(uuid);
        rateLimiter.clearActor(uuid.toString());
    }

    // ===== Konfigurationsklasse =====

    public static class ChatFilterConfig {
        // Anti-Spam
        public boolean antiSpamEnabled  = true;
        public long    spamCooldownMs   = 1500;  // Legacy-Feld fuer Kompatibilitaet
        public int     spamMaxMessages  = 3;     // Legacy-Feld fuer Kompatibilitaet
        public String  spamMessage      = "&cBitte nicht so schnell schreiben!";

        // Globales Rate-Limit-Framework
        public boolean globalRateLimitEnabled = true;
        public long    globalRateLimitWindowMs = 2500;
        public int     globalRateLimitMaxActions = 3;
        public long    globalRateLimitBlockMs = 6000;

        // Duplikat
        public boolean duplicateCheckEnabled = true;
        public String  duplicateMessage      = "&cBitte keine identischen Nachrichten senden.";

        // Blacklist
        public boolean      blacklistEnabled = true;
        public List<String> blacklistWords   = new ArrayList<>();

        // Caps
        public boolean capsFilterEnabled = true;
        public int     capsMinLength     = 6;    // Mindestl\u00e4nge f\u00fcr Caps-Check
        public int     capsMaxPercent    = 70;   // Max. % Gro\u00dfbuchstaben

        // Anti-Werbung
        public boolean      antiAdEnabled     = true;
        public String       antiAdMessage     = "&cWerbung ist in diesem Chat nicht erlaubt!";
        // Domains/Substrings die NICHT geblockt werden (z.B. eigene Serveradresse)
        public List<String> antiAdWhitelist   = new ArrayList<>();
        // TLDs die als Werbung gewertet werden (leer = alle TLDs pr\u00fcfen)
        public List<String> antiAdBlockedTlds = new ArrayList<>(Arrays.asList(
                "net", "com", "de", "org", "gg", "io", "eu", "tv", "xyz",
                "info", "me", "cc", "co", "app", "online", "site", "fun"
        ));
    }
}