package com.tiedup.remake.tasks;

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.action.PacketTying;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;

/**
 *
 * Based on original TyingPlayerTask from 1.12.2
 *
 * This task:
 * 1. Tracks progress as the kidnapper repeatedly right-clicks the target
 * 2. Sends progress updates to kidnapper (and target if it's a player)
 * 3. Applies the bind/gag when the timer completes
 * 4. Works with any LivingEntity that has an IBondageState state
 */
public class TyingPlayerTask extends TyingTask {

    /** The player performing the tying action. */
    protected Player kidnapper;

    /** FIX: Source inventory slot to consume from when task completes */
    private int sourceSlot = -1;

    /** FIX: Source player whose inventory to consume from */
    private Player sourcePlayer;

    /**
     * Create a new tying task.
     *
     * @param bind The bind/gag item to apply
     * @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 TyingPlayerTask(
        ItemStack bind,
        IBondageState targetState,
        LivingEntity targetEntity,
        int seconds,
        Level level
    ) {
        super(bind, targetState, targetEntity, seconds, level);
    }

    /**
     * Create a new tying task with kidnapper reference.
     *
     * @param bind The bind/gag item to apply
     * @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 kidnapper The player performing the tying
     */
    public TyingPlayerTask(
        ItemStack bind,
        IBondageState targetState,
        LivingEntity targetEntity,
        int seconds,
        Level level,
        Player kidnapper
    ) {
        super(bind, targetState, targetEntity, seconds, level);
        this.kidnapper = kidnapper;
    }

    /**
     * Set the kidnapper (the player doing the tying).
     */
    public void setKidnapper(Player kidnapper) {
        this.kidnapper = kidnapper;
    }

    /**
     * FIX: Set the source inventory slot for item consumption.
     * Called when task starts to track which slot to consume from.
     */
    public void setSourceSlot(int slot) {
        this.sourceSlot = slot;
    }

    /**
     * FIX: Set the source player for item consumption.
     * Called when task starts to track whose inventory to consume from.
     */
    public void setSourcePlayer(Player player) {
        this.sourcePlayer = player;
    }

    /**
     * Update the tying progress.
     * Called each time the kidnapper right-clicks the target (or left-clicks for self-tying).
     *
     * In the new progress-based system, this method only:
     * 1. Validates kidnapper is still close to target
     * 2. Marks this tick as "active" (progress will increase in tick())
     *
     * The actual progress increment and completion check happen in tick().
     */
    @Override
    public synchronized void update() {
        // Check if this is self-tying (target == kidnapper)
        boolean isSelfTying =
            kidnapper != null && kidnapper.equals(targetEntity);

        // SECURITY: Validate kidnapper is still close to target (skip for self-tying)
        if (!isSelfTying && kidnapper != null && targetEntity != null) {
            double distance = kidnapper.distanceTo(targetEntity);
            if (distance > 4.0) {
                TiedUpMod.LOGGER.debug(
                    "[TyingPlayerTask] Kidnapper {} moved too far from target ({} blocks), cancelling",
                    kidnapper.getName().getString(),
                    String.format("%.1f", distance)
                );
                stop();
                return;
            }

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

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

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

        // Check if this is self-tying (target == kidnapper)
        boolean isSelfTying =
            kidnapper != null && kidnapper.equals(targetEntity);

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

        if (isSelfTying) {
            // Self-tying: Send single packet with self-tying message
            if (kidnapper instanceof ServerPlayer serverPlayer) {
                PacketTying selfPacket = new PacketTying(
                    this.getState(),
                    this.getMaxSeconds(),
                    true, // isKidnapper (shows "Tying..." message)
                    "yourself" // Special indicator for self-tying
                );
                ModNetwork.sendToPlayer(selfPacket, serverPlayer);
            }
        } else {
            // Normal tying: Send packets to both parties
            // Packet to victim: isKidnapper=false, shows kidnapper's name
            if (targetEntity instanceof ServerPlayer serverTarget) {
                PacketTying victimPacket = new PacketTying(
                    this.getState(),
                    this.getMaxSeconds(),
                    false,
                    kidnapperName
                );
                ModNetwork.sendToPlayer(victimPacket, serverTarget);
            }

            // Packet to kidnapper: isKidnapper=true, shows target's name
            if (kidnapper instanceof ServerPlayer serverKidnapper) {
                PacketTying kidnapperPacket = new PacketTying(
                    this.getState(),
                    this.getMaxSeconds(),
                    true,
                    targetName
                );
                ModNetwork.sendToPlayer(kidnapperPacket, serverKidnapper);
            }
        }
    }

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

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

        // Apply the bind/gag to the target
        targetState.equip(BodyRegionV2.ARMS, bind);

        // FIX: Consume the item from the stored inventory slot
        // This prevents duplication by consuming from the exact slot used to start the task
        if (sourcePlayer != null && sourceSlot >= 0) {
            ItemStack slotStack = sourcePlayer
                .getInventory()
                .getItem(sourceSlot);
            if (
                !slotStack.isEmpty() &&
                ItemStack.isSameItemSameTags(slotStack, bind)
            ) {
                slotStack.shrink(1);
                TiedUpMod.LOGGER.debug(
                    "[TyingPlayerTask] Consumed bind item from slot {}",
                    sourceSlot
                );
            } else {
                // Slot changed - try to find and consume from any matching slot
                for (
                    int i = 0;
                    i < sourcePlayer.getInventory().getContainerSize();
                    i++
                ) {
                    ItemStack checkStack = sourcePlayer
                        .getInventory()
                        .getItem(i);
                    if (
                        !checkStack.isEmpty() &&
                        ItemStack.isSameItemSameTags(checkStack, bind)
                    ) {
                        checkStack.shrink(1);
                        TiedUpMod.LOGGER.debug(
                            "[TyingPlayerTask] Consumed bind item from fallback slot {}",
                            i
                        );
                        break;
                    }
                }
            }
        }

        // Track who tied this entity (for reward anti-abuse)
        if (
            targetEntity instanceof
                com.tiedup.remake.entities.EntityDamsel damsel
        ) {
            damsel.setTiedBy(kidnapper);
        }

        // Mark task as stopped
        stop();

        sendCompletionPackets();
    }

    /**
     * Send completion packets to kidnapper and/or target.
     * Handles both self-tying and normal tying cases.
     */
    protected void sendCompletionPackets() {
        boolean isSelfTying =
            kidnapper != null && kidnapper.equals(targetEntity);

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

        if (isSelfTying) {
            if (kidnapper instanceof ServerPlayer serverPlayer) {
                PacketTying completionPacket = new PacketTying(
                    -1,
                    this.getMaxSeconds(),
                    true,
                    "yourself"
                );
                ModNetwork.sendToPlayer(completionPacket, serverPlayer);

                PlayerBindState playerState = PlayerBindState.getInstance(
                    serverPlayer
                );
                if (playerState != null) {
                    playerState.tasks().setRestrainedState(null);
                }
            }
        } else {
            if (targetEntity instanceof ServerPlayer serverTarget) {
                PacketTying completionPacket = new PacketTying(
                    -1,
                    this.getMaxSeconds(),
                    false,
                    kidnapperName
                );
                ModNetwork.sendToPlayer(completionPacket, serverTarget);

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

            if (kidnapper instanceof ServerPlayer serverKidnapper) {
                PacketTying completionPacket = new PacketTying(
                    -1,
                    this.getMaxSeconds(),
                    true,
                    targetName
                );
                ModNetwork.sendToPlayer(completionPacket, serverKidnapper);
            }
        }
    }

    /**
     * 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 (PacketTying) 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
    }
}
