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

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ItemMasterKey;
import com.tiedup.remake.network.PacketRateLimiter;
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.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.minecraftforge.network.NetworkEvent;

/**
 * Client-to-server packet: toggle lock/unlock on a specific furniture seat.
 *
 * <p>The sender must hold a key item (ItemMasterKey) in their main hand,
 * the seat must be lockable and occupied (someone sitting in it), and the
 * sender must be within 5 blocks of the furniture entity.</p>
 *
 * <p>On success, toggles the lock state and broadcasts a
 * {@link PacketSyncFurnitureState} to all tracking clients.</p>
 *
 * <p>Wire format: int entityId (4) + utf seatId (variable)</p>
 *
 * <p>Direction: Client to Server (C2S)</p>
 */
public class PacketFurnitureLock {

    private final int entityId;
    private final String seatId;

    public PacketFurnitureLock(int entityId, String seatId) {
        this.entityId = entityId;
        this.seatId = seatId;
    }

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

    public static void encode(PacketFurnitureLock msg, FriendlyByteBuf buf) {
        buf.writeInt(msg.entityId);
        buf.writeUtf(msg.seatId);
    }

    public static PacketFurnitureLock decode(FriendlyByteBuf buf) {
        return new PacketFurnitureLock(buf.readInt(), buf.readUtf());
    }

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

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

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

        // Rate limit: prevent lock toggle spam
        if (!PacketRateLimiter.allowPacket(sender, "action")) return;

        // Resolve the target entity
        Entity entity = sender.level().getEntity(msg.entityId);
        if (entity == null) return;
        if (!(entity instanceof ISeatProvider provider)) return;

        // Unified authorization: range, master key, seat state, collar ownership.
        // Shared with EntityFurniture.interact() via FurnitureAuthPredicate to
        // prevent the security drift found in the 2026-04-17 audit.
        if (
            !com.tiedup.remake.v2.furniture.FurnitureAuthPredicate.canLockUnlock(
                sender,
                entity,
                msg.seatId
            )
        ) {
            return;
        }

        // Retrieve seat metadata for the downstream feedback/animation logic
        SeatDefinition seat = findSeatById(provider, msg.seatId);
        if (seat == null) return;

        // Toggle the lock state
        boolean wasLocked = provider.isSeatLocked(msg.seatId);
        provider.setSeatLocked(msg.seatId, !wasLocked);

        TiedUpMod.LOGGER.debug(
            "[PacketFurnitureLock] {} {} seat '{}' on furniture entity {}",
            sender.getName().getString(),
            wasLocked ? "unlocked" : "locked",
            msg.seatId,
            msg.entityId
        );

        // Play lock/unlock sound and set animation state
        if (entity instanceof EntityFurniture furniture) {
            FurnitureDefinition def = furniture.getDefinition();
            if (def != null) {
                FurnitureFeedback feedback = def.feedback();
                ResourceLocation soundRL = wasLocked
                    ? feedback.unlockSound()
                    : feedback.lockSound();
                if (soundRL != null) {
                    SoundEvent sound = SoundEvent.createVariableRangeEvent(
                        soundRL
                    );
                    entity
                        .level()
                        .playSound(
                            null, // null = play for all nearby players
                            entity.getX(),
                            entity.getY(),
                            entity.getZ(),
                            sound,
                            SoundSource.BLOCKS,
                            1.0f,
                            1.0f
                        );
                }
            }

            // Use setTransitionState (not setAnimState) so the LOCKING /
            // UNLOCKING pose survives past one tick — otherwise the tick-
            // decrement path in EntityFurniture reverts to transitionTarget
            // before any client observes the transient state.
            boolean nowLocked = !wasLocked;
            furniture.setTransitionState(
                nowLocked
                    ? EntityFurniture.STATE_LOCKING
                    : EntityFurniture.STATE_UNLOCKING,
                20,
                EntityFurniture.STATE_OCCUPIED
            );

            // Broadcast updated state to all tracking clients
            PacketSyncFurnitureState.sendToTracking(furniture);
        }
    }

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

    /**
     * Find a SeatDefinition by ID from the provider's seat list.
     */
    private static SeatDefinition findSeatById(
        ISeatProvider provider,
        String seatId
    ) {
        for (SeatDefinition seat : provider.getSeats()) {
            if (seat.id().equals(seatId)) return seat;
        }
        return null;
    }

    /**
     * Check if a seat is occupied by any passenger.
     */
    private static boolean isSeatOccupied(
        ISeatProvider provider,
        Entity furnitureEntity,
        String seatId
    ) {
        for (Entity passenger : furnitureEntity.getPassengers()) {
            SeatDefinition passengerSeat = provider.getSeatForPassenger(
                passenger
            );
            if (passengerSeat != null && passengerSeat.id().equals(seatId)) {
                return true;
            }
        }
        return false;
    }
}
