/*
 *   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.mods;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import de.steamwar.linkage.Linked;
import de.steamwar.persistent.Storage;
import de.steamwar.sql.Mod;
import de.steamwar.sql.SWException;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.listeners.BasicListener;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Linked
public class FabricModSender extends BasicListener {

    private final Set<String> neededFabricMods = new HashSet<>();
    private final Set<String> neededQuiltMods = new HashSet<>();

    public FabricModSender() {
        neededFabricMods.add("java");
        neededFabricMods.add("minecraft");
        neededFabricMods.add("steamwarmodsender");
        neededQuiltMods.addAll(neededFabricMods);

        neededFabricMods.add("fabricloader");
        neededQuiltMods.add("quilt_loader");

        VelocityCore.schedule(() -> {
            synchronized (Storage.fabricExpectPluginMessage) {
                for (Map.Entry<Player, Long> entry : Storage.fabricExpectPluginMessage.entrySet()) {
                    if (!Storage.fabricCheckedPlayers.containsKey(entry.getKey())) {
                        continue;
                    }
                    if (System.currentTimeMillis() - entry.getValue() > TimeUnit.SECONDS.toMillis(20)) {
                        Storage.fabricExpectPluginMessage.remove(entry.getKey());
                        return;
                    }
                }
            }
        }).repeat(1, TimeUnit.SECONDS).schedule();
    }

    public void handlePluginMessage(PluginMessageEvent e) {
        Player player = (Player) e.getSource();
        SteamwarUser user = SteamwarUser.get(player.getUniqueId());

        if (!Storage.fabricCheckedPlayers.containsKey(player)) {
            synchronized (Storage.fabricExpectPluginMessage) {
                if (Storage.fabricExpectPluginMessage.containsKey(player)) {
                    logMessage(user, "Was not fabric checked but send message nonetheless", Arrays.toString(e.getData()));
                    return;
                }
            }
        }
        Storage.fabricExpectPluginMessage.remove(player);

        List<Mod> mods = new ArrayList<>();

        ByteBuf buf = Unpooled.wrappedBuffer(e.getData());
        String data = ProtocolUtils.readString(buf, 1024 * 1024);
        if (buf.readableBytes() > 0) {
            logMessage(user, "Invalid message length", Arrays.toString(e.getData()));
            return;
        }

        JsonArray array = JsonParser.parseString(data).getAsJsonArray();

        for (JsonElement mod : array) {
            mods.add(Mod.getOrCreate(mod.getAsString(), Mod.Platform.FABRIC));
        }

        if (!neededModsContained(neededFabricMods, mods) && !neededModsContained(neededQuiltMods, mods)) {
            logMessage(user, "Needed mods are not contained", data);
            return;
        }

        if (!ModUtils.handleMods(player, mods)) return;

        if (!Storage.fabricCheckedPlayers.containsKey(player)) {
            Storage.fabricCheckedPlayers.put(player, data.hashCode());
        } else if (Storage.fabricCheckedPlayers.get(player) != data.hashCode()) {
            logMessage(user, "Mods changed during runtime", data);
        }
    }

    @Subscribe
    public void onServerSwitchEvent(ServerConnectedEvent e) {
        if (e.getPreviousServer().isEmpty()) return;
        synchronized (Storage.fabricExpectPluginMessage) {
            Storage.fabricExpectPluginMessage.put(e.getPlayer(), System.currentTimeMillis());
        }
    }

    @Subscribe
    public void onDisconnect(DisconnectEvent e) {
        Player player = e.getPlayer();

        Storage.fabricCheckedPlayers.remove(player);
        synchronized (Storage.fabricExpectPluginMessage) {
            Storage.fabricExpectPluginMessage.remove(player);
        }
    }

    private boolean neededModsContained(Set<String> neededMods, List<Mod> mods) {
        return mods.stream()
                .map(Mod::getModName)
                .filter(neededMods::contains)
                .count() == neededFabricMods.size();
    }

    private void logMessage(SteamwarUser user, String reason, String data) {
        SWException.log("FabricModSender " + user.getUserName() + ": " + reason, data);
    }
}
