package com.tiedup.remake.network.action;

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.network.PacketRateLimiter;
import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.state.IRestrainable;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.List;
import java.util.function.Supplier;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.network.NetworkEvent;

/**
 *
 * Based on original PacketTightenBinds from 1.12.2
 *
 * Sent by client when player wants to tighten a nearby tied player's binds.
 * No payload needed - server will find the nearest tied player the sender is looking at.
 */
public class PacketTighten {

    /**
     * Empty packet - no data needed.
     */
    public PacketTighten() {
        // Empty packet
    }

    /**
     * Encode the packet to the network buffer.
     * Nothing to encode for this packet.
     *
     * @param buf The buffer to write to
     */
    public void encode(FriendlyByteBuf buf) {
        // Empty - no data
    }

    /**
     * Decode the packet from the network buffer.
     * Nothing to decode for this packet.
     *
     * @param buf The buffer to read from
     * @return The decoded packet
     */
    public static PacketTighten decode(FriendlyByteBuf buf) {
        return new PacketTighten();
    }

    /**
     * Handle the packet on the receiving side (SERVER SIDE).
     *
     * @param ctx The network context
     */
    public void handle(Supplier<NetworkEvent.Context> ctx) {
        ctx
            .get()
            .enqueueWork(() -> {
                // This runs on the SERVER side only
                ServerPlayer tightener = ctx.get().getSender();
                if (tightener == null) {
                    return;
                }

                handleServer(tightener);
            });
        ctx.get().setPacketHandled(true);
    }

    /**
     * Handle the packet on the server side.
     * Finds the nearest tied entity (player or NPC) the tightener is looking at
     * and tightens their binds.
     *
     * Based on original PacketTightenBinds handler
     *
     * @param tightener The player who sent the packet
     */
    private void handleServer(ServerPlayer tightener) {
        if (!PacketRateLimiter.allowPacket(tightener, "action")) return;

        // Find nearby tied entities within 5 blocks
        Vec3 eyePos = tightener.getEyePosition();
        Vec3 lookVec = tightener.getLookAngle();
        double maxDistance = 5.0;

        AABB searchBox = new AABB(
            eyePos.x - maxDistance,
            eyePos.y - maxDistance,
            eyePos.z - maxDistance,
            eyePos.x + maxDistance,
            eyePos.y + maxDistance,
            eyePos.z + maxDistance
        );

        // Search for all LivingEntities (Players + NPCs)
        List<LivingEntity> nearbyEntities = tightener
            .level()
            .getEntitiesOfClass(
                LivingEntity.class,
                searchBox,
                entity -> entity != tightener && entity.isAlive()
            );

        // Find the closest entity the tightener is looking at
        LivingEntity closestTarget = null;
        IRestrainable closestState = null;
        double closestDistance = maxDistance;

        for (LivingEntity candidate : nearbyEntities) {
            // Get kidnapped state (works for Players and NPCs)
            IRestrainable candidateState = KidnappedHelper.getKidnappedState(
                candidate
            );
            if (candidateState == null || !candidateState.isTiedUp()) {
                continue; // Only tighten tied entities
            }

            // Check if tightener is looking at this entity
            Vec3 toCandidate = candidate
                .position()
                .add(0, candidate.getBbHeight() / 2, 0)
                .subtract(eyePos);
            double distance = toCandidate.length();

            if (distance > maxDistance) {
                continue;
            }

            // Check angle - must be looking roughly at the entity
            Vec3 toCandidateNorm = toCandidate.normalize();
            double dot = lookVec.dot(toCandidateNorm);

            if (dot > 0.9 && distance < closestDistance) {
                // ~25 degree cone
                closestTarget = candidate;
                closestState = candidateState;
                closestDistance = distance;
            }
        }

        if (closestTarget != null && closestState != null) {
            // Security: Verify sender has permission to tighten
            // Must be captor of target, collar owner, or admin
            boolean hasPermission = false;

            // Check if sender is captor
            if (closestState.isCaptive()) {
                ICaptor captor = closestState.getCaptor();
                if (
                    captor != null &&
                    captor.getEntity() != null &&
                    captor.getEntity().getUUID().equals(tightener.getUUID())
                ) {
                    hasPermission = true;
                }
            }

            // Check if sender owns collar
            if (!hasPermission && closestState.hasCollar()) {
                var collarStack = closestState.getEquipment(BodyRegionV2.NECK);
                if (com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collarStack)) {
                    if (com.tiedup.remake.v2.bondage.CollarHelper.isOwner(collarStack, tightener)) {
                        hasPermission = true;
                    }
                }
            }

            // Check if sender is admin
            if (!hasPermission && tightener.hasPermissions(2)) {
                hasPermission = true;
            }

            if (!hasPermission) {
                TiedUpMod.LOGGER.debug(
                    "[PACKET] {} tried to tighten {} but has no permission",
                    tightener.getName().getString(),
                    closestTarget.getName().getString()
                );
                return;
            }

            closestState.tighten(tightener);
            TiedUpMod.LOGGER.info(
                "[PACKET] {} tightened {}'s binds",
                tightener.getName().getString(),
                closestTarget.getName().getString()
            );
        } else {
            TiedUpMod.LOGGER.debug(
                "[PACKET] {} tried to tighten but no valid target found",
                tightener.getName().getString()
            );
        }
    }
}
