package com.tiedup.remake.network.minigame;

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.ItemLockpick;
import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.minigame.LockpickMiniGameState;
import com.tiedup.remake.minigame.LockpickMiniGameState.PickAttemptResult;
import com.tiedup.remake.minigame.LockpickSessionManager;
import com.tiedup.remake.minigame.LockpickTargetKind;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.PacketRateLimiter;
import com.tiedup.remake.network.sync.SyncManager;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import com.tiedup.remake.v2.furniture.EntityFurniture;
import com.tiedup.remake.v2.furniture.FurnitureDefinition;
import com.tiedup.remake.v2.furniture.network.PacketSyncFurnitureState;
import java.util.UUID;
import java.util.function.Supplier;
import net.minecraft.ChatFormatting;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
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.item.ItemStack;
import net.minecraftforge.network.NetworkEvent;

/**
 * Packet for lockpick attempt at current position (Client to Server).
 *
 * Sent when player presses SPACE to attempt picking at current position.
 */
public class PacketLockpickAttempt {

    private final UUID sessionId;

    public PacketLockpickAttempt(UUID sessionId) {
        this.sessionId = sessionId;
    }

    public void encode(FriendlyByteBuf buf) {
        buf.writeUUID(sessionId);
    }

    public static PacketLockpickAttempt decode(FriendlyByteBuf buf) {
        UUID sessionId = buf.readUUID();
        return new PacketLockpickAttempt(sessionId);
    }

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

    private void handleServer(ServerPlayer player) {
        // Rate limiting to prevent lockpick attempt spam exploit
        if (!PacketRateLimiter.allowPacket(player, "minigame")) {
            return;
        }

        LockpickSessionManager manager = LockpickSessionManager.getInstance();

        // Validate session
        if (!manager.validateLockpickSession(player.getUUID(), sessionId)) {
            TiedUpMod.LOGGER.warn(
                "[PacketLockpickAttempt] Invalid session {} for player {}",
                sessionId.toString().substring(0, 8),
                player.getName().getString()
            );
            return;
        }

        LockpickMiniGameState session = manager.getLockpickSession(
            player.getUUID()
        );
        if (session == null || session.isComplete()) {
            return;
        }

        // Play sound and notify guards
        manager.onLockpickAttempt(player);

        // Attempt to pick
        PickAttemptResult result = session.attemptPick();

        TiedUpMod.LOGGER.debug(
            "[PacketLockpickAttempt] Player {} attempted pick, result: {}",
            player.getName().getString(),
            result
        );

        // Handle result
        switch (result) {
            case SUCCESS -> handleSuccess(player, session);
            case OUT_OF_PICKS -> handleOutOfPicks(player, session);
            case MISSED -> handleMissed(player, session);
            default -> {
                // Session already complete
            }
        }
    }

    private void handleSuccess(
        ServerPlayer player,
        LockpickMiniGameState session
    ) {
        if (session.getTargetKind() == LockpickTargetKind.FURNITURE_SEAT) {
            // Validate EVERY input before any side effect. Consuming the
            // session and damaging the lockpick before verifying that the
            // unlock will succeed would show "Lock picked!" to the player
            // while nothing actually unlocks.
            Entity furnitureEntity = player.level().getEntity(session.getTargetData());
            if (
                furnitureEntity == null ||
                player.distanceTo(furnitureEntity) > 10.0
            ) {
                return;
            }
            CompoundTag furnitureCtx = player
                .getPersistentData()
                .getCompound("tiedup_furniture_lockpick_ctx");
            String seatId = furnitureCtx.contains("seat_id", net.minecraft.nbt.Tag.TAG_STRING)
                ? furnitureCtx.getString("seat_id")
                : "";
            if (seatId.isEmpty()) {
                TiedUpMod.LOGGER.warn(
                    "[PacketLockpickAttempt] Furniture lockpick ctx missing seat_id for {} — aborting without consuming session",
                    player.getName().getString()
                );
                return;
            }

            LockpickSessionManager.getInstance().endLockpickSession(
                player.getUUID(),
                true
            );
            handleFurnitureLockpickSuccess(player, furnitureEntity, seatId);
            player.getPersistentData().remove("tiedup_furniture_lockpick_ctx");
            damageLockpick(player);

            ModNetwork.sendToPlayer(
                new PacketLockpickMiniGameResult(
                    session.getSessionId(),
                    PacketLockpickMiniGameResult.ResultType.SUCCESS,
                    0
                ),
                player
            );
            return;
        }

        // Body item lockpick — self-targeting, no distance check needed
        LockpickSessionManager.getInstance().endLockpickSession(
            player.getUUID(),
            true
        );

        BodyRegionV2 targetRegion =
            BodyRegionV2.values()[session.getTargetData()];
        ItemStack targetStack = V2EquipmentHelper.getInRegion(
            player,
            targetRegion
        );

        if (
            !targetStack.isEmpty() &&
            targetStack.getItem() instanceof ILockable lockable
        ) {
            // Unlock the item
            lockable.setLockedByKeyUUID(targetStack, null);

            TiedUpMod.LOGGER.info(
                "[PacketLockpickAttempt] Player {} successfully picked lock on {} ({})",
                player.getName().getString(),
                targetStack.getDisplayName().getString(),
                targetRegion
            );

            // Lock is now unlocked -- bind resistance is tracked separately via IHasResistance
        }

        // Damage lockpick
        damageLockpick(player);

        // Sync to all players so unlock is visible immediately
        SyncManager.syncInventory(player);

        // Send result to client
        ModNetwork.sendToPlayer(
            new PacketLockpickMiniGameResult(
                session.getSessionId(),
                PacketLockpickMiniGameResult.ResultType.SUCCESS,
                0
            ),
            player
        );
    }

    /**
     * Handle a successful furniture seat lockpick: unlock the seat, dismount
     * the passenger, play the unlock sound, and broadcast the updated state.
     * Caller is responsible for validating inputs before firing side effects.
     */
    private void handleFurnitureLockpickSuccess(
        ServerPlayer player,
        Entity furnitureEntity,
        String seatId
    ) {
        if (!(furnitureEntity instanceof EntityFurniture furniture)) {
            TiedUpMod.LOGGER.warn(
                "[PacketLockpickAttempt] Lockpick target {} is not an EntityFurniture",
                furnitureEntity.getId()
            );
            return;
        }

        furniture.setSeatLocked(seatId, false);

        // Dismount the passenger in that seat
        Entity passenger = furniture.findPassengerInSeat(seatId);
        if (passenger != null) {
            // Clear reconnection tag before dismount
            if (passenger instanceof ServerPlayer passengerPlayer) {
                passengerPlayer
                    .getPersistentData()
                    .remove("tiedup_locked_furniture");
            }
            passenger.stopRiding();
        }

        FurnitureDefinition def = furniture.getDefinition();
        if (def != null && def.feedback().unlockSound() != null) {
            player
                .level()
                .playSound(
                    null,
                    furniture.getX(),
                    furniture.getY(),
                    furniture.getZ(),
                    SoundEvent.createVariableRangeEvent(
                        def.feedback().unlockSound()
                    ),
                    SoundSource.BLOCKS,
                    1.0f,
                    1.0f
                );
        }

        PacketSyncFurnitureState.sendToTracking(furniture);

        TiedUpMod.LOGGER.info(
            "[PacketLockpickAttempt] Player {} picked furniture lock on entity {} seat '{}'",
            player.getName().getString(),
            furniture.getId(),
            seatId
        );
    }

    private void handleOutOfPicks(
        ServerPlayer player,
        LockpickMiniGameState session
    ) {
        LockpickSessionManager.getInstance().endLockpickSession(
            player.getUUID(),
            false
        );

        // Destroy the lockpick
        ItemStack lockpickStack = ItemLockpick.findLockpickInInventory(player);
        if (!lockpickStack.isEmpty()) {
            lockpickStack.shrink(1);
        }

        TiedUpMod.LOGGER.info(
            "[PacketLockpickAttempt] Player {} ran out of lockpicks",
            player.getName().getString()
        );

        // Trigger shock if wearing shock collar
        triggerShockIfCollar(player);

        // Send result to client
        ModNetwork.sendToPlayer(
            new PacketLockpickMiniGameResult(
                session.getSessionId(),
                PacketLockpickMiniGameResult.ResultType.OUT_OF_PICKS,
                0
            ),
            player
        );
    }

    private void handleMissed(
        ServerPlayer player,
        LockpickMiniGameState session
    ) {
        // Calculate distance BEFORE damaging (for animation feedback)
        float distance = session.getDistanceToSweetSpot();

        // Damage lockpick
        damageLockpick(player);

        // Update remaining uses from actual lockpick
        int remainingUses = 0;
        ItemStack lockpickStack = ItemLockpick.findLockpickInInventory(player);
        if (!lockpickStack.isEmpty()) {
            remainingUses =
                lockpickStack.getMaxDamage() - lockpickStack.getDamageValue();
        }
        session.setRemainingUses(remainingUses);

        // Jam mechanic (5%) only applies to body-item sessions — seat locks
        // have no ILockable stack to jam.
        boolean jammed = false;
        boolean isBodySession =
            session.getTargetKind() == LockpickTargetKind.BODY_REGION;

        if (isBodySession && player.getRandom().nextFloat() < 0.05f) {
            int targetOrdinal = session.getTargetData();
            if (targetOrdinal >= 0 && targetOrdinal < BodyRegionV2.values().length) {
                BodyRegionV2 targetRegion = BodyRegionV2.values()[targetOrdinal];
                ItemStack targetStack = V2EquipmentHelper.getInRegion(
                    player,
                    targetRegion
                );

                if (
                    !targetStack.isEmpty() &&
                    targetStack.getItem() instanceof ILockable lockable
                ) {
                    lockable.setJammed(targetStack, true);
                    jammed = true;

                    player.sendSystemMessage(
                        Component.translatable(
                            "msg.tiedup.network.lock_jammed"
                        ).withStyle(ChatFormatting.RED)
                    );

                    TiedUpMod.LOGGER.info(
                        "[PacketLockpickAttempt] Player {} jammed the lock on {} ({})",
                        player.getName().getString(),
                        targetStack.getDisplayName().getString(),
                        targetRegion
                    );

                    // End session since lock is jammed
                    LockpickSessionManager.getInstance().endLockpickSession(
                        player.getUUID(),
                        false
                    );

                    // Sync jam state to all players
                    SyncManager.syncInventory(player);
                }
            }
        }

        // Trigger shock if wearing shock collar
        triggerShockIfCollar(player);

        // Send result to client with distance for animation
        if (jammed) {
            // Send cancelled result since lock is jammed
            ModNetwork.sendToPlayer(
                new PacketLockpickMiniGameResult(
                    session.getSessionId(),
                    PacketLockpickMiniGameResult.ResultType.CANCELLED,
                    0,
                    distance
                ),
                player
            );
        } else {
            ModNetwork.sendToPlayer(
                new PacketLockpickMiniGameResult(
                    session.getSessionId(),
                    PacketLockpickMiniGameResult.ResultType.MISSED,
                    remainingUses,
                    distance
                ),
                player
            );
        }
    }

    /**
     * Damage the player's lockpick by 1 durability. If durability is exhausted,
     * the lockpick item is consumed (shrunk). Shared by handleSuccess, handleMissed,
     * and handleFurnitureLockpickSuccess paths.
     */
    private void damageLockpick(ServerPlayer player) {
        ItemStack lockpickStack = ItemLockpick.findLockpickInInventory(player);
        if (!lockpickStack.isEmpty()) {
            lockpickStack.setDamageValue(lockpickStack.getDamageValue() + 1);
            if (
                lockpickStack.getDamageValue() >= lockpickStack.getMaxDamage()
            ) {
                lockpickStack.shrink(1);
            }
        }
    }

    private void triggerShockIfCollar(ServerPlayer player) {
        PlayerBindState state = PlayerBindState.getInstance(player);
        if (state == null) return;

        ItemStack collar = V2EquipmentHelper.getInRegion(
            player,
            BodyRegionV2.NECK
        );
        if (collar.isEmpty()) return;

        if (com.tiedup.remake.v2.bondage.CollarHelper.canShock(collar)) {
            state.shockKidnapped(" (Failed lockpick attempt)", 2.0f);
            TiedUpMod.LOGGER.info(
                "[PacketLockpickAttempt] Player {} shocked for failed lockpick",
                player.getName().getString()
            );
        }
    }
}
