package com.tiedup.remake.commands;

import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.entities.*;
import com.tiedup.remake.entities.skins.EliteKidnapperSkinManager;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.List;
import java.util.Optional;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.AABB;

/**
 * NPC management commands.
 *
 * Commands:
 *   /npc spawn kidnapper [player] - Spawn a kidnapper at location
 *   /npc spawn elite <name> [player] - Spawn an elite kidnapper
 *   /npc spawn archer [player] - Spawn an archer kidnapper
 *   /npc spawn damsel [player] - Spawn a damsel NPC
 *   /npc kill [radius] - Kill all mod NPCs in radius
 *   /npc tie - Tie the nearest NPC
 *   /npc gag - Gag the nearest NPC
 *   /npc blindfold - Blindfold the nearest NPC
 *   /npc collar - Collar the nearest NPC
 *   /npc untie - Untie the nearest NPC (remove all)
 *   /npc state - Show state of nearest NPC
 *   /npc full - Apply full bondage to nearest NPC
 *
 * All commands require OP level 2.
 */
public class NPCCommand {

    public static void register(
        CommandDispatcher<CommandSourceStack> dispatcher
    ) {
        dispatcher.register(createNPCCommand());
    }

    /**
     * Create the NPC command builder (for use as subcommand of /tiedup).
     * @return The command builder
     */
    public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
        CommandSourceStack
    > createNPCCommand() {
        return Commands.literal("npc")
            .requires(CommandHelper.REQUIRES_OP)
            .then(
                Commands.literal("spawn")
                    // /npc spawn kidnapper [player]
                    .then(
                        Commands.literal("kidnapper")
                            .executes(ctx -> spawnKidnapper(ctx, null))
                            .then(
                                Commands.argument(
                                    "player",
                                    EntityArgument.player()
                                ).executes(ctx ->
                                    spawnKidnapper(
                                        ctx,
                                        EntityArgument.getPlayer(ctx, "player")
                                    )
                                )
                            )
                    )
                    // /npc spawn elite <name> [player]
                    .then(
                        Commands.literal("elite").then(
                            Commands.argument("name", StringArgumentType.word())
                                .suggests((ctx, builder) -> {
                                    for (KidnapperVariant v : EliteKidnapperSkinManager.CORE.getAllVariants()) {
                                        builder.suggest(v.id());
                                    }
                                    return builder.buildFuture();
                                })
                                .executes(ctx -> spawnElite(ctx, null))
                                .then(
                                    Commands.argument(
                                        "player",
                                        EntityArgument.player()
                                    ).executes(ctx ->
                                        spawnElite(
                                            ctx,
                                            EntityArgument.getPlayer(
                                                ctx,
                                                "player"
                                            )
                                        )
                                    )
                                )
                        )
                    )
                    // /npc spawn archer [player]
                    .then(
                        Commands.literal("archer")
                            .executes(ctx -> spawnArcher(ctx, null))
                            .then(
                                Commands.argument(
                                    "player",
                                    EntityArgument.player()
                                ).executes(ctx ->
                                    spawnArcher(
                                        ctx,
                                        EntityArgument.getPlayer(ctx, "player")
                                    )
                                )
                            )
                    )
                    // /npc spawn damsel [player]
                    .then(
                        Commands.literal("damsel")
                            .executes(ctx -> spawnDamsel(ctx, null))
                            .then(
                                Commands.argument(
                                    "player",
                                    EntityArgument.player()
                                ).executes(ctx ->
                                    spawnDamsel(
                                        ctx,
                                        EntityArgument.getPlayer(ctx, "player")
                                    )
                                )
                            )
                    )
            )
            // /npc kill <radius> - Kill all mod NPCs in radius
            .then(
                Commands.literal("kill")
                    .executes(ctx -> killNPCs(ctx, 10))
                    .then(
                        Commands.argument(
                            "radius",
                            com.mojang.brigadier.arguments.IntegerArgumentType.integer(
                                1,
                                100
                            )
                        ).executes(ctx ->
                            killNPCs(
                                ctx,
                                com.mojang.brigadier.arguments.IntegerArgumentType.getInteger(
                                    ctx,
                                    "radius"
                                )
                            )
                        )
                    )
            )
            // /npc tie - Tie nearest NPC
            .then(Commands.literal("tie").executes(NPCCommand::tieNPC))
            // /npc gag - Gag nearest NPC
            .then(Commands.literal("gag").executes(NPCCommand::gagNPC))
            // /npc blindfold - Blindfold nearest NPC
            .then(
                Commands.literal("blindfold").executes(NPCCommand::blindfoldNPC)
            )
            // /npc collar - Collar nearest NPC
            .then(Commands.literal("collar").executes(NPCCommand::collarNPC))
            // /npc untie - Untie nearest NPC
            .then(Commands.literal("untie").executes(NPCCommand::untieNPC))
            // /npc state - Show NPC state
            .then(Commands.literal("state").executes(NPCCommand::showNPCState))
            // /npc full - Full bondage on nearest NPC
            .then(
                Commands.literal("full").executes(NPCCommand::fullBondageNPC)
            );
    }

    private static int spawnKidnapper(
        CommandContext<CommandSourceStack> context,
        ServerPlayer targetPlayer
    ) throws CommandSyntaxException {
        CommandSourceStack source = context.getSource();
        ServerLevel level = source.getLevel();

        // Get spawn location
        double x, y, z;
        if (targetPlayer != null) {
            x = targetPlayer.getX();
            y = targetPlayer.getY();
            z = targetPlayer.getZ();
        } else if (source.getEntity() instanceof ServerPlayer player) {
            x = player.getX();
            y = player.getY();
            z = player.getZ();
        } else {
            source.sendFailure(
                Component.translatable("command.tiedup.error.must_be_player")
            );
            return 0;
        }

        // Spawn the kidnapper
        EntityKidnapper kidnapper = ModEntities.KIDNAPPER.get().create(level);
        if (kidnapper != null) {
            kidnapper.moveTo(x, y, z, level.random.nextFloat() * 360F, 0.0F);
            kidnapper.finalizeSpawn(
                level,
                level.getCurrentDifficultyAt(kidnapper.blockPosition()),
                MobSpawnType.COMMAND,
                null,
                null
            );
            level.addFreshEntity(kidnapper);

            source.sendSuccess(
                () ->
                    Component.translatable(
                        "command.tiedup.npc.spawned_kidnapper", formatPos(x, y, z)
                    ).withStyle(ChatFormatting.GREEN),
                true
            );
            return 1;
        }

        source.sendFailure(Component.translatable("command.tiedup.npc.spawn_failed_kidnapper"));
        return 0;
    }

    private static int spawnElite(
        CommandContext<CommandSourceStack> context,
        ServerPlayer targetPlayer
    ) throws CommandSyntaxException {
        CommandSourceStack source = context.getSource();
        ServerLevel level = source.getLevel();
        String name = StringArgumentType.getString(context, "name");

        // Parse variant
        KidnapperVariant variant = EliteKidnapperSkinManager.CORE.getVariant(
            name
        );
        if (variant == null) {
            source.sendFailure(
                Component.translatable("command.tiedup.npc.unknown_variant", name)
            );
            return 0;
        }

        // Get spawn location
        double x, y, z;
        if (targetPlayer != null) {
            x = targetPlayer.getX();
            y = targetPlayer.getY();
            z = targetPlayer.getZ();
        } else if (source.getEntity() instanceof ServerPlayer player) {
            x = player.getX();
            y = player.getY();
            z = player.getZ();
        } else {
            source.sendFailure(
                Component.translatable("command.tiedup.error.must_be_player")
            );
            return 0;
        }

        // Spawn the elite kidnapper
        EntityKidnapperElite elite = ModEntities.KIDNAPPER_ELITE.get().create(
            level
        );
        if (elite != null) {
            elite.moveTo(x, y, z, level.random.nextFloat() * 360F, 0.0F);
            elite.setKidnapperVariant(variant);
            elite.finalizeSpawn(
                level,
                level.getCurrentDifficultyAt(elite.blockPosition()),
                MobSpawnType.COMMAND,
                null,
                null
            );
            level.addFreshEntity(elite);

            source.sendSuccess(
                () ->
                    Component.translatable(
                        "command.tiedup.npc.spawned_elite", variant.defaultName(), formatPos(x, y, z)
                    ).withStyle(ChatFormatting.GREEN),
                true
            );
            return 1;
        }

        source.sendFailure(
            Component.translatable("command.tiedup.npc.spawn_failed_elite")
        );
        return 0;
    }

    private static int spawnArcher(
        CommandContext<CommandSourceStack> context,
        ServerPlayer targetPlayer
    ) throws CommandSyntaxException {
        CommandSourceStack source = context.getSource();
        ServerLevel level = source.getLevel();

        // Get spawn location
        double x, y, z;
        if (targetPlayer != null) {
            x = targetPlayer.getX();
            y = targetPlayer.getY();
            z = targetPlayer.getZ();
        } else if (source.getEntity() instanceof ServerPlayer player) {
            x = player.getX();
            y = player.getY();
            z = player.getZ();
        } else {
            source.sendFailure(
                Component.translatable("command.tiedup.error.must_be_player")
            );
            return 0;
        }

        // Spawn the archer
        EntityKidnapperArcher archer =
            ModEntities.KIDNAPPER_ARCHER.get().create(level);
        if (archer != null) {
            archer.moveTo(x, y, z, level.random.nextFloat() * 360F, 0.0F);
            archer.finalizeSpawn(
                level,
                level.getCurrentDifficultyAt(archer.blockPosition()),
                MobSpawnType.COMMAND,
                null,
                null
            );
            level.addFreshEntity(archer);

            source.sendSuccess(
                () ->
                    Component.translatable(
                        "command.tiedup.npc.spawned_archer", formatPos(x, y, z)
                    ).withStyle(ChatFormatting.GREEN),
                true
            );
            return 1;
        }

        source.sendFailure(
            Component.translatable("command.tiedup.npc.spawn_failed_archer")
        );
        return 0;
    }

    private static int spawnDamsel(
        CommandContext<CommandSourceStack> context,
        ServerPlayer targetPlayer
    ) throws CommandSyntaxException {
        CommandSourceStack source = context.getSource();
        ServerLevel level = source.getLevel();

        // Get spawn location
        double x, y, z;
        if (targetPlayer != null) {
            x = targetPlayer.getX();
            y = targetPlayer.getY();
            z = targetPlayer.getZ();
        } else if (source.getEntity() instanceof ServerPlayer player) {
            x = player.getX();
            y = player.getY();
            z = player.getZ();
        } else {
            source.sendFailure(
                Component.translatable("command.tiedup.error.must_be_player")
            );
            return 0;
        }

        // Spawn the damsel
        EntityDamsel damsel = ModEntities.DAMSEL.get().create(level);
        if (damsel != null) {
            damsel.moveTo(x, y, z, level.random.nextFloat() * 360F, 0.0F);
            damsel.finalizeSpawn(
                level,
                level.getCurrentDifficultyAt(damsel.blockPosition()),
                MobSpawnType.COMMAND,
                null,
                null
            );
            level.addFreshEntity(damsel);

            source.sendSuccess(
                () ->
                    Component.translatable(
                        "command.tiedup.npc.spawned_damsel", formatPos(x, y, z)
                    ).withStyle(ChatFormatting.GREEN),
                true
            );
            return 1;
        }

        source.sendFailure(Component.translatable("command.tiedup.npc.spawn_failed_damsel"));
        return 0;
    }

    private static int killNPCs(
        CommandContext<CommandSourceStack> context,
        int radius
    ) throws CommandSyntaxException {
        CommandSourceStack source = context.getSource();

        Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
            source
        );
        if (playerOpt.isEmpty()) return 0;
        ServerPlayer player = playerOpt.get();

        ServerLevel level = player.serverLevel();
        int killed = 0;

        // Find and kill all mod NPCs in radius
        var entities = level.getEntitiesOfClass(
            net.minecraft.world.entity.LivingEntity.class,
            player.getBoundingBox().inflate(radius),
            e -> e instanceof com.tiedup.remake.entities.AbstractTiedUpNpc
        );

        for (var entity : entities) {
            entity.discard();
            killed++;
        }

        int finalKilled = killed;
        source.sendSuccess(
            () ->
                Component.translatable(
                    "command.tiedup.npc.killed", finalKilled, radius
                ).withStyle(ChatFormatting.GREEN),
            true
        );

        return killed;
    }

    private static String formatPos(double x, double y, double z) {
        return String.format("(%.1f, %.1f, %.1f)", x, y, z);
    }

    // NPC Bondage Commands (from DamselTestCommand)

    /**
     * Find the nearest mod NPC (Damsel or Kidnapper) within 10 blocks.
     */
    private static IBondageState findNearestNPC(
        CommandContext<CommandSourceStack> context
    ) {
        Entity source = context.getSource().getEntity();
        if (source == null) return null;

        AABB searchBox = source.getBoundingBox().inflate(10.0);

        // Search for any IBondageState entity (EntityDamsel implements this)
        List<EntityDamsel> npcs = source
            .level()
            .getEntitiesOfClass(EntityDamsel.class, searchBox);

        if (npcs.isEmpty()) {
            return null;
        }

        // Return closest NPC
        return npcs
            .stream()
            .min((a, b) ->
                Double.compare(a.distanceToSqr(source), b.distanceToSqr(source))
            )
            .orElse(null);
    }

    private static int tieNPC(CommandContext<CommandSourceStack> context) {
        IBondageState npc = findNearestNPC(context);
        if (npc == null) {
            context
                .getSource()
                .sendFailure(
                    Component.translatable("command.tiedup.npc.no_npc_nearby")
                );
            return 0;
        }

        if (npc.isTiedUp()) {
            context
                .getSource()
                .sendFailure(Component.translatable("command.tiedup.npc.already_tied"));
            return 0;
        }

        npc.equip(
            BodyRegionV2.ARMS,
            DataDrivenBondageItem.createStack(ResourceLocation.fromNamespaceAndPath("tiedup", "ropes"))
        );
        context
            .getSource()
            .sendSuccess(
                () -> Component.translatable("command.tiedup.npc.tied", npc.getKidnappedName()).withStyle(ChatFormatting.GREEN),
                true
            );
        return 1;
    }

    private static int gagNPC(CommandContext<CommandSourceStack> context) {
        IBondageState npc = findNearestNPC(context);
        if (npc == null) {
            context
                .getSource()
                .sendFailure(
                    Component.translatable("command.tiedup.npc.no_npc_nearby")
                );
            return 0;
        }

        if (npc.isGagged()) {
            context
                .getSource()
                .sendFailure(Component.translatable("command.tiedup.npc.already_gagged"));
            return 0;
        }

        npc.equip(
            BodyRegionV2.MOUTH,
            DataDrivenBondageItem.createStack(ResourceLocation.fromNamespaceAndPath("tiedup", "cloth_gag"))
        );
        context
            .getSource()
            .sendSuccess(
                () -> Component.translatable("command.tiedup.npc.gagged", npc.getKidnappedName()).withStyle(ChatFormatting.GREEN),
                true
            );
        return 1;
    }

    private static int blindfoldNPC(
        CommandContext<CommandSourceStack> context
    ) {
        IBondageState npc = findNearestNPC(context);
        if (npc == null) {
            context
                .getSource()
                .sendFailure(
                    Component.translatable("command.tiedup.npc.no_npc_nearby")
                );
            return 0;
        }

        if (npc.isBlindfolded()) {
            context
                .getSource()
                .sendFailure(Component.translatable("command.tiedup.npc.already_blindfolded"));
            return 0;
        }

        npc.equip(
            BodyRegionV2.EYES,
            DataDrivenBondageItem.createStack(ResourceLocation.fromNamespaceAndPath("tiedup", "classic_blindfold"))
        );
        context
            .getSource()
            .sendSuccess(
                () ->
                    Component.translatable("command.tiedup.npc.blindfolded", npc.getKidnappedName()
                    ).withStyle(ChatFormatting.GREEN),
                true
            );
        return 1;
    }

    private static int collarNPC(CommandContext<CommandSourceStack> context) {
        IBondageState npc = findNearestNPC(context);
        if (npc == null) {
            context
                .getSource()
                .sendFailure(
                    Component.translatable("command.tiedup.npc.no_npc_nearby")
                );
            return 0;
        }

        if (npc.hasCollar()) {
            context
                .getSource()
                .sendFailure(Component.translatable("command.tiedup.npc.already_collared"));
            return 0;
        }

        npc.equip(
            BodyRegionV2.NECK,
            DataDrivenBondageItem.createStack(ResourceLocation.fromNamespaceAndPath("tiedup", "classic_collar"))
        );
        context
            .getSource()
            .sendSuccess(
                () -> Component.translatable("command.tiedup.npc.collared", npc.getKidnappedName()).withStyle(ChatFormatting.GREEN),
                true
            );
        return 1;
    }

    private static int untieNPC(CommandContext<CommandSourceStack> context) {
        IBondageState npc = findNearestNPC(context);
        if (npc == null) {
            context
                .getSource()
                .sendFailure(
                    Component.translatable("command.tiedup.npc.no_npc_nearby")
                );
            return 0;
        }

        npc.untie(true); // Drop items
        context
            .getSource()
            .sendSuccess(
                () -> Component.translatable("command.tiedup.npc.untied", npc.getKidnappedName()).withStyle(ChatFormatting.GREEN),
                true
            );
        return 1;
    }

    private static int fullBondageNPC(
        CommandContext<CommandSourceStack> context
    ) {
        IBondageState npc = findNearestNPC(context);
        if (npc == null) {
            context
                .getSource()
                .sendFailure(
                    Component.translatable("command.tiedup.npc.no_npc_nearby")
                );
            return 0;
        }

        // Apply full bondage using AbstractTiedUpNpc method
        if (
            npc instanceof
                com.tiedup.remake.entities.AbstractTiedUpNpc npcEntity
        ) {
            npcEntity.applyBondage(
                DataDrivenBondageItem.createStack(ResourceLocation.fromNamespaceAndPath("tiedup", "ropes")),
                DataDrivenBondageItem.createStack(ResourceLocation.fromNamespaceAndPath("tiedup", "cloth_gag")),
                DataDrivenBondageItem.createStack(ResourceLocation.fromNamespaceAndPath("tiedup", "classic_blindfold")),
                DataDrivenBondageItem.createStack(ResourceLocation.fromNamespaceAndPath("tiedup", "classic_earplugs")),
                DataDrivenBondageItem.createStack(ResourceLocation.fromNamespaceAndPath("tiedup", "classic_collar")),
                ItemStack.EMPTY // No clothes
            );
        }

        context
            .getSource()
            .sendSuccess(
                () ->
                    Component.translatable(
                        "command.tiedup.npc.fully_restrained", npc.getKidnappedName()
                    ).withStyle(ChatFormatting.GREEN),
                true
            );
        return 1;
    }

    private static int showNPCState(
        CommandContext<CommandSourceStack> context
    ) {
        IBondageState npc = findNearestNPC(context);
        if (npc == null) {
            context
                .getSource()
                .sendFailure(
                    Component.translatable("command.tiedup.npc.no_npc_nearby")
                );
            return 0;
        }

        CommandSourceStack source = context.getSource();

        source.sendSuccess(
            () ->
                Component.translatable(
                    "command.tiedup.npc.state_header", npc.getKidnappedName()
                ).withStyle(ChatFormatting.GOLD),
            false
        );

        if (npc instanceof EntityDamsel damsel) {
            source.sendSuccess(
                () ->
                    Component.translatable("command.tiedup.npc.state_variant", damsel.getVariantId()).withStyle(ChatFormatting.YELLOW),
                false
            );
            source.sendSuccess(
                () ->
                    Component.translatable("command.tiedup.npc.state_slim_arms",
                        Component.translatable(damsel.hasSlimArms() ? "command.tiedup.yes" : "command.tiedup.no")
                    ).withStyle(ChatFormatting.YELLOW),
                false
            );
        }

        source.sendSuccess(
            () ->
                Component.translatable("command.tiedup.npc.state_tied",
                    Component.translatable(npc.isTiedUp() ? "command.tiedup.yes" : "command.tiedup.no")
                ).withStyle(ChatFormatting.YELLOW),
            false
        );
        source.sendSuccess(
            () ->
                Component.translatable("command.tiedup.npc.state_gagged",
                    Component.translatable(npc.isGagged() ? "command.tiedup.yes" : "command.tiedup.no")
                ).withStyle(ChatFormatting.YELLOW),
            false
        );
        source.sendSuccess(
            () ->
                Component.translatable("command.tiedup.npc.state_blindfolded",
                    Component.translatable(npc.isBlindfolded() ? "command.tiedup.yes" : "command.tiedup.no")
                ).withStyle(ChatFormatting.YELLOW),
            false
        );
        source.sendSuccess(
            () ->
                Component.translatable("command.tiedup.npc.state_collar",
                    Component.translatable(npc.hasCollar() ? "command.tiedup.yes" : "command.tiedup.no")
                ).withStyle(ChatFormatting.YELLOW),
            false
        );
        source.sendSuccess(
            () ->
                Component.translatable("command.tiedup.npc.state_earplugs",
                    Component.translatable(npc.hasEarplugs() ? "command.tiedup.yes" : "command.tiedup.no")
                ).withStyle(ChatFormatting.YELLOW),
            false
        );
        source.sendSuccess(
            () ->
                Component.translatable("command.tiedup.npc.state_captive",
                    Component.translatable(npc.isCaptive() ? "command.tiedup.yes" : "command.tiedup.no")
                ).withStyle(ChatFormatting.YELLOW),
            false
        );

        return 1;
    }
}
