package land.chipmunk.chipmunkmod.commands;

import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import land.chipmunk.chipmunkmod.mixin.ClientCommonPacketListenerImplAccessor;
import land.chipmunk.chipmunkmod.mixin.MinecraftAccessor;
import land.chipmunk.chipmunkmod.util.RandomUtilities;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.Minecraft;
import net.minecraft.client.User;
import net.minecraft.client.gui.screens.ConnectScreen;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.multiplayer.resolver.ServerAddress;
import net.minecraft.core.UUIDUtil;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;

import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
import static land.chipmunk.chipmunkmod.command.CommandManager.argument;
import static land.chipmunk.chipmunkmod.command.CommandManager.literal;

public class UsernameCommand {
    private static final SimpleCommandExceptionType USERNAME_TOO_LONG = new SimpleCommandExceptionType(Component.translatable("The specified username is longer than 16 characters"));
    private static final char[] PREMIUM_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
            .toCharArray();
    private static final char SECTION_CHAR = '§';

    private static final User ORIGINAL_SESSION = Minecraft.getInstance().getUser();
    private static final Random RANDOM = new Random();

    public static void register (final CommandDispatcher<FabricClientCommandSource> dispatcher) {
        dispatcher.register(literal("username")
                                    .then(literal("set")
                                                  .then(argument("username", greedyString())
                                                                .executes(c -> changeOffline(c, getString(c, "username")))))
                                    .then(literal("revert")
                                                  .executes(c -> changeSession(c, ORIGINAL_SESSION)))

                                    .then(literal("random")
                                                  .then(literal("blank")
                                                                .executes(c -> changeOffline(c, RandomUtilities.emptyUsername(RANDOM, SECTION_CHAR))))
                                                  .executes(c -> changeOffline(c, RandomUtilities.randomString(RANDOM, PREMIUM_CHARS, RANDOM.nextInt(3, 16)))))
                                    .then(literal("empty")
                                                  .executes(c -> changeOffline(c, "")))
        );
    }

    private static User offline (final String username) {
        // This is how Minecraft's Main class does it
        return new User(username, UUIDUtil.createOfflinePlayerUUID(username),
                           "", Optional.empty(), Optional.empty());
    }

    // TODO: Put this in a separate class
    private static void reconnect (final Minecraft client) {
        final ClientPacketListener networkHandler = client.getConnection();
        if (networkHandler == null) return; // single-player?

        final ServerData info = networkHandler.getServerData();
        if (info == null) return; // definitely single-player

        final Connection connection = networkHandler.getConnection();
        final Screen screen = Objects.requireNonNullElseGet(
                ((ClientCommonPacketListenerImplAccessor) networkHandler).getPostDisconnectScreen(),
                TitleScreen::new);

        // This stuff needs to run after we close chat, otherwise it kicks us to the title screen. Don't ask me why
        client.schedule(() -> {
            connection.disconnect(Component.translatable("disconnect.transfer"));
            connection.setReadOnly();
            connection.handleDisconnection();

            ConnectScreen.startConnecting(screen, client, ServerAddress.parseString(info.ip), info, false, null);
        });
    }

    private static int changeSession (final CommandContext<FabricClientCommandSource> context, final User session) {
        final Minecraft client = context.getSource().getClient();
        ((MinecraftAccessor) client).user(session);

        reconnect(client);
        return Command.SINGLE_SUCCESS;
    }

    private static int changeOffline (final CommandContext<FabricClientCommandSource> context, final String username) throws CommandSyntaxException {
        if (username.length() > 16) throw USERNAME_TOO_LONG.create();
        return changeSession(context, offline(username));
    }
}
