package net.viper.status.modules.broadcast;

import net.viper.status.StatusAPI;
import net.md_5.bungee.api.ChatColor;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Plugin;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.BaseComponent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.ClickEvent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.TextComponent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Listener;
import net.viper.status.module.Module;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * BroadcastModule
 *
 * Fixes:
 *   - loadSchedules(): ID-Split nutzt jetzt indexOf/lastIndexOf statt split("\\.") mit length==2-Check.
 *     Damit werden auch clientScheduleIds die Punkte enthalten korrekt geladen. (Bug #2)
 *   - handleBroadcast(): &-Farbcodes werden jetzt auch in der Nachricht selbst \u00fcbersetzt. (Bug #3)
 *   - handleBroadcast(): Literal \n in der Nachricht wird als echter Zeilenumbruch gerendert. (Bug #4)
 *   - handleBroadcast(): URLs (http/https) werden als anklickbare TextComponents eingebettet. (Bug #5)
 */
public class BroadcastModule implements Module, Listener {

    private Plugin plugin;
    private boolean enabled = true;
    private String requiredApiKey = "";
    private String format = "%prefix% %message%";
    private String fallbackPrefix = "[Broadcast]";
    private String fallbackPrefixColor = "&c";
    private String fallbackBracketColor = "&8";
    private String fallbackMessageColor = "&f";

    private final Map<String, ScheduledBroadcast> scheduledByClientId = new ConcurrentHashMap<>();
    private File schedulesFile;
    private final SimpleDateFormat dateFormat;

    public BroadcastModule() {
        dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    }

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

    @Override
    public void onEnable(Plugin plugin) {
        this.plugin = plugin;
        schedulesFile = new File(plugin.getDataFolder(), "broadcasts.schedules");
        loadConfig();
        if (!enabled) return;
        try { plugin.getProxy().getPluginManager().registerListener(plugin, this); } catch (Throwable ignored) {}
        plugin.getLogger().fine("[BroadcastModule] aktiviert. Format: " + format);
        loadSchedules();
        plugin.getProxy().getScheduler().schedule(plugin, this::processScheduled, 1, 1, TimeUnit.SECONDS);
    }

    @Override
    public void onDisable(Plugin plugin) {
        saveSchedules();
        scheduledByClientId.clear();
    }

    private void loadConfig() {
        File file = new File(plugin.getDataFolder(), "verify.properties");
        if (!file.exists()) return;
        try (InputStream in = new FileInputStream(file)) {
            Properties props = new Properties();
            props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
            enabled            = Boolean.parseBoolean(props.getProperty("broadcast.enabled", "true"));
            requiredApiKey     = props.getProperty("broadcast.api_key", "").trim();
            format             = props.getProperty("broadcast.format", format).trim();
            if (format.isEmpty()) format = "%prefix% %message%";
            fallbackPrefix       = props.getProperty("broadcast.prefix", fallbackPrefix).trim();
            fallbackPrefixColor  = props.getProperty("broadcast.prefix-color", fallbackPrefixColor).trim();
            fallbackBracketColor = props.getProperty("broadcast.bracket-color", fallbackBracketColor).trim();
            fallbackMessageColor = props.getProperty("broadcast.message-color", fallbackMessageColor).trim();
        } catch (IOException e) {
            plugin.getLogger().warning("[BroadcastModule] Fehler beim Laden von verify.properties: " + e.getMessage());
        }
    }

    public boolean handleBroadcast(String sourceName, String message, String type, String apiKeyHeader,
                                   String prefix, String prefixColor, String bracketColor, String messageColor) {
        loadConfig();
        if (!enabled) return false;
        if (requiredApiKey != null && !requiredApiKey.isEmpty()) {
            if (apiKeyHeader == null || !requiredApiKey.equals(apiKeyHeader)) {
                plugin.getLogger().warning("[BroadcastModule] Broadcast abgelehnt: API-Key fehlt oder inkorrekt.");
                return false;
            }
        }

        if (message == null) message = "";
        if (sourceName == null || sourceName.isEmpty()) sourceName = "System";
        if (type == null) type = "global";

        String usedPrefix       = (prefix != null && !prefix.trim().isEmpty()) ? prefix.trim() : fallbackPrefix;
        String usedPrefixColor  = (prefixColor != null && !prefixColor.trim().isEmpty()) ? prefixColor.trim() : fallbackPrefixColor;
        String usedBracketColor = (bracketColor != null && !bracketColor.trim().isEmpty()) ? bracketColor.trim() : fallbackBracketColor;
        String usedMessageColor = (messageColor != null && !messageColor.trim().isEmpty()) ? messageColor.trim() : fallbackMessageColor;

        String prefixColorCode  = normalizeColorCode(usedPrefixColor);
        String bracketColorCode = normalizeColorCode(usedBracketColor);
        String messageColorCode = normalizeColorCode(usedMessageColor);

        String finalPrefix;
        if (!bracketColorCode.isEmpty()) {
            String textContent = usedPrefix;
            if (textContent.startsWith("[")) textContent = textContent.substring(1);
            if (textContent.endsWith("]"))   textContent = textContent.substring(0, textContent.length() - 1);
            finalPrefix = bracketColorCode + "[" + prefixColorCode + textContent + bracketColorCode + "]" + ChatColor.RESET;
        } else {
            finalPrefix = prefixColorCode + usedPrefix + ChatColor.RESET;
        }

        // FIX #1: &-Farbcodes auch in der Nachricht selbst \u00fcbersetzen
        String translatedMessage = ChatColor.translateAlternateColorCodes('&', message);
        String coloredMessage = (messageColorCode.isEmpty() ? "" : messageColorCode) + translatedMessage;

        String out = format
                .replace("%name%",          sourceName)
                .replace("%prefix%",        finalPrefix)
                .replace("%prefixColored%", finalPrefix)
                .replace("%message%",       translatedMessage)
                .replace("%messageColored%",coloredMessage)
                .replace("%type%",          type);

        // FIX #2: \r entfernen (Windows CRLF -> nur LF), Literal \\n als Fallback
        out = out.replace("\r\n", "\n").replace("\r", "").replace("\\n", "\n");

        // FIX #3: Nachricht mit anklickbaren URLs aufbauen
        BaseComponent[] components = buildClickableComponents(out);
        int sent = 0;
        for (ProxiedPlayer p : plugin.getProxy().getPlayers()) {
            try { p.sendMessage(components); sent++; } catch (Throwable ignored) {}
        }
        StatusAPI.debugLog(plugin, "[BroadcastModule] Broadcast gesendet (Empf\u00e4nger=" + sent + "): " + message);
        return true;
    }

    /**
     * Baut ein BaseComponent-Array aus einem formatierten String.
     * URLs (http/https) werden als anklickbare TextComponents eingebettet.
     * Unterst\u00fctzt auch echte Newlines (\n) als Zeilenumbruch.
     */
    private BaseComponent[] buildClickableComponents(String text) {
        // Regex f\u00fcr URLs
        java.util.regex.Pattern urlPattern = java.util.regex.Pattern.compile(
                "(https?://[^\\s\\n]+)", java.util.regex.Pattern.CASE_INSENSITIVE);

        ComponentBuilder builder = new ComponentBuilder("");

        // Zeilenweise aufteilen (echte \n)
        String[] lines = text.split("\n", -1);
        for (int li = 0; li < lines.length; li++) {
            if (li > 0) {
                // Zeilenumbruch als eigene Komponente
                builder.append(TextComponent.fromLegacyText("\n"));
            }

            String line = lines[li];
            java.util.regex.Matcher matcher = urlPattern.matcher(line);
            int lastEnd = 0;

            while (matcher.find()) {
                // Text vor der URL (mit Minecraft-Farbcodes)
                if (matcher.start() > lastEnd) {
                    String before = line.substring(lastEnd, matcher.start());
                    builder.append(TextComponent.fromLegacyText(before));
                }

                // URL selbst: anklickbar + unterstrichen
                String url = matcher.group(1);
                TextComponent urlComponent = new TextComponent(url);
                urlComponent.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url));
                // Farbe der URL auf Cyan setzen damit sie sich abhebt
                urlComponent.setColor(ChatColor.AQUA);
                urlComponent.setUnderlined(true);
                builder.append(urlComponent, ComponentBuilder.FormatRetention.NONE);

                lastEnd = matcher.end();
            }

            // Restlicher Text nach der letzten URL
            if (lastEnd < line.length()) {
                builder.append(TextComponent.fromLegacyText(line.substring(lastEnd)));
            }
        }

        return builder.create();
    }

    private String normalizeColorCode(String code) {
        if (code == null) return "";
        code = code.trim();
        if (code.isEmpty()) return "";
        return code.contains("&") ? ChatColor.translateAlternateColorCodes('&', code) : code;
    }

    private void saveSchedules() {
        Properties props = new Properties();
        for (Map.Entry<String, ScheduledBroadcast> entry : scheduledByClientId.entrySet()) {
            String id = entry.getKey();
            ScheduledBroadcast sb = entry.getValue();
            // Wir escapen den ID-Wert damit Punkte in der ID nicht den Parser verwirren
            props.setProperty(id + ".nextRunMillis", String.valueOf(sb.nextRunMillis));
            props.setProperty(id + ".sourceName",    sb.sourceName);
            props.setProperty(id + ".message",       sb.message);
            props.setProperty(id + ".type",          sb.type);
            props.setProperty(id + ".prefix",        sb.prefix);
            props.setProperty(id + ".prefixColor",   sb.prefixColor);
            props.setProperty(id + ".bracketColor",  sb.bracketColor);
            props.setProperty(id + ".messageColor",  sb.messageColor);
            props.setProperty(id + ".recur",         sb.recur);
        }
        try (OutputStream out = new FileOutputStream(schedulesFile)) {
            props.store(out, "PulseCast Scheduled Broadcasts");
        } catch (IOException e) {
            plugin.getLogger().severe("[BroadcastModule] Konnte Schedules nicht speichern: " + e.getMessage());
        }
    }

    /**
     * FIX #2: Robusteres Parsen der Property-Keys.
     * Statt split("\\.") mit length==2-Check nutzen wir lastIndexOf('.') um den letzten
     * Punkt als Trenner zu verwenden. Damit funktionieren auch IDs die Punkte enthalten.
     *
     * Bekannte Felder: nextRunMillis, sourceName, message, type, prefix, prefixColor,
     *                  bracketColor, messageColor, recur → alle ohne Punkte im Namen.
     */
    private void loadSchedules() {
        if (!schedulesFile.exists()) return;
        Properties props = new Properties();
        try (InputStream in = new FileInputStream(schedulesFile)) {
            props.load(in);
        } catch (IOException e) {
            plugin.getLogger().severe("[BroadcastModule] Konnte Schedules nicht laden: " + e.getMessage());
            return;
        }

        // Bekannte Feld-Suffixe
        Set<String> knownFields = new HashSet<>(Arrays.asList(
                "nextRunMillis", "sourceName", "message", "type",
                "prefix", "prefixColor", "bracketColor", "messageColor", "recur"
        ));

        Map<String, ScheduledBroadcast> loaded = new LinkedHashMap<>();
        for (String key : props.stringPropertyNames()) {
            // Finde das letzte '.' das einen bekannten Feldnamen abtrennt
            int lastDot = key.lastIndexOf('.');
            if (lastDot < 0) continue;
            String field = key.substring(lastDot + 1);
            if (!knownFields.contains(field)) continue;
            String id    = key.substring(0, lastDot);
            if (id.isEmpty()) continue;
            String value = props.getProperty(key);

            ScheduledBroadcast sb = loaded.computeIfAbsent(id,
                    k -> new ScheduledBroadcast(k, 0, "", "", "", "", "", "", "", ""));

            switch (field) {
                case "nextRunMillis": try { sb.nextRunMillis = Long.parseLong(value); } catch (NumberFormatException ignored) {} break;
                case "sourceName":   sb.sourceName   = value; break;
                case "message":      sb.message       = value; break;
                case "type":         sb.type          = value; break;
                case "prefix":       sb.prefix        = value; break;
                case "prefixColor":  sb.prefixColor   = value; break;
                case "bracketColor": sb.bracketColor  = value; break;
                case "messageColor": sb.messageColor  = value; break;
                case "recur":        sb.recur         = value; break;
            }
        }
        scheduledByClientId.putAll(loaded);
        plugin.getLogger().fine("[BroadcastModule] geplante Broadcasts wiederhergestellt.");
    }

    public boolean scheduleBroadcast(long timestampMillis, String sourceName, String message, String type,
                                     String apiKeyHeader, String prefix, String prefixColor, String bracketColor,
                                     String messageColor, String recur, String clientScheduleId) {
        loadConfig();
        if (!enabled) return false;
        if (requiredApiKey != null && !requiredApiKey.isEmpty()) {
            if (apiKeyHeader == null || !requiredApiKey.equals(apiKeyHeader)) {
                plugin.getLogger().warning("[BroadcastModule] schedule abgelehnt: API-Key fehlt oder inkorrekt.");
                return false;
            }
        }
        if (message == null)    message = "";
        if (sourceName == null || sourceName.isEmpty()) sourceName = "System";
        if (type == null)       type = "global";
        if (recur == null)      recur = "none";

        String id = (clientScheduleId != null && !clientScheduleId.trim().isEmpty())
                ? clientScheduleId.trim() : UUID.randomUUID().toString();

        long now = System.currentTimeMillis();
        if (timestampMillis <= now) {
            plugin.getLogger().warning("[BroadcastModule] Geplante Zeit in der Vergangenheit → sende sofort!");
            return handleBroadcast(sourceName, message, type, apiKeyHeader, prefix, prefixColor, bracketColor, messageColor);
        }

        ScheduledBroadcast sb = new ScheduledBroadcast(id, timestampMillis, sourceName, message, type,
                prefix, prefixColor, bracketColor, messageColor, recur);
        scheduledByClientId.put(id, sb);
        saveSchedules();
        StatusAPI.debugLog(plugin, "[BroadcastModule] Neue geplante Nachricht registriert: " + id
                + " @ " + dateFormat.format(new Date(timestampMillis)));
        return true;
    }

    public boolean cancelScheduled(String clientScheduleId) {
        if (clientScheduleId == null || clientScheduleId.trim().isEmpty()) return false;
        ScheduledBroadcast removed = scheduledByClientId.remove(clientScheduleId);
        if (removed != null) { StatusAPI.debugLog(plugin, "[BroadcastModule] Schedule abgebrochen: " + clientScheduleId); saveSchedules(); return true; }
        return false;
    }

    private void processScheduled() {
        if (scheduledByClientId.isEmpty()) return;
        long now = System.currentTimeMillis();
        List<String> toRemove = new ArrayList<>();
        boolean changed = false;

        for (Map.Entry<String, ScheduledBroadcast> entry : scheduledByClientId.entrySet()) {
            ScheduledBroadcast sb = entry.getValue();
            if (sb.nextRunMillis <= now) {
                StatusAPI.debugLog(plugin, "[BroadcastModule] ⏰ Sende geplante Nachricht (ID: " + entry.getKey() + ")");
                handleBroadcast(sb.sourceName, sb.message, sb.type, "", sb.prefix, sb.prefixColor, sb.bracketColor, sb.messageColor);
                if (!"none".equalsIgnoreCase(sb.recur)) {
                    long next = computeNextMillis(sb.nextRunMillis, sb.recur);
                    if (next > 0) { sb.nextRunMillis = next; changed = true; }
                    else { toRemove.add(entry.getKey()); changed = true; }
                } else { toRemove.add(entry.getKey()); changed = true; }
            }
        }
        if (changed || !toRemove.isEmpty()) {
            for (String k : toRemove) { scheduledByClientId.remove(k); }
            saveSchedules();
        }
    }

    private long computeNextMillis(long currentMillis, String recur) {
        switch (recur.toLowerCase(Locale.ROOT)) {
            case "hourly": return currentMillis + TimeUnit.HOURS.toMillis(1);
            case "daily":  return currentMillis + TimeUnit.DAYS.toMillis(1);
            case "weekly": return currentMillis + TimeUnit.DAYS.toMillis(7);
            default:       return -1L;
        }
    }

    private static class ScheduledBroadcast {
        final String clientId;
        long nextRunMillis;
        String sourceName, message, type, prefix, prefixColor, bracketColor, messageColor, recur;

        ScheduledBroadcast(String clientId, long nextRunMillis, String sourceName, String message, String type,
                           String prefix, String prefixColor, String bracketColor, String messageColor, String recur) {
            this.clientId      = clientId;
            this.nextRunMillis = nextRunMillis;
            this.sourceName    = sourceName;
            this.message       = message;
            this.type          = type;
            this.prefix        = prefix;
            this.prefixColor   = prefixColor;
            this.bracketColor  = bracketColor;
            this.messageColor  = messageColor;
            this.recur         = recur == null ? "none" : recur;
        }
    }
}