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.ClientCommonNetworkHandlerAccessor;
import land.chipmunk.chipmunkmod.mixin.MinecraftClientAccessor;
import land.chipmunk.chipmunkmod.util.RandomUtilities;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.TitleScreen;
import net.minecraft.client.gui.screen.multiplayer.ConnectScreen;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ServerAddress;
import net.minecraft.client.network.ServerInfo;
import net.minecraft.client.session.Session;
import net.minecraft.network.ClientConnection;
import net.minecraft.text.Text;
import net.minecraft.util.Uuids;

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(Text.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 Session ORIGINAL_SESSION = MinecraftClient.getInstance().getSession();
    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 Session offline (final String username) {
        // This is how Minecraft's Main class does it
        return new Session(username, Uuids.getOfflinePlayerUuid(username),
                           "", Optional.empty(), Optional.empty(), Session.AccountType.LEGACY);
    }

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

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

        final ClientConnection connection = networkHandler.getConnection();
        final Screen screen = Objects.requireNonNullElseGet(
                ((ClientCommonNetworkHandlerAccessor) 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.send(() -> {
            connection.disconnect(Text.translatable("disconnect.transfer"));
            connection.tryDisableAutoRead();
            connection.handleDisconnection();

            ConnectScreen.connect(screen, client, ServerAddress.parse(info.address), info, false, null);
        });
    }

    private static int changeSession (final CommandContext<FabricClientCommandSource> context, final Session session) {
        final MinecraftClient client = context.getSource().getClient();
        ((MinecraftClientAccessor) client).session(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));
    }
}
