/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.velocitycore.listeners;

import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.api.event.player.PlayerChatEvent;
import com.velocitypowered.api.event.player.TabCompleteEvent;
import com.velocitypowered.api.proxy.ConsoleCommandSource;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.ServerInfo;
import de.steamwar.linkage.Linked;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.ChatterGroup;
import de.steamwar.messages.Message;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.network.packets.server.PingPacket;
import de.steamwar.persistent.Subserver;
import de.steamwar.sql.*;
import de.steamwar.velocitycore.ArenaMode;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.commands.PunishmentCommand;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.network.NetworkSender;

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

@Linked
public class ChatListener extends BasicListener {

    private static final Logger cmdLogger = Logger.getLogger("Command logger");

    private static final List<String> rankedModes = ArenaMode.getAllModes().stream().filter(gameModeConfig -> gameModeConfig.Server.Ranked).map(GameModeConfig::getSchemTypeOrInternalName).toList();

    private static final Set<String> noLogCommands = Set.of("webpw", "webpassword", "web");

    @Subscribe(order = PostOrder.FIRST)
    public void fixCommands(CommandExecuteEvent e) {
        String command = e.getCommand();
        if (command.startsWith("7")) {
            command = "/" + command.substring(1);

            CommandExecuteEvent.CommandResult result = e.getResult();
            if (result.isForwardToServer()) {
                result = CommandExecuteEvent.CommandResult.forwardToServer(command);
            } else if (result.isAllowed()) {
                result = CommandExecuteEvent.CommandResult.command(command);
            }

            e.setResult(result);
        }
    }

    @Subscribe(order = PostOrder.LAST)
    public void logCommands(CommandExecuteEvent e) {
        String command = e.getCommand();
        int space = command.indexOf(' ');
        String cmd = space != -1 ? command.substring(0, space) : command;
        if (VelocityCore.getProxy().getCommandManager().hasCommand(cmd)) {
            CommandSource source = e.getCommandSource();
            String name;
            SteamwarUser user = null;
            if (source instanceof Player player) {
                user = SteamwarUser.get(player.getUniqueId());
                name = player.getUsername();
            } else if (source instanceof ConsoleCommandSource) {
                user = SteamwarUser.byId(-1);
                name = "«CONSOLE»";
            } else {
                name = source.toString();
            }

            if (noLogCommands.contains(cmd)) {
                return;
            }

            switch (cmd) {
                case "msg":
                case "r":
                case "tc":
                    AuditLog.createSensitiveCommand(AuditLog.SERVER_NAME_VELOCITY, null, user, "/" + command);
                    break;
                default:
                    AuditLog.createCommand(AuditLog.SERVER_NAME_VELOCITY, null, user, "/" + command);
                    break;
            }

            cmdLogger.log(Level.INFO, "%s -> executed command /%s".formatted(name, command));
        } else if (e.getCommandSource() instanceof Player player) {
            // System.out.println("spoofChatInput " + e);
            player.spoofChatInput("/" + command);
            e.setResult(CommandExecuteEvent.CommandResult.denied());
        }
    }

    @Subscribe
    public void onChatEvent(PlayerChatEvent e) {
        Player player = e.getPlayer();
        String message = e.getMessage();

        e.setResult(PlayerChatEvent.ChatResult.denied());

        SteamwarUser user = SteamwarUser.get(player.getUniqueId());
        if (message.contains("jndi:ldap")) {
            PunishmentCommand.ban(user, Punishment.PERMA_TIME, "Versuchte Exploit-Ausnutzung", SteamwarUser.byId(-1), true);
            VelocityCore.getLogger().log(Level.SEVERE, "%s %s wurde automatisch wegen jndi:ldap gebannt.".formatted(user.getUserName(), user.getId()));
            return;
        }

        if (isMistypedCommand(player, message)) {
            return;
        }

        Subserver subserver = Subserver.getSubserver(player);
        String serverName = AuditLog.SERVER_NAME_VELOCITY;
        if (Subserver.isArena(subserver) && subserver.getServer() == player.getCurrentServer().orElseThrow().getServerInfo()) {
            serverName = subserver.getServer().getName();
            localChat(Chatter.of(player), message);
        } else if (message.startsWith("+")) {
            serverName = player.getCurrentServer()
                    .map(ServerConnection::getServerInfo)
                    .map(ServerInfo::getName)
                    .orElse(serverName);
            localChat(Chatter.of(player), message.substring(1));
        } else {
            sendChat(Chatter.of(player), Chatter.globalChat(), "CHAT_GLOBAL", null, message);
        }
        AuditLog.createChat(serverName, null, user, message);
    }

    private static boolean isMistypedCommand(Player player, String message) {
        String command = message.substring(1);
        boolean isCommand = message.startsWith("7") && command.matches("^[7/]?[A-Za-z]+");
        if (isCommand && Boolean.FALSE.equals(VelocityCore.getProxy().getCommandManager().executeAsync(player, command).join())) {
            if (command.startsWith("7")) {
                command = "/" + command.substring(1);
            }
            message = "/" + command;

            if (filteredCommand(Chatter.of(player), message)) return true;

            player.spoofChatInput(message);
        }

        return isCommand;
    }

    public static void sendChat(Chatter sender, ChatterGroup receivers, String format, Chatter msgReceiver, String message) {
        SteamwarUser user = sender.user();
        final String coloredMessage = user.hasPerm(UserPerm.COLOR_CHAT) ? message.replace('&', '§') : message;
        if (chatFilter(sender, coloredMessage)) return;

        boolean noReceiver = true;
        for (Chatter player : receivers.getChatters()) {
            if (player.chatShown()) {
                chatToReciever(player, msgReceiver, user, format, coloredMessage);
                if (sender.user().getId() != player.user().getId()) noReceiver = false;
            }

        }

        if (format.equals("CHAT_GLOBAL")) {
            DiscordBot.withBot(bot -> chatToReciever(bot.getIngameChat(), msgReceiver, user, format, coloredMessage));
        } else if (format.equals("CHAT_SERVERTEAM")) {
            DiscordBot.withBot(bot -> chatToReciever(bot.getServerTeamChat(), msgReceiver, user, "CHAT_GLOBAL", coloredMessage));
        } else if (noReceiver) {
            sender.system("CHAT_NO_RECEIVER");
        }
    }

    public static void localChat(PlayerChatter sender, String message) {
        if (message.isEmpty()) {
            sender.system("CHAT_BC_USAGE");
            return;
        }

        if (ChatListener.filteredCommand(sender, message)) return;

        if (!message.startsWith("/") && chatFilter(sender, message)) return;

        sender.getPlayer().spoofChatInput(message);
    }

    private static boolean chatFilter(Chatter sender, String message) {
        if (!sender.chatShown()) {
            sender.system("CHAT_RECEIVE");
            return true;
        }

        if (message.replace("§[a-f0-9klmno]", "").trim().isEmpty()) {
            sender.system("CHAT_EMPTY");
            return true;
        }

        SteamwarUser user = sender.user();
        if (!user.hasPerm(UserPerm.TEAM) && (message.contains("http:") || message.contains("https:") || message.contains("www."))) {
            sender.system("CHAT_NO_LINKS");
            return true;
        }

        if (PunishmentCommand.isPunishedWithMessage(sender, Punishment.PunishmentType.Mute)) return true;

        if (message.contains("LIXFEL")) {
            specialAlert(sender, "Lixfel", "CHAT_LIXFEL_", 3, 6, 11, 12, 15);
        }
        if (message.contains("YOYONOW")) {
            specialAlert(sender, "YoyoNow", "CHAT_YOYONOW_", 3, 6, 11, 12);
        }
        if (message.contains("CHAOSCAOT")) {
            specialAlert(sender, "Chaoscaot", "CHAT_CHAOSCAOT_", 3, 6, 11, 12, 15, 17);
        }

        return false;
    }

    private static void chatToReciever(Chatter receiver, Chatter msgReceiver, SteamwarUser sender, String format, String message) {
        UserPerm.Prefix prefix = sender.prefix();
        String chatColorCode = sender.hasPerm(UserPerm.TEAM) ? "§f" : "§7";
        receiver.prefixless(format,
                sender,
                msgReceiver == null ? receiver : msgReceiver,
                highlightMentions(message, chatColorCode, receiver),
                sender.getTeam() == 0 ? "" : "§" + Team.byId(sender.getTeam()).getTeamColor() + Team.byId(sender.getTeam()).getTeamKuerzel() + " ",
                "",
                prefix.getColorCode(),
                prefix.getChatPrefix().length() == 0 ? "§f" : prefix.getChatPrefix() + " ",
                chatColorCode);
    }

    private static boolean filteredCommand(Chatter sender, String message) {
        String command = message.split(" ", 2)[0];
        if (command.startsWith("/") && command.contains(":")) {
            sender.system("UNKNOWN_COMMAND");
            return true;
        }
        return false;
    }

    private static void specialAlert(Chatter sender, String name, String baseMessage, int... delay) {
        sender.system("CHAT_LIXFEL_ACTION_BAR");
        for (int i = 0; i < delay.length; i++) {
            int finalI = i;
            VelocityCore.schedule(() -> sender.prefixless("CHAT_MSG", name, sender.user(), new Message(baseMessage + (finalI + 1)))).delay(delay[i], TimeUnit.SECONDS).schedule();
        }
    }

    private static String highlightMentions(String message, String returnColor, Chatter player) {
        if (!message.contains("@")) return message;

        String mark = "@" + player.user().getUserName();
        return Arrays.stream(message.split(" ")).map(cur -> {
            if (cur.equalsIgnoreCase(mark) && player.getPlayer() != null) {
                NetworkSender.send(player.getPlayer(), new PingPacket(player.user().getId()));
                return "§e" + cur + returnColor;
            }
            return cur;
        }).collect(Collectors.joining(" "));
    }

    @Subscribe
    public void onTabCompleteResponseEvent(TabCompleteEvent e) {
        List<String> suggestions = e.getSuggestions();
        int i = 0;
        while (i < suggestions.size()) {
            String suggestion = suggestions.get(i);
            if (suggestion.startsWith("/") && suggestion.contains(":")) {
                suggestions.remove(i);
            } else {
                i++;
            }
        }
    }
}
