package com.tiedup.remake.network.personality;

import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.EntityDialogueManager;
import com.tiedup.remake.dialogue.EntityDialogueManager.DialogueCategory;
import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.items.ItemCommandWand;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.network.PacketRateLimiter;
import com.tiedup.remake.personality.JobExperience;
import com.tiedup.remake.personality.NpcCommand;
import com.tiedup.remake.personality.NpcNeeds;
import com.tiedup.remake.personality.PersonalityState;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.UUID;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.network.NetworkEvent;
import org.jetbrains.annotations.Nullable;

/**
 * Unified C2S packet for all NPC command wand actions.
 * Replaces PacketGiveCommand, PacketCancelCommand, PacketSelectJobCommand,
 * PacketCycleFollowDistance, and PacketToggleAutoRest.
 */
public class PacketNpcCommand {

    private static final double MAX_COMMAND_RANGE = 32.0;

    private final UUID entityUUID;
    private final NpcCommandType type;

    // Optional fields depending on type
    private final String commandName; // GIVE_COMMAND, SELECT_JOB

    @Nullable
    private final BlockPos targetPos; // GIVE_COMMAND only

    private final boolean refreshScreen; // CYCLE_FOLLOW_DISTANCE, TOGGLE_AUTO_REST

    // --- Constructors for each command type ---

    public static PacketNpcCommand giveCommand(
        UUID entityUUID,
        NpcCommand command,
        @Nullable BlockPos targetPos
    ) {
        return new PacketNpcCommand(
            entityUUID,
            NpcCommandType.GIVE_COMMAND,
            command.name(),
            targetPos,
            false
        );
    }

    public static PacketNpcCommand cancelCommand(UUID entityUUID) {
        return new PacketNpcCommand(
            entityUUID,
            NpcCommandType.CANCEL_COMMAND,
            "",
            null,
            false
        );
    }

    public static PacketNpcCommand selectJob(UUID entityUUID, NpcCommand job) {
        return new PacketNpcCommand(
            entityUUID,
            NpcCommandType.SELECT_JOB,
            job.name(),
            null,
            false
        );
    }

    public static PacketNpcCommand cycleFollowDistance(
        UUID entityUUID,
        boolean refreshScreen
    ) {
        return new PacketNpcCommand(
            entityUUID,
            NpcCommandType.CYCLE_FOLLOW_DISTANCE,
            "",
            null,
            refreshScreen
        );
    }

    public static PacketNpcCommand toggleAutoRest(
        UUID entityUUID,
        boolean refreshScreen
    ) {
        return new PacketNpcCommand(
            entityUUID,
            NpcCommandType.TOGGLE_AUTO_REST,
            "",
            null,
            refreshScreen
        );
    }

    private PacketNpcCommand(
        UUID entityUUID,
        NpcCommandType type,
        String commandName,
        @Nullable BlockPos targetPos,
        boolean refreshScreen
    ) {
        this.entityUUID = entityUUID;
        this.type = type;
        this.commandName = commandName;
        this.targetPos = targetPos;
        this.refreshScreen = refreshScreen;
    }

    // --- Wire format ---

    public void encode(FriendlyByteBuf buf) {
        buf.writeUUID(entityUUID);
        buf.writeEnum(type);
        switch (type) {
            case GIVE_COMMAND -> {
                buf.writeUtf(commandName);
                buf.writeBoolean(targetPos != null);
                if (targetPos != null) {
                    buf.writeBlockPos(targetPos);
                }
            }
            case SELECT_JOB -> buf.writeUtf(commandName);
            case CYCLE_FOLLOW_DISTANCE, TOGGLE_AUTO_REST -> buf.writeBoolean(
                refreshScreen
            );
            case CANCEL_COMMAND -> {
            } // no extra data
        }
    }

    public static PacketNpcCommand decode(FriendlyByteBuf buf) {
        UUID uuid = buf.readUUID();
        NpcCommandType type = buf.readEnum(NpcCommandType.class);
        String cmd = "";
        BlockPos pos = null;
        boolean refresh = false;

        switch (type) {
            case GIVE_COMMAND -> {
                cmd = buf.readUtf(32);
                if (buf.readBoolean()) {
                    pos = buf.readBlockPos();
                }
            }
            case SELECT_JOB -> cmd = buf.readUtf(32);
            case CYCLE_FOLLOW_DISTANCE, TOGGLE_AUTO_REST -> refresh =
                buf.readBoolean();
            case CANCEL_COMMAND -> {
            }
        }

        return new PacketNpcCommand(uuid, type, cmd, pos, refresh);
    }

    public void handle(Supplier<NetworkEvent.Context> ctx) {
        ctx
            .get()
            .enqueueWork(() -> {
                ServerPlayer sender = ctx.get().getSender();
                if (sender == null) return;
                handleServer(sender);
            });
        ctx.get().setPacketHandled(true);
    }

    // --- Server handling ---

    private void handleServer(ServerPlayer sender) {
        if (!PacketRateLimiter.allowPacket(sender, "action")) return;

        Entity entity = (
            (net.minecraft.server.level.ServerLevel) sender.level()
        ).getEntity(entityUUID);
        if (!(entity instanceof EntityDamsel damsel)) {
            TiedUpMod.LOGGER.warn(
                "[PacketNpcCommand:{}] Entity {} not found or not a Damsel",
                type,
                entityUUID
            );
            return;
        }

        double distance = sender.distanceTo(damsel);
        if (distance > MAX_COMMAND_RANGE) {
            SystemMessageManager.sendToPlayer(
                sender,
                SystemMessageManager.MessageCategory.ERROR,
                "Target is too far away!"
            );
            return;
        }

        switch (type) {
            case GIVE_COMMAND -> handleGiveCommand(sender, damsel);
            case CANCEL_COMMAND -> handleCancelCommand(sender, damsel);
            case SELECT_JOB -> handleSelectJob(sender, damsel);
            case CYCLE_FOLLOW_DISTANCE -> handleCycleFollowDistance(
                sender,
                damsel
            );
            case TOGGLE_AUTO_REST -> handleToggleAutoRest(sender, damsel);
        }
    }

    private void handleGiveCommand(ServerPlayer sender, EntityDamsel damsel) {
        NpcCommand command = NpcCommand.fromString(commandName);
        if (command == NpcCommand.NONE && !"NONE".equals(commandName)) {
            TiedUpMod.LOGGER.warn(
                "[PacketNpcCommand:GIVE] Unknown command: {}",
                commandName
            );
            return;
        }

        boolean success = damsel.giveCommand(sender, command, targetPos);
        String npcName = damsel.getNpcName();
        if (success) {
            EntityDialogueManager.talkTo(
                damsel,
                sender,
                DialogueCategory.COMMAND_ACCEPT
            );
            SystemMessageManager.sendToPlayer(
                sender,
                SystemMessageManager.MessageCategory.INFO,
                npcName + " accepted command: " + command.name()
            );
            TiedUpMod.LOGGER.debug(
                "[PacketNpcCommand:GIVE] {} gave command {} to {}",
                sender.getName().getString(),
                command.name(),
                npcName
            );
        }
    }

    private void handleCancelCommand(ServerPlayer sender, EntityDamsel damsel) {
        if (!validateCollarOwnership(sender, damsel)) return;

        damsel.cancelCommand();
        String npcName = damsel.getNpcName();
        SystemMessageManager.sendToPlayer(
            sender,
            SystemMessageManager.MessageCategory.INFO,
            npcName + "'s command cancelled."
        );
        TiedUpMod.LOGGER.debug(
            "[PacketNpcCommand:CANCEL] {} cancelled command for {}",
            sender.getName().getString(),
            npcName
        );
    }

    private void handleSelectJob(ServerPlayer sender, EntityDamsel damsel) {
        NpcCommand job = NpcCommand.fromString(commandName);
        if (!job.isWorkCommand()) {
            TiedUpMod.LOGGER.warn(
                "[PacketNpcCommand:JOB] {} is not a work command",
                commandName
            );
            return;
        }

        PersonalityState state = damsel.getPersonalityState();
        if (state == null) return;

        String npcName = damsel.getNpcName();
        if (state.willObeyCommand(sender, job)) {
            ItemStack mainHand = sender.getItemInHand(
                InteractionHand.MAIN_HAND
            );
            ItemStack offHand = sender.getItemInHand(InteractionHand.OFF_HAND);

            ItemStack wand = null;
            if (mainHand.getItem() == ModItems.COMMAND_WAND.get()) {
                wand = mainHand;
            } else if (offHand.getItem() == ModItems.COMMAND_WAND.get()) {
                wand = offHand;
            }

            if (wand != null) {
                ItemCommandWand.enterSelectionMode(wand, damsel.getUUID(), job);
                EntityDialogueManager.talkTo(
                    damsel,
                    sender,
                    DialogueCategory.COMMAND_ACCEPT
                );
                SystemMessageManager.sendToPlayer(
                    sender,
                    SystemMessageManager.MessageCategory.INFO,
                    npcName +
                        " is ready to " +
                        job.name() +
                        ". Click a chest to set work zone!"
                );
                TiedUpMod.LOGGER.debug(
                    "[PacketNpcCommand:JOB] {} accepted job {} - wand in selection mode",
                    npcName,
                    job.name()
                );
            }
        }
    }

    private void handleCycleFollowDistance(
        ServerPlayer sender,
        EntityDamsel damsel
    ) {
        if (!validateCollarOwnership(sender, damsel)) return;

        PersonalityState state = damsel.getPersonalityState();
        if (state == null) return;

        NpcCommand.FollowDistance current = state.getFollowDistance();
        NpcCommand.FollowDistance next = switch (current) {
            case FAR -> NpcCommand.FollowDistance.CLOSE;
            case CLOSE -> NpcCommand.FollowDistance.HEEL;
            case HEEL -> NpcCommand.FollowDistance.FAR;
        };
        state.setFollowDistance(next);

        TiedUpMod.LOGGER.debug(
            "[PacketNpcCommand:FOLLOW] {} changed {} follow distance to {}",
            sender.getName().getString(),
            damsel.getNpcName(),
            next.name()
        );

        if (refreshScreen) {
            sendRefreshedScreen(sender, damsel, state);
        }
    }

    private void handleToggleAutoRest(
        ServerPlayer sender,
        EntityDamsel damsel
    ) {
        if (!validateCollarOwnership(sender, damsel)) return;

        PersonalityState state = damsel.getPersonalityState();
        if (state == null) return;

        boolean newState = state.toggleAutoRest();
        TiedUpMod.LOGGER.debug(
            "[PacketNpcCommand:REST] {} toggled {} auto-rest to {}",
            sender.getName().getString(),
            damsel.getNpcName(),
            newState ? "ON" : "OFF"
        );
        SystemMessageManager.sendToPlayer(
            sender,
            SystemMessageManager.MessageCategory.INFO,
            "Auto-Rest: " + (newState ? "ON" : "OFF")
        );

        if (refreshScreen) {
            sendRefreshedScreen(sender, damsel, state);
        }
    }

    // --- Shared helpers ---

    private boolean validateCollarOwnership(
        ServerPlayer sender,
        EntityDamsel damsel
    ) {
        if (!damsel.hasCollar()) {
            SystemMessageManager.sendToPlayer(
                sender,
                SystemMessageManager.MessageCategory.ERROR,
                damsel.getNpcName() + " is not wearing a collar!"
            );
            return false;
        }

        ItemStack collar = damsel.getEquipment(BodyRegionV2.NECK);
        if (!CollarHelper.isCollar(collar)) {
            return false;
        }

        if (!CollarHelper.isOwner(collar, sender)) {
            SystemMessageManager.sendToPlayer(
                sender,
                SystemMessageManager.MessageCategory.ERROR,
                "You don't own " + damsel.getNpcName() + "'s collar!"
            );
            return false;
        }

        return true;
    }

    private static void sendRefreshedScreen(
        ServerPlayer sender,
        EntityDamsel damsel,
        PersonalityState state
    ) {
        NpcNeeds needs = state.getNeeds();
        String homeType = state.getHomeType().name();

        JobExperience jobExp = state.getJobExperience();
        NpcCommand activeCmd = state.getActiveCommand();
        String activeJobLevelName = "";
        int activeJobXp = 0;
        int activeJobXpMax = 10;
        if (activeCmd.isActiveJob()) {
            JobExperience.JobLevel level = jobExp.getJobLevel(activeCmd);
            activeJobLevelName = level.name();
            activeJobXp = jobExp.getExperience(activeCmd);
            activeJobXpMax = level.maxExp;
        }

        ModNetwork.sendToPlayer(
            new PacketOpenCommandWandScreen(
                damsel.getUUID(),
                damsel.getNpcName(),
                state.getPersonality().name(),
                activeCmd.name(),
                needs.getHunger(),
                needs.getRest(),
                state.getMood(),
                state.getFollowDistance().name(),
                homeType,
                state.isAutoRestEnabled(),
                "",
                "",
                activeJobLevelName,
                activeJobXp,
                activeJobXpMax
            ),
            sender
        );
    }
}
