package com.tiedup.remake.tasks;

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.entities.NpcTypeHelper;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.action.PacketUntying;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import java.util.Map;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;

/**
 *
 * Based on original UntyingPlayerTask from 1.12.2
 *
 * This task:
 * 1. Tracks progress as the helper repeatedly right-clicks the tied target
 * 2. Sends progress updates to both helper and target clients (if player)
 * 3. Removes all restraints and drops items when the timer completes
 * 4. Sets up target's restraint state for client-side visualization (if player)
 *
 * Epic 5F: Uses V2EquipmentHelper/BodyRegionV2.
 */
public class UntyingPlayerTask extends UntyingTask {

    /** The player performing the untying action. */
    private Player helper;

    /**
     * Create a new untying task.
     *
     * @param targetState The target's IBondageState state
     * @param targetEntity The target entity (Player or NPC)
     * @param seconds Total duration in seconds
     * @param level The world
     */
    public UntyingPlayerTask(
        IBondageState targetState,
        LivingEntity targetEntity,
        int seconds,
        Level level
    ) {
        super(targetState, targetEntity, seconds, level);
    }

    /**
     * Create a new untying task with helper reference.
     *
     * @param targetState The target's IBondageState state
     * @param targetEntity The target entity (Player or NPC)
     * @param seconds Total duration in seconds
     * @param level The world
     * @param helper The player performing the untying
     */
    public UntyingPlayerTask(
        IBondageState targetState,
        LivingEntity targetEntity,
        int seconds,
        Level level,
        Player helper
    ) {
        super(targetState, targetEntity, seconds, level);
        this.helper = helper;
    }

    /**
     * Set the helper (the player doing the untying).
     */
    public void setHelper(Player helper) {
        this.helper = helper;
    }

    /**
     * Update the untying progress.
     * Called each time the helper right-clicks the tied target.
     *
     * In the new progress-based system, this method only:
     * 1. Marks this tick as "active" (progress will increase in tick())
     * 2. Validates helper is still close to target
     *
     * The actual progress increment and completion check happen in tick().
     */
    @Override
    public synchronized void update() {
        // SECURITY: Validate helper is still close to target
        if (helper != null && targetEntity != null) {
            double distance = helper.distanceTo(targetEntity);
            if (distance > 4.0) {
                TiedUpMod.LOGGER.debug(
                    "[UntyingPlayerTask] Helper {} moved too far from target ({} blocks), cancelling",
                    helper.getName().getString(),
                    String.format("%.1f", distance)
                );
                stop();
                return;
            }

            // Check line-of-sight
            if (!helper.hasLineOfSight(targetEntity)) {
                TiedUpMod.LOGGER.debug(
                    "[UntyingPlayerTask] Helper {} lost line of sight to target, cancelling",
                    helper.getName().getString()
                );
                stop();
                return;
            }
        }

        // Mark this tick as active (player is clicking on target)
        super.update();
    }

    /**
     * Send progress packets to both helper and target (if players).
     * Called every tick from RestraintTaskTickHandler.onPlayerTick().
     */
    @Override
    public void sendProgressPackets() {
        if (stopped) return;

        String helperName =
            helper != null ? helper.getName().getString() : "Someone";
        String targetName = targetEntity.getName().getString();

        // Packet to victim: isHelper=false, shows helper's name
        if (targetEntity instanceof ServerPlayer serverTarget) {
            PacketUntying victimPacket = new PacketUntying(
                this.getState(),
                this.getMaxSeconds(),
                false,
                helperName
            );
            ModNetwork.sendToPlayer(victimPacket, serverTarget);
        }

        // Packet to helper: isHelper=true, shows target's name
        if (helper instanceof ServerPlayer serverHelper) {
            PacketUntying helperPacket = new PacketUntying(
                this.getState(),
                this.getMaxSeconds(),
                true,
                targetName
            );
            ModNetwork.sendToPlayer(helperPacket, serverHelper);
        }
    }

    /**
     * Called when the task completes successfully.
     * Drops bondage items, frees the target, and sends completion packets.
     */
    @Override
    protected void onComplete() {
        // Verify target entity still exists and is alive
        if (!isTargetValid()) {
            TiedUpMod.LOGGER.warn(
                "[UntyingPlayerTask] Target entity no longer valid, cancelling task"
            );
            stop();
            return;
        }

        TiedUpMod.LOGGER.info(
            "[UntyingPlayerTask] Untying complete for {}",
            targetEntity.getName().getString()
        );

        // Drop all bondage items on the ground
        dropBondageItems();

        // Remove all restraints from target
        untieTarget();

        // Handle Damsel-specific rewards
        if (
            targetEntity instanceof EntityDamsel damsel &&
            NpcTypeHelper.isDamselOnly(targetEntity) &&
            helper != null
        ) {
            // Reward the savior (gives emeralds and marks player as savior)
            damsel.rewardSavior(helper);
        }

        // Mark task as stopped
        stop();

        // Send completion packets to both parties
        String helperName =
            helper != null ? helper.getName().getString() : "Someone";
        String targetName = targetEntity.getName().getString();

        if (targetEntity instanceof ServerPlayer serverTarget) {
            PacketUntying completionPacket = new PacketUntying(
                -1,
                this.getMaxSeconds(),
                false,
                helperName
            );
            ModNetwork.sendToPlayer(completionPacket, serverTarget);

            PlayerBindState playerState = PlayerBindState.getInstance(
                serverTarget
            );
            if (playerState != null) {
                playerState.tasks().setRestrainedState(null);
            }
        }

        if (helper instanceof ServerPlayer serverHelper) {
            PacketUntying completionPacket = new PacketUntying(
                -1,
                this.getMaxSeconds(),
                true,
                targetName
            );
            ModNetwork.sendToPlayer(completionPacket, serverHelper);
        }
    }

    /**
     * Set up the target's restraint state for client-side progress tracking.
     * Only applies to player targets (NPCs don't need client-side state).
     *
     * Note: On dedicated servers, this is a no-op. The client receives
     * progress packets (PacketUntying) which create the PlayerStateTask locally.
     */
    @Override
    public void setUpTargetState() {
        // Only set up state for player targets
        if (!(targetEntity instanceof Player targetPlayer)) {
            return;
        }

        // Server-side: nothing to do - client handles its own PlayerStateTask via packets
        if (!targetPlayer.level().isClientSide) {
            return;
        }

        // Client-side: set up local progress tracking (only reached in single-player/integrated server)
        // Note: This code path is rarely used since packets handle client-side state
    }

    /**
     * Drop all bondage items on the ground.
     * Works for both players (via V2EquipmentHelper) and NPCs (via IBondageState).
     */
    private void dropBondageItems() {
        // For player targets: use V2EquipmentHelper to get all equipped items
        if (targetEntity instanceof Player player) {
            Map<BodyRegionV2, ItemStack> equipped =
                V2EquipmentHelper.getAllEquipped(player);
            for (Map.Entry<
                BodyRegionV2,
                ItemStack
            > entry : equipped.entrySet()) {
                ItemStack stack = entry.getValue();
                if (!stack.isEmpty()) {
                    // Drop item at player's position
                    ItemEntity itemEntity = new ItemEntity(
                        targetEntity.level(),
                        targetEntity.getX(),
                        targetEntity.getY(),
                        targetEntity.getZ(),
                        stack.copy()
                    );

                    targetEntity.level().addFreshEntity(itemEntity);

                    TiedUpMod.LOGGER.debug(
                        "[UntyingPlayerTask] Dropped {} from region {}",
                        stack.getHoverName().getString(),
                        entry.getKey().name()
                    );
                }
            }
        } else {
            // For NPC targets: use IBondageState interface
            // Drop bind if present
            ItemStack bind = targetState.getEquipment(BodyRegionV2.ARMS);
            if (bind != null && !bind.isEmpty()) {
                ItemEntity itemEntity = new ItemEntity(
                    targetEntity.level(),
                    targetEntity.getX(),
                    targetEntity.getY(),
                    targetEntity.getZ(),
                    bind.copy()
                );
                targetEntity.level().addFreshEntity(itemEntity);
                TiedUpMod.LOGGER.debug(
                    "[UntyingPlayerTask] Dropped bind: {}",
                    bind.getHoverName().getString()
                );
            }

            // Drop gag if present
            ItemStack gag = targetState.getEquipment(BodyRegionV2.MOUTH);
            if (gag != null && !gag.isEmpty()) {
                ItemEntity itemEntity = new ItemEntity(
                    targetEntity.level(),
                    targetEntity.getX(),
                    targetEntity.getY(),
                    targetEntity.getZ(),
                    gag.copy()
                );
                targetEntity.level().addFreshEntity(itemEntity);
                TiedUpMod.LOGGER.debug(
                    "[UntyingPlayerTask] Dropped gag: {}",
                    gag.getHoverName().getString()
                );
            }

            // Drop blindfold if present
            ItemStack blindfold = targetState.getEquipment(BodyRegionV2.EYES);
            if (blindfold != null && !blindfold.isEmpty()) {
                ItemEntity itemEntity = new ItemEntity(
                    targetEntity.level(),
                    targetEntity.getX(),
                    targetEntity.getY(),
                    targetEntity.getZ(),
                    blindfold.copy()
                );
                targetEntity.level().addFreshEntity(itemEntity);
                TiedUpMod.LOGGER.debug(
                    "[UntyingPlayerTask] Dropped blindfold: {}",
                    blindfold.getHoverName().getString()
                );
            }

            // Drop earplugs if present
            ItemStack earplugs = targetState.getEquipment(BodyRegionV2.EARS);
            if (earplugs != null && !earplugs.isEmpty()) {
                ItemEntity itemEntity = new ItemEntity(
                    targetEntity.level(),
                    targetEntity.getX(),
                    targetEntity.getY(),
                    targetEntity.getZ(),
                    earplugs.copy()
                );
                targetEntity.level().addFreshEntity(itemEntity);
                TiedUpMod.LOGGER.debug(
                    "[UntyingPlayerTask] Dropped earplugs: {}",
                    earplugs.getHoverName().getString()
                );
            }

            // Drop mittens if present
            ItemStack mittens = targetState.getEquipment(BodyRegionV2.HANDS);
            if (mittens != null && !mittens.isEmpty()) {
                ItemEntity itemEntity = new ItemEntity(
                    targetEntity.level(),
                    targetEntity.getX(),
                    targetEntity.getY(),
                    targetEntity.getZ(),
                    mittens.copy()
                );
                targetEntity.level().addFreshEntity(itemEntity);
                TiedUpMod.LOGGER.debug(
                    "[UntyingPlayerTask] Dropped mittens: {}",
                    mittens.getHoverName().getString()
                );
            }
        }
    }

    /**
     * Remove all restraints from the target entity.
     * Works for both players and NPCs via IBondageState interface.
     */
    private void untieTarget() {
        // For player targets: clear all V2 equipment (fires onUnequipped callbacks)
        if (targetEntity instanceof Player player) {
            V2EquipmentHelper.clearAll(player);

            PlayerBindState playerState = PlayerBindState.getInstance(player);
            if (playerState != null && playerState.isCaptive()) {
                playerState.free();
            }
        } else {
            // For NPC targets: use IBondageState interface directly
            targetState.unequip(BodyRegionV2.ARMS);
            targetState.unequip(BodyRegionV2.MOUTH);
            targetState.unequip(BodyRegionV2.EYES);
            targetState.unequip(BodyRegionV2.EARS);
            targetState.unequip(BodyRegionV2.HANDS);
        }

        TiedUpMod.LOGGER.info(
            "[UntyingPlayerTask] Fully untied {}",
            targetEntity.getName().getString()
        );
    }
}
