/*
 *   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.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.proxy.LoginPhaseConnection;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.proxy.connection.client.LoginInboundConnection;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import de.steamwar.linkage.Linked;
import de.steamwar.sql.Mod;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.listeners.BasicListener;
import de.steamwar.velocitycore.listeners.PluginMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.AllArgsConstructor;
import net.kyori.adventure.text.Component;

import java.util.*;
import java.util.logging.Level;

@Linked
public class FML2 extends BasicListener {
    // FML2: https://wiki.vg/Minecraft_Forge_Handshake#FML2_protocol_.281.13_-_Current.29
    // FML3: https://github.com/adde0109/Ambassador/tree/non-api/src/main/java/org/adde0109/ambassador/forge

    // FORGE: https://github.com/MinecraftForge/MinecraftForge/blob/1.20.x/src/main/java/net/minecraftforge/network/NetworkInitialization.java
    // FORGE: https://github.com/MinecraftForge/MinecraftForge/blob/1.20.x/src/main/java/net/minecraftforge/network/ForgePacketHandler.java
    // FORGE: https://github.com/MinecraftForge/MinecraftForge/blob/1.20.x/src/main/java/net/minecraftforge/network/packets/ModVersions.java

    public static boolean isFML(LoginInboundConnection connection, String type) {
        return Hostname.getExtraHandshakeData(connection).equals(type);
    }

    private final byte[] fml2ModListPacket;
    private final byte[] fml3ModListPacket;
    private final byte[] forgeModListPacket;

    public FML2() {
        fml2ModListPacket = generateModListPacket(false);
        fml3ModListPacket = generateModListPacket(true);
        forgeModListPacket = PluginMessage.genBufPacket(buf -> {
            buf.writeByte(0); // Login wrapper packet
            ProtocolUtils.writeString(buf, "forge:handshake");

            ByteBuf packet = Unpooled.buffer();
            packet.writeByte(1); // Mod list packet
            ProtocolUtils.writeVarInt(packet, 0); // Mod amount

            ProtocolUtils.writeVarInt(buf, packet.readableBytes());
            buf.writeBytes(packet);
        });
    }

    @Subscribe
    public void onLogin(PreLoginEvent event) {
        LoginInboundConnection connection = (LoginInboundConnection) event.getConnection();

        boolean fml2 = isFML(connection, "\0FML2\0");
        boolean fml3 = isFML(connection, "\0FML3\0");
        boolean forge = isFML(connection, "\0FORGE");
        if (!fml2 && !fml3 && !forge) return;

        FML2LoginHandler handler = new FML2LoginHandler(connection, event.getUniqueId(), forge);

        if (forge) {
            connection.sendLoginPluginMessage(MinecraftChannelIdentifier.from("forge:login"), forgeModListPacket, handler);
        } else {
            connection.sendLoginPluginMessage(MinecraftChannelIdentifier.from("fml:loginwrapper"), fml3 ? fml3ModListPacket : fml2ModListPacket, handler);
        }
    }

    private byte[] generateModListPacket(boolean fml3) {
        return PluginMessage.genBufPacket(buf -> {
            ProtocolUtils.writeString(buf, "fml:handshake");

            ByteBuf packet = Unpooled.buffer();
            packet.writeByte(1); // Mod list packet
            ProtocolUtils.writeVarInt(packet, 0); // Mod amount

            if (fml3) {
                ProtocolUtils.writeVarInt(packet, 1); // Channel amount
                ProtocolUtils.writeString(packet, "forge:tier_sorting");
                ProtocolUtils.writeString(packet, "1.0");
            } else {
                ProtocolUtils.writeVarInt(packet, 0); // Channel amount
            }

            ProtocolUtils.writeVarInt(packet, 0); // Registries amount
            if (fml3) {
                ProtocolUtils.writeVarInt(packet, 0); // DataPacks amount
            }

            ProtocolUtils.writeVarInt(buf, packet.readableBytes());
            buf.writeBytes(packet);
        });
    }

    @AllArgsConstructor
    private static class FML2LoginHandler implements LoginPhaseConnection.MessageConsumer {

        private final LoginInboundConnection connection;
        private final UUID uuid;
        private final boolean forge;

        @Override
        public String toString() {
            return "SteamWar Forge Handler";
        }

        @Override
        public void onMessageResponse(byte[] data) {
            if (data == null) {
                abort(null, "Not FML2/3 client");
                return;
            }

            ByteBuf buf = Unpooled.wrappedBuffer(data);
            if (forge && buf.readByte() != 0) {
                abort(data, "Not FORGE login wrapper");
                return;
            }

            if (!ProtocolUtils.readString(buf).equals(forge ? "forge:handshake" : "fml:handshake")) {
                abort(data, "Not FML2/3/FORGE handshake response");
                return;
            }

            if (ProtocolUtils.readVarInt(buf) != buf.readableBytes()) {
                abort(data, "FML2/3/FORGE packet size mismatch");
                return;
            }

            if (ProtocolUtils.readVarInt(buf) != (forge ? /* Mod Versions */ 1 : /* Mod List Reply */ 2)) {
                abort(data, "Not FML2/3/FORGE mod list reply");
                return;
            }

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

            int modCount = ProtocolUtils.readVarInt(buf);
            for (int i = 0; i < modCount; i++) {
                mods.add(Mod.getOrCreate(ProtocolUtils.readString(buf), Mod.Platform.FORGE));

                if (forge) {
                    ProtocolUtils.readString(buf); // Human readable name
                    ProtocolUtils.readString(buf); // Version
                }
            }

            ModUtils.handleMods(uuid, Locale.getDefault(), connection::disconnect, mods);
        }

        private void abort(byte[] response, String error) {
            connection.disconnect(Component.text(error));
            VelocityCore.getLogger().log(Level.SEVERE, () -> error + "\n" + Base64.getEncoder().encodeToString(response));
        }
    }
}
