package com.tiedup.remake.v2.furniture.network;

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.network.PacketRateLimiter;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.furniture.EntityFurniture;
import com.tiedup.remake.v2.furniture.FurnitureDefinition;
import com.tiedup.remake.v2.furniture.FurnitureFeedback;
import com.tiedup.remake.v2.furniture.ISeatProvider;
import com.tiedup.remake.v2.furniture.SeatDefinition;
import java.util.UUID;
import java.util.function.Supplier;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.AABB;
import net.minecraftforge.network.NetworkEvent;

/**
 * Client-to-server packet: master forces a captive onto a furniture seat.
 *
 * <p>The sender must own the captive's collar (verified via
 * {@link CollarHelper#isOwner(ItemStack, net.minecraft.world.entity.player.Player)}),
 * the captive must be alive and within 5 blocks of both sender and furniture,
 * and the furniture must have an available seat.</p>
 *
 * <p>Wire format: int furnitureEntityId (4) + UUID captiveUUID (16)</p>
 *
 * <p>Direction: Client to Server (C2S)</p>
 */
public class PacketFurnitureForcemount {

    private final int furnitureEntityId;
    private final UUID captiveUUID;

    public PacketFurnitureForcemount(int furnitureEntityId, UUID captiveUUID) {
        this.furnitureEntityId = furnitureEntityId;
        this.captiveUUID = captiveUUID;
    }

    // ==================== Codec ====================

    public static void encode(
        PacketFurnitureForcemount msg,
        FriendlyByteBuf buf
    ) {
        buf.writeInt(msg.furnitureEntityId);
        buf.writeUUID(msg.captiveUUID);
    }

    public static PacketFurnitureForcemount decode(FriendlyByteBuf buf) {
        return new PacketFurnitureForcemount(buf.readInt(), buf.readUUID());
    }

    // ==================== Handler ====================

    public static void handle(
        PacketFurnitureForcemount msg,
        Supplier<NetworkEvent.Context> ctxSupplier
    ) {
        NetworkEvent.Context ctx = ctxSupplier.get();
        ctx.enqueueWork(() -> handleOnServer(msg, ctx));
        ctx.setPacketHandled(true);
    }

    private static void handleOnServer(
        PacketFurnitureForcemount msg,
        NetworkEvent.Context ctx
    ) {
        ServerPlayer sender = ctx.getSender();
        if (sender == null) return;

        // Rate limit: prevent force-mount spam
        if (!PacketRateLimiter.allowPacket(sender, "action")) return;

        // Resolve the furniture entity
        Entity entity = sender.level().getEntity(msg.furnitureEntityId);
        if (entity == null) return;
        if (!(entity instanceof ISeatProvider provider)) return;
        if (sender.distanceTo(entity) > 5.0) return;
        if (!entity.isAlive() || entity.isRemoved()) return;

        // Look up captive by UUID in the sender's level
        LivingEntity captive = findCaptiveByUUID(sender, msg.captiveUUID);
        if (captive == null) {
            TiedUpMod.LOGGER.debug(
                "[PacketFurnitureForcemount] Captive not found: {}",
                msg.captiveUUID
            );
            return;
        }

        // Unified authorization: range, liveness, collar ownership, leashed
        // status, not-already-passenger. Shared with EntityFurniture.interact()
        // via FurnitureAuthPredicate to prevent security drift between paths.
        if (
            !com.tiedup.remake.v2.furniture.FurnitureAuthPredicate.canForceMount(
                sender,
                entity,
                captive
            )
        ) {
            return;
        }

        // Find the first available (unoccupied) seat
        String availableSeatId = findFirstAvailableSeat(provider, entity);
        if (availableSeatId == null) {
            TiedUpMod.LOGGER.debug(
                "[PacketFurnitureForcemount] No available seat on furniture entity {}",
                msg.furnitureEntityId
            );
            return;
        }

        // Force-mount: startRiding triggers EntityFurniture.addPassenger which
        // assigns the first available seat automatically. We use force=true to
        // bypass any canRide checks on the captive.
        boolean success = captive.startRiding(entity, true);

        if (success) {
            TiedUpMod.LOGGER.debug(
                "[PacketFurnitureForcemount] {} force-mounted {} onto furniture {}",
                sender.getName().getString(),
                captive.getName().getString(),
                msg.furnitureEntityId
            );

            // Play mount sound from FurnitureFeedback
            if (entity instanceof EntityFurniture furniture) {
                FurnitureDefinition def = furniture.getDefinition();
                if (def != null) {
                    FurnitureFeedback feedback = def.feedback();
                    ResourceLocation mountSoundRL = feedback.mountSound();
                    if (mountSoundRL != null) {
                        SoundEvent sound = SoundEvent.createVariableRangeEvent(
                            mountSoundRL
                        );
                        entity
                            .level()
                            .playSound(
                                null,
                                entity.getX(),
                                entity.getY(),
                                entity.getZ(),
                                sound,
                                SoundSource.BLOCKS,
                                1.0f,
                                1.0f
                            );
                    }
                }

                // Broadcast updated state to tracking clients
                PacketSyncFurnitureState.sendToTracking(furniture);
            }
        } else {
            TiedUpMod.LOGGER.debug(
                "[PacketFurnitureForcemount] startRiding failed for {} on furniture {}",
                captive.getName().getString(),
                msg.furnitureEntityId
            );
        }
    }

    // ==================== Helpers ====================

    /**
     * Find a living entity by UUID within a reasonable range of the sender.
     * Checks players first (O(1) lookup), then falls back to entity search.
     */
    private static LivingEntity findCaptiveByUUID(
        ServerPlayer sender,
        UUID uuid
    ) {
        // Try player lookup first (fast)
        net.minecraft.world.entity.player.Player player = sender
            .level()
            .getPlayerByUUID(uuid);
        if (player != null) return player;

        // Search nearby entities (64 block radius)
        AABB searchBox = sender.getBoundingBox().inflate(64);
        for (LivingEntity nearby : sender
            .level()
            .getEntitiesOfClass(LivingEntity.class, searchBox)) {
            if (nearby.getUUID().equals(uuid)) {
                return nearby;
            }
        }
        return null;
    }

    /**
     * Find the first available (unoccupied) seat on the furniture.
     *
     * @return the seat ID, or null if all seats are occupied
     */
    private static String findFirstAvailableSeat(
        ISeatProvider provider,
        Entity furniture
    ) {
        for (SeatDefinition seat : provider.getSeats()) {
            boolean occupied = false;
            for (Entity passenger : furniture.getPassengers()) {
                SeatDefinition passengerSeat = provider.getSeatForPassenger(
                    passenger
                );
                if (
                    passengerSeat != null &&
                    passengerSeat.id().equals(seat.id())
                ) {
                    occupied = true;
                    break;
                }
            }
            if (!occupied) return seat.id();
        }
        return null;
    }
}
