package top.leisuretimedock.dglabgame.core.command;

import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.r3944realms.dg_lab.api.manager.Status;
import com.r3944realms.dg_lab.api.message.IPowerBoxMsg;
import com.r3944realms.dg_lab.api.message.argType.Channel;
import com.r3944realms.dg_lab.api.message.argType.ChangePolicy;
import com.r3944realms.dg_lab.api.message.data.PulseWaveListGenerator;
import com.r3944realms.dg_lab.api.websocket.message.MessageDirection;
import com.r3944realms.dg_lab.api.websocket.message.PowerBoxMessage;
import com.r3944realms.dg_lab.api.websocket.message.role.WebSocketClientRole;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
import top.leisuretimedock.dglabgame.config.CommonConfig;
import top.leisuretimedock.dglabgame.core.manager.ClientManager;
import top.leisuretimedock.dglabgame.core.manager.DGLabManagerHolder;
import top.leisuretimedock.dglabgame.core.manager.ServerManager;

import java.util.Optional;

public class DGLabCommands {
    private static final String CLIENT_ID = "dglabgame";

    public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
        dispatcher.register(
                Commands.literal("dglab")
                        .requires(source -> source.hasPermission(2))
                        .then(serverNode())
                        .then(clientNode())
                        .then(strengthNode())
                        .then(pulseNode())
                        .then(clearNode())
                        .then(feedbackNode())
        );
    }

    // ==================== Server Commands ====================

    private static com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> serverNode() {
        return Commands.literal("server")
                .then(Commands.literal("start")
                        .executes(ctx -> serverStart(ctx, CommonConfig.PORT.get()))
                        .then(Commands.argument("port", IntegerArgumentType.integer(1, 65535))
                                .executes(ctx -> serverStart(ctx, IntegerArgumentType.getInteger(ctx, "port")))
                        )
                )
                .then(Commands.literal("stop")
                        .executes(DGLabCommands::serverStop)
                )
                .then(Commands.literal("status")
                        .executes(DGLabCommands::serverStatus)
                );
    }

    private static int serverStart(CommandContext<CommandSourceStack> ctx, int port) {
        Optional<ServerManager> existing = DGLabManagerHolder.getServerManager();
        if (existing.isPresent() && existing.get().getStatus() != Status.STOPPED) {
            ctx.getSource().sendSuccess(
                    () -> Component.translatable("dglabgame.command.server.already_running"), false
            );
            return 0;
        }
        ServerManager mgr = DGLabManagerHolder.getOrCreateServerManager();
        mgr.start();
        ctx.getSource().sendSuccess(
                () -> Component.translatable("dglabgame.command.server.started", port), true
        );
        return 1;
    }

    private static int serverStop(CommandContext<CommandSourceStack> ctx) {
        Optional<ServerManager> existing = DGLabManagerHolder.getServerManager();
        if (existing.isEmpty() || existing.get().getStatus() == Status.STOPPED) {
            ctx.getSource().sendFailure(
                    Component.translatable("dglabgame.command.server.not_running")
            );
            return 0;
        }
        existing.get().stop();
        ctx.getSource().sendSuccess(
                () -> Component.translatable("dglabgame.command.server.stopped"), true
        );
        return 1;
    }

    private static int serverStatus(CommandContext<CommandSourceStack> ctx) {
        Optional<ServerManager> existing = DGLabManagerHolder.getServerManager();
        if (existing.isEmpty()) {
            ctx.getSource().sendSuccess(
                    () -> Component.translatable("dglabgame.command.server.not_created"), false
            );
        } else {
            ServerManager mgr = existing.get();
            Status status = mgr.getStatus();
            int clients = mgr.getConnectedClients().size();
            ctx.getSource().sendSuccess(
                    () -> Component.translatable("dglabgame.command.server.status", status.name(), clients), false
            );
        }
        return 1;
    }

    // ==================== Client Commands ====================

    private static com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> clientNode() {
        return Commands.literal("client")
                .then(Commands.literal("create")
                        .then(Commands.argument("name", StringArgumentType.word())
                                .executes(ctx -> clientCreate(ctx,
                                        StringArgumentType.getString(ctx, "name"),
                                        CommonConfig.ADDRESS.get(),
                                        CommonConfig.PORT.get()))
                                .then(Commands.argument("address", StringArgumentType.word())
                                        .executes(ctx -> clientCreate(ctx,
                                                StringArgumentType.getString(ctx, "name"),
                                                StringArgumentType.getString(ctx, "address"),
                                                CommonConfig.PORT.get()))
                                        .then(Commands.argument("port", IntegerArgumentType.integer(1, 65535))
                                                .executes(ctx -> clientCreate(ctx,
                                                        StringArgumentType.getString(ctx, "name"),
                                                        StringArgumentType.getString(ctx, "address"),
                                                        IntegerArgumentType.getInteger(ctx, "port")))
                                        )
                                )
                        )
                )
                .then(Commands.literal("remove")
                        .then(Commands.argument("name", StringArgumentType.word())
                                .executes(DGLabCommands::clientRemove)
                        )
                )
                .then(Commands.literal("start")
                        .then(Commands.argument("name", StringArgumentType.word())
                                .executes(DGLabCommands::clientStart)
                        )
                )
                .then(Commands.literal("stop")
                        .then(Commands.argument("name", StringArgumentType.word())
                                .executes(DGLabCommands::clientStop)
                        )
                )
                .then(Commands.literal("list")
                        .executes(DGLabCommands::clientList)
                );
    }

    private static int clientCreate(CommandContext<CommandSourceStack> ctx, String name, String address, int port) {
        ClientManager mgr = DGLabManagerHolder.getClientManager();
        WebSocketClientRole role = WebSocketClientRole.of(CLIENT_ID + "_" + name);
        mgr.createClient(name, role, address, port);
        ctx.getSource().sendSuccess(
                () -> Component.translatable("dglabgame.command.client.created", name, address, port), true
        );
        return 1;
    }

    private static int clientRemove(CommandContext<CommandSourceStack> ctx) {
        String name = StringArgumentType.getString(ctx, "name");
        ClientManager mgr = DGLabManagerHolder.getClientManager();
        if (mgr.getClient(name).isEmpty()) {
            ctx.getSource().sendFailure(
                    Component.translatable("dglabgame.command.client.not_found", name)
            );
            return 0;
        }
        mgr.removeClient(name);
        ctx.getSource().sendSuccess(
                () -> Component.translatable("dglabgame.command.client.removed", name), true
        );
        return 1;
    }

    private static int clientStart(CommandContext<CommandSourceStack> ctx) {
        String name = StringArgumentType.getString(ctx, "name");
        ClientManager mgr = DGLabManagerHolder.getClientManager();
        Optional<com.r3944realms.dg_lab.manager.DGPBClientManager> client = mgr.getClient(name);
        if (client.isEmpty()) {
            ctx.getSource().sendFailure(
                    Component.translatable("dglabgame.command.client.not_found", name)
            );
            return 0;
        }
        client.get().start();
        ctx.getSource().sendSuccess(
                () -> Component.translatable("dglabgame.command.client.started", name), true
        );
        return 1;
    }

    private static int clientStop(CommandContext<CommandSourceStack> ctx) {
        String name = StringArgumentType.getString(ctx, "name");
        ClientManager mgr = DGLabManagerHolder.getClientManager();
        Optional<com.r3944realms.dg_lab.manager.DGPBClientManager> client = mgr.getClient(name);
        if (client.isEmpty()) {
            ctx.getSource().sendFailure(
                    Component.translatable("dglabgame.command.client.not_found", name)
            );
            return 0;
        }
        client.get().stop();
        ctx.getSource().sendSuccess(
                () -> Component.translatable("dglabgame.command.client.stopped", name), true
        );
        return 1;
    }

    private static int clientList(CommandContext<CommandSourceStack> ctx) {
        ClientManager mgr = DGLabManagerHolder.getClientManager();
        var names = mgr.getClientNames();
        if (names.isEmpty()) {
            ctx.getSource().sendSuccess(
                    () -> Component.translatable("dglabgame.command.client.none"), false
            );
        } else {
            String list = String.join(", ", names);
            ctx.getSource().sendSuccess(
                    () -> Component.translatable("dglabgame.command.client.list", list), false
            );
        }
        return 1;
    }

    // ==================== Device Commands ====================

    private static com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> strengthNode() {
        return Commands.literal("strength")
                .then(Commands.argument("channel", StringArgumentType.word())
                        .then(Commands.argument("policy", StringArgumentType.word())
                                .then(Commands.argument("value", IntegerArgumentType.integer(0, 200))
                                        .executes(DGLabCommands::strengthExec)
                                )
                        )
                );
    }

    private static int strengthExec(CommandContext<CommandSourceStack> ctx) {
        Channel channel = parseChannel(StringArgumentType.getString(ctx, "channel"));
        if (channel == null) {
            ctx.getSource().sendFailure(Component.translatable("dglabgame.command.invalid_channel"));
            return 0;
        }
        ChangePolicy policy = parsePolicy(StringArgumentType.getString(ctx, "policy"));
        if (policy == null) {
            ctx.getSource().sendFailure(Component.translatable("dglabgame.command.invalid_policy"));
            return 0;
        }
        int value = IntegerArgumentType.getInteger(ctx, "value");

        IPowerBoxMsg.StrengthChange msg = new IPowerBoxMsg.StrengthChange(channel, policy, value);
        int sent = sendToAll(msg);
        if (sent == 0) {
            ctx.getSource().sendFailure(Component.translatable("dglabgame.command.no_output"));
            return 0;
        }
        ctx.getSource().sendSuccess(
                () -> Component.translatable("dglabgame.command.strength.sent", channel.name(), policy.name(), value, sent), true
        );
        return 1;
    }

    private static com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> pulseNode() {
        return Commands.literal("pulse")
                .then(Commands.argument("channel", StringArgumentType.word())
                        .then(Commands.argument("f1", IntegerArgumentType.integer(10, 240))
                                .then(Commands.argument("s1", IntegerArgumentType.integer(0, 100))
                                        .then(Commands.argument("f2", IntegerArgumentType.integer(10, 240))
                                                .then(Commands.argument("s2", IntegerArgumentType.integer(0, 100))
                                                        .then(Commands.argument("f3", IntegerArgumentType.integer(10, 240))
                                                                .then(Commands.argument("s3", IntegerArgumentType.integer(0, 100))
                                                                        .then(Commands.argument("f4", IntegerArgumentType.integer(10, 240))
                                                                                .then(Commands.argument("s4", IntegerArgumentType.integer(0, 100))
                                                                                        .executes(DGLabCommands::pulseExec)
                                                                                        .then(Commands.argument("timer", IntegerArgumentType.integer(0, 60000))
                                                                                                .executes(DGLabCommands::pulseExec)
                                                                                        )
                                                                                )
                                                                        )
                                                                )
                                                        )
                                                )
                                        )
                                )
                        )
                );
    }

    private static int pulseExec(CommandContext<CommandSourceStack> ctx) {
        Channel channel = parseChannel(StringArgumentType.getString(ctx, "channel"));
        if (channel == null) {
            ctx.getSource().sendFailure(Component.translatable("dglabgame.command.invalid_channel"));
            return 0;
        }
        int f1 = IntegerArgumentType.getInteger(ctx, "f1");
        int s1 = IntegerArgumentType.getInteger(ctx, "s1");
        int f2 = IntegerArgumentType.getInteger(ctx, "f2");
        int s2 = IntegerArgumentType.getInteger(ctx, "s2");
        int f3 = IntegerArgumentType.getInteger(ctx, "f3");
        int s3 = IntegerArgumentType.getInteger(ctx, "s3");
        int f4 = IntegerArgumentType.getInteger(ctx, "f4");
        int s4 = IntegerArgumentType.getInteger(ctx, "s4");

        var waveList = PulseWaveListGenerator.pulseWave(
                new int[]{f1, f2, f3, f4},
                new int[]{s1, s2, s3, s4}
        );

        Integer timer = null;
        try {
            timer = IntegerArgumentType.getInteger(ctx, "timer");
        } catch (IllegalArgumentException ignored) {
        }

        IPowerBoxMsg.Pulse msg = new IPowerBoxMsg.Pulse(channel, waveList, timer);
        int sent = sendToAll(msg);
        if (sent == 0) {
            ctx.getSource().sendFailure(Component.translatable("dglabgame.command.no_output"));
            return 0;
        }
        ctx.getSource().sendSuccess(
                () -> Component.translatable("dglabgame.command.pulse.sent", channel.name(), sent), true
        );
        return 1;
    }

    private static com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> clearNode() {
        return Commands.literal("clear")
                .then(Commands.argument("channel", StringArgumentType.word())
                        .executes(DGLabCommands::clearExec)
                );
    }

    private static int clearExec(CommandContext<CommandSourceStack> ctx) {
        Channel channel = parseChannel(StringArgumentType.getString(ctx, "channel"));
        if (channel == null) {
            ctx.getSource().sendFailure(Component.translatable("dglabgame.command.invalid_channel"));
            return 0;
        }

        IPowerBoxMsg.Clear msg = new IPowerBoxMsg.Clear(channel);
        int sent = sendToAll(msg);
        if (sent == 0) {
            ctx.getSource().sendFailure(Component.translatable("dglabgame.command.no_output"));
            return 0;
        }
        ctx.getSource().sendSuccess(
                () -> Component.translatable("dglabgame.command.clear.sent", channel.name(), sent), true
        );
        return 1;
    }

    private static com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> feedbackNode() {
        return Commands.literal("feedback")
                .then(Commands.argument("value", IntegerArgumentType.integer(0, 10))
                        .executes(DGLabCommands::feedbackExec)
                );
    }

    private static int feedbackExec(CommandContext<CommandSourceStack> ctx) {
        int value = IntegerArgumentType.getInteger(ctx, "value");

        IPowerBoxMsg.Feedback msg = new IPowerBoxMsg.Feedback(value);
        int sent = sendToAll(msg);
        if (sent == 0) {
            ctx.getSource().sendFailure(Component.translatable("dglabgame.command.no_output"));
            return 0;
        }
        ctx.getSource().sendSuccess(
                () -> Component.translatable("dglabgame.command.feedback.sent", value, sent), true
        );
        return 1;
    }

    // ==================== Send Helpers ====================

    private static int sendToAll(IPowerBoxMsg msg) {
        int sent = 0;
        Optional<ServerManager> serverMgr = DGLabManagerHolder.getServerManager();
        if (serverMgr.isPresent()) {
            ServerManager mgr = serverMgr.get();
            if (mgr.getStatus() == Status.RUNNING) {
                PowerBoxMessage pbm = msg.toPowerBoxMessage(CLIENT_ID, CLIENT_ID,
                        MessageDirection.DirectType.SERVER_TO_CLIENT);
                mgr.sendToAll(pbm);
                sent += mgr.getConnectedClients().size();
            }
        }
        return sent;
    }

    private static Channel parseChannel(String s) {
        return switch (s.toLowerCase()) {
            case "a" -> Channel.A;
            case "b" -> Channel.B;
            default -> null;
        };
    }

    private static ChangePolicy parsePolicy(String s) {
        return switch (s.toLowerCase()) {
            case "increase", "inc", "+" -> ChangePolicy.INCREASE;
            case "decrease", "dec", "-" -> ChangePolicy.DECREASE;
            case "goto", "set", "=" -> ChangePolicy.GOTO;
            default -> null;
        };
    }
}
