// Generated by delombok at Tue Dec 16 19:35:59 UTC 2025
package net.md_5.bungee.query;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.AddressedEnvelope;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import java.net.InetAddress;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;

public class QueryHandler extends SimpleChannelInboundHandler<DatagramPacket> {
    private final ProxyServer bungee;
    private final ListenerInfo listener;
    /*========================================================================*/
    private final Random random = new Random();
    private final Cache<InetAddress, QuerySession> sessions = CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).build();

    private void writeShort(ByteBuf buf, int s) {
        buf.writeShortLE(s);
    }

    private void writeNumber(ByteBuf buf, int i) {
        writeString(buf, Integer.toString(i));
    }

    private void writeString(ByteBuf buf, String s) {
        for (char c : s.toCharArray()) {
            buf.writeByte(c);
        }
        buf.writeByte(0);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
        try {
            handleMessage(ctx, msg);
        } catch (Throwable t) {
            bungee.getLogger().log(Level.WARNING, "Error whilst handling query packet from " + msg.sender(), t);
        }
    }

    private void handleMessage(ChannelHandlerContext ctx, DatagramPacket msg) {
        ByteBuf in = msg.content();
        if (in.readUnsignedByte() != 254 || in.readUnsignedByte() != 253) {
            bungee.getLogger().log(Level.WARNING, "Query - Incorrect magic!: {0}", msg.sender());
            return;
        }
        ByteBuf out = ctx.alloc().buffer();
        AddressedEnvelope response = new DatagramPacket(out, msg.sender());
        byte type = in.readByte();
        int sessionId = in.readInt();
        if (type == 9) {
            out.writeByte(9);
            out.writeInt(sessionId);
            int challengeToken = random.nextInt();
            sessions.put(msg.sender().getAddress(), new QuerySession(challengeToken, System.currentTimeMillis()));
            writeNumber(out, challengeToken);
        }
        if (type == 0) {
            int challengeToken = in.readInt();
            QuerySession session = sessions.getIfPresent(msg.sender().getAddress());
            if (session == null || session.getToken() != challengeToken) {
                throw new IllegalStateException("No session!");
            }
            out.writeByte(0);
            out.writeInt(sessionId);
            if (in.readableBytes() == 0) {
                // Short response
                writeString(out, listener.getMotd()); // MOTD
                writeString(out, "SMP"); // Game Type
                writeString(out, "BungeeCord_Proxy"); // World Name
                writeNumber(out, bungee.getOnlineCount()); // Online Count
                writeNumber(out, listener.getMaxPlayers()); // Max Players
                writeShort(out, listener.getHost().getPort()); // Port
                writeString(out, listener.getHost().getHostString()); // IP
            } else if (in.readableBytes() == 4) {
                // Long Response
                out.writeBytes(new byte[] {115, 112, 108, 105, 116, 110, 117, 109, 0, (byte) 128, 0});
                Map<String, String> data = new LinkedHashMap<>();
                data.put("hostname", listener.getMotd());
                data.put("gametype", "SMP");
                // Start Extra Info
                data.put("game_id", "MINECRAFT");
                data.put("version", bungee.getGameVersion());
                data.put("plugins", "");
                // End Extra Info
                data.put("map", "BungeeCord_Proxy");
                data.put("numplayers", Integer.toString(bungee.getOnlineCount()));
                data.put("maxplayers", Integer.toString(listener.getMaxPlayers()));
                data.put("hostport", Integer.toString(listener.getHost().getPort()));
                data.put("hostip", listener.getHost().getHostString());
                for (Map.Entry<String, String> entry : data.entrySet()) {
                    writeString(out, entry.getKey());
                    writeString(out, entry.getValue());
                }
                out.writeByte(0); // Null
                // Padding
                writeString(out, "\001player_\000");
                // Player List
                for (ProxiedPlayer p : bungee.getPlayers()) {
                    writeString(out, p.getName());
                }
                out.writeByte(0); // Null
            } else {
                // Error!
                throw new IllegalStateException("Invalid data request packet");
            }
        }
        ctx.writeAndFlush(response);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        bungee.getLogger().log(Level.WARNING, "Error whilst handling query packet from " + ctx.channel().remoteAddress(), cause);
    }


    private static class QuerySession {
        private final int token;
        private final long time;

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public QuerySession(final int token, final long time) {
            this.token = token;
            this.time = time;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public int getToken() {
            return this.token;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public long getTime() {
            return this.time;
        }

        @java.lang.Override
        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public boolean equals(final java.lang.Object o) {
            if (o == this) return true;
            if (!(o instanceof QueryHandler.QuerySession)) return false;
            final QueryHandler.QuerySession other = (QueryHandler.QuerySession) o;
            if (!other.canEqual((java.lang.Object) this)) return false;
            if (this.getToken() != other.getToken()) return false;
            if (this.getTime() != other.getTime()) return false;
            return true;
        }

        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        protected boolean canEqual(final java.lang.Object other) {
            return other instanceof QueryHandler.QuerySession;
        }

        @java.lang.Override
        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public int hashCode() {
            final int PRIME = 59;
            int result = 1;
            result = result * PRIME + this.getToken();
            final long $time = this.getTime();
            result = result * PRIME + (int) ($time >>> 32 ^ $time);
            return result;
        }

        @java.lang.Override
        @java.lang.SuppressWarnings("all")
        @lombok.Generated
        public java.lang.String toString() {
            return "QueryHandler.QuerySession(token=" + this.getToken() + ", time=" + this.getTime() + ")";
        }
    }

    @java.lang.SuppressWarnings("all")
    @lombok.Generated
    public QueryHandler(final ProxyServer bungee, final ListenerInfo listener) {
        this.bungee = bungee;
        this.listener = listener;
    }
}
