package com.tiedup.remake.events.restriction;

import com.tiedup.remake.core.SettingsAccessor;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.minigame.StruggleSessionManager;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.personality.PacketSlaveBeingFreed;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.tasks.ForceFeedingTask;
import com.tiedup.remake.tasks.TimedInteractTask;
import com.tiedup.remake.tasks.UntyingPlayerTask;
import com.tiedup.remake.tasks.UntyingTask;
import com.tiedup.remake.util.GameConstants;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.List;
import java.util.UUID;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

/**
 * Tick handler for restraint-related tasks (untying, tying, force feeding).
 *
 * Manages progress-based interaction tasks that span multiple ticks:
 * - Untying mechanic (empty hand right-click on tied entity)
 * - Tying mechanic (tick progression)
 * - Force feeding mechanic (food right-click on gagged entity)
 * - Auto-shock collar checks
 * - Struggle auto-stop (legacy QTE fallback)
 *
 * @see BondageItemRestrictionHandler for movement, interaction, and eating restrictions
 */
@Mod.EventBusSubscriber(
    modid = TiedUpMod.MOD_ID,
    bus = Mod.EventBusSubscriber.Bus.FORGE
)
public class RestraintTaskTickHandler {

    // ========== PLAYER-SPECIFIC TICK ==========

    /**
     * Handle player tick event for player-specific features.
     * - Auto-shock collar check (throttled to every N ticks)
     */
    @SubscribeEvent
    public static void onPlayerTick(TickEvent.PlayerTickEvent event) {
        if (event.side.isClient() || event.phase != TickEvent.Phase.END) {
            return;
        }

        Player player = event.player;
        PlayerBindState playerState = PlayerBindState.getInstance(player);

        // Check if struggle animation should stop
        // For continuous struggle: animation is managed by MiniGameSessionManager
        // Only auto-stop if NO active continuous session (legacy QTE fallback)
        if (playerState != null && playerState.isStruggling()) {
            // Don't auto-stop if there's an active continuous struggle session
            StruggleSessionManager mgr = StruggleSessionManager.getInstance();
            if (mgr.getContinuousStruggleSession(player.getUUID()) == null) {
                // Legacy behavior: stop after 80 ticks (no active continuous session)
                if (
                    playerState.getStruggle().shouldStopStruggling(
                        player.level().getGameTime()
                    )
                ) {
                    playerState.setStruggling(false, 0);
                    com.tiedup.remake.network.sync.SyncManager.syncStruggleState(
                        player
                    );
                }
            }
        }

        // Process untying task tick (progress-based system)
        // tick() increments/decrements progress based on whether update() was called this tick
        // sendProgressPackets() updates the UI for both players
        if (playerState != null) {
            com.tiedup.remake.tasks.UntyingTask currentUntyingTask =
                playerState.tasks().getCurrentUntyingTask();
            if (currentUntyingTask != null && !currentUntyingTask.isStopped()) {
                // AUTO-UPDATE: Check if player is still targeting the same entity
                // This allows "hold click" behavior without needing repeated interactLivingEntity calls
                if (
                    currentUntyingTask instanceof
                        com.tiedup.remake.tasks.UntyingPlayerTask untyingPlayerTask
                ) {
                    net.minecraft.world.entity.LivingEntity target =
                        untyingPlayerTask.getTargetEntity();
                    if (target != null && target.isAlive()) {
                        // Check if player is looking at target and close enough
                        double distance = player.distanceTo(target);
                        boolean isLookingAtTarget = isPlayerLookingAtEntity(
                            player,
                            target,
                            4.0
                        );

                        if (
                            distance <= 4.0 &&
                            isLookingAtTarget &&
                            player.hasLineOfSight(target)
                        ) {
                            // Player is still targeting - auto-update the task
                            currentUntyingTask.update();
                        }
                    }
                }

                // Process tick (increment if active, decrement if not)
                currentUntyingTask.tick();
                // Send progress packets to update UI
                currentUntyingTask.sendProgressPackets();

                // Check if task stopped (completed or cancelled due to no progress)
                if (currentUntyingTask.isStopped()) {
                    playerState.tasks().setCurrentUntyingTask(null);
                    TiedUpMod.LOGGER.debug(
                        "[RESTRAINT] {} untying task ended (tick update)",
                        player.getName().getString()
                    );
                }
            }

            // Process tying task tick (same progress-based system)
            com.tiedup.remake.tasks.TyingTask currentTyingTask =
                playerState.tasks().getCurrentTyingTask();
            if (currentTyingTask != null && !currentTyingTask.isStopped()) {
                // AUTO-UPDATE: Check if player is still targeting the same entity
                // This allows "hold click" behavior without needing repeated interactLivingEntity calls
                if (
                    currentTyingTask instanceof
                        com.tiedup.remake.tasks.TyingPlayerTask tyingPlayerTask
                ) {
                    net.minecraft.world.entity.LivingEntity target =
                        tyingPlayerTask.getTargetEntity();
                    boolean isSelfTying =
                        target != null && target.equals(player);

                    if (isSelfTying) {
                        // Self-tying: skip look-at/distance checks (player can't raycast to own hitbox)
                        // Progress is driven by continuous PacketSelfBondage packets from client
                        currentTyingTask.update();
                    } else if (target != null && target.isAlive()) {
                        // Tying another player: check distance + line of sight
                        double distance = player.distanceTo(target);
                        boolean isLookingAtTarget = isPlayerLookingAtEntity(
                            player,
                            target,
                            4.0
                        );

                        if (
                            distance <= 4.0 &&
                            isLookingAtTarget &&
                            player.hasLineOfSight(target)
                        ) {
                            currentTyingTask.update();
                        }
                    }
                }

                // Process tick (increment if active, decrement if not)
                currentTyingTask.tick();
                // Send progress packets to update UI
                currentTyingTask.sendProgressPackets();

                // Check if task stopped (completed or cancelled due to no progress)
                if (currentTyingTask.isStopped()) {
                    playerState.tasks().setCurrentTyingTask(null);
                    TiedUpMod.LOGGER.debug(
                        "[RESTRAINT] {} tying task ended (tick update)",
                        player.getName().getString()
                    );
                }
            }

            // Process force feeding task tick
            TimedInteractTask feedingTask = playerState.tasks().getCurrentFeedingTask();
            if (feedingTask != null && !feedingTask.isStopped()) {
                LivingEntity target = feedingTask.getTargetEntity();
                if (target != null && target.isAlive()) {
                    double distance = player.distanceTo(target);
                    boolean isLookingAtTarget = isPlayerLookingAtEntity(
                        player,
                        target,
                        4.0
                    );

                    if (
                        distance <= 4.0 &&
                        isLookingAtTarget &&
                        player.hasLineOfSight(target)
                    ) {
                        feedingTask.update();
                    }
                }

                feedingTask.tick();
                feedingTask.sendProgressPackets();

                if (feedingTask.isStopped()) {
                    playerState.tasks().setCurrentFeedingTask(null);
                    TiedUpMod.LOGGER.debug(
                        "[RESTRAINT] {} feeding task ended (tick update)",
                        player.getName().getString()
                    );
                }
            }
        }

        // Throttle: only check every N ticks (configurable via GameConstants) - per-player timing
        if (player.tickCount % GameConstants.SHOCK_COLLAR_CHECK_INTERVAL != 0) {
            return;
        }

        if (playerState != null) {
            playerState.checkAutoShockCollar();
        }
    }

    // ========== UNTYING MECHANIC ==========

    /**
     * Handle untying a tied entity (right-click with empty hand).
     *
     * Based on original PlayerKidnapActionsHandler.onUntyingTarget() (1.12.2)
     *
     * When a player right-clicks a tied entity (player or NPC) with an empty hand,
     * starts or continues an untying task to free them.
     */
    @SubscribeEvent
    public static void onUntyingTarget(
        PlayerInteractEvent.EntityInteract event
    ) {
        // Only run on server side
        if (event.getLevel().isClientSide) {
            return;
        }

        Entity target = event.getTarget();
        Player helper = event.getEntity();

        // Must be targeting a LivingEntity, using main hand, and have empty hand
        if (
            !(target instanceof LivingEntity targetEntity) ||
            event.getHand() != InteractionHand.MAIN_HAND ||
            !helper.getMainHandItem().isEmpty()
        ) {
            return;
        }

        // MCA villagers require Shift+click to untie (prevents conflict with MCA menu)
        if (
            com.tiedup.remake.compat.mca.MCACompat.isMCALoaded() &&
            com.tiedup.remake.compat.mca.MCACompat.isMCAVillager(
                targetEntity
            ) &&
            !helper.isShiftKeyDown()
        ) {
            return;
        }

        // Check if target is tied using IBondageState interface
        IBondageState targetState = KidnappedHelper.getKidnappedState(
            targetEntity
        );
        if (targetState == null || !targetState.isTiedUp()) {
            return;
        }

        // SECURITY: Distance and line-of-sight validation
        double maxUntieDistance = 4.0; // Max distance to untie (blocks)
        double distance = helper.distanceTo(targetEntity);
        if (distance > maxUntieDistance) {
            TiedUpMod.LOGGER.warn(
                "[RESTRAINT] {} tried to untie {} from too far away ({} blocks)",
                helper.getName().getString(),
                targetEntity.getName().getString(),
                String.format("%.1f", distance)
            );
            return;
        }

        // Check line-of-sight (helper must be able to see target)
        if (!helper.hasLineOfSight(targetEntity)) {
            TiedUpMod.LOGGER.warn(
                "[RESTRAINT] {} tried to untie {} without line of sight",
                helper.getName().getString(),
                targetEntity.getName().getString()
            );
            return;
        }

        // Check for Kidnapper fight back - block untying if Kidnapper is nearby
        if (
            targetEntity instanceof
                com.tiedup.remake.entities.AbstractTiedUpNpc npc
        ) {
            if (
                npc.getCaptor() instanceof EntityKidnapper kidnapper &&
                kidnapper.isAlive()
            ) {
                double distanceToKidnapper = helper.distanceTo(kidnapper);
                double fightBackRange = 16.0; // Kidnapper notices within 16 blocks

                if (distanceToKidnapper <= fightBackRange) {
                    // Trigger Kidnapper fight back by setting helper as "attacker"
                    // This activates KidnapperFightBackGoal which handles pursuit and attack
                    kidnapper.setLastAttacker(helper);

                    TiedUpMod.LOGGER.debug(
                        "[RESTRAINT] {} tried to untie {}, {} fights back!",
                        helper.getName().getString(),
                        npc.getName().getString(),
                        kidnapper.getName().getString()
                    );

                    // Block untying - send message to player
                    helper.displayClientMessage(
                        Component.translatable(
                            "tiedup.message.kidnapper_guards_captive"
                        ),
                        true
                    );
                    return;
                }
            }
        }

        // Check for Kidnapper fight back - block untying if player is captive or job worker
        if (targetEntity instanceof Player targetPlayer) {
            List<EntityKidnapper> nearbyKidnappers = helper
                .level()
                .getEntitiesOfClass(
                    EntityKidnapper.class,
                    helper.getBoundingBox().inflate(16.0)
                );

            for (EntityKidnapper kidnapper : nearbyKidnappers) {
                if (!kidnapper.isAlive()) continue;

                // Check if player is kidnapper's current captive (held by leash)
                IBondageState captive = kidnapper.getCaptive();
                boolean isCaptive =
                    captive != null && captive.asLivingEntity() == targetPlayer;

                // Check if player is kidnapper's job worker
                UUID workerUUID = kidnapper.getJobWorkerUUID();
                boolean isJobWorker =
                    workerUUID != null &&
                    workerUUID.equals(targetPlayer.getUUID());

                if (isCaptive || isJobWorker) {
                    // Trigger Kidnapper fight back
                    kidnapper.setLastAttacker(helper);

                    TiedUpMod.LOGGER.debug(
                        "[RESTRAINT] {} tried to untie {} (captive={}, worker={}), {} fights back!",
                        helper.getName().getString(),
                        targetPlayer.getName().getString(),
                        isCaptive,
                        isJobWorker,
                        kidnapper.getNpcName()
                    );

                    // Block untying - send message to player
                    helper.displayClientMessage(
                        Component.translatable(
                            "tiedup.message.kidnapper_guards_captive"
                        ),
                        true
                    );
                    return;
                }
            }
        }

        // Check if helper is tied using IBondageState interface
        IBondageState helperKidnappedState = KidnappedHelper.getKidnappedState(
            helper
        );
        if (helperKidnappedState == null || helperKidnappedState.isTiedUp()) {
            return;
        }

        // Get PlayerBindState for task management (helper only)
        PlayerBindState helperState = PlayerBindState.getInstance(helper);
        if (helperState == null) {
            return;
        }

        // Block untying while force feeding
        TimedInteractTask activeFeedTask = helperState.tasks().getCurrentFeedingTask();
        if (activeFeedTask != null && !activeFeedTask.isStopped()) {
            return;
        }

        // Get untying duration (default: 10 seconds)
        int untyingSeconds = getUntyingDuration(helper);

        // Non-owners take 3x longer and trigger alert to owners
        if (
            targetEntity instanceof
                com.tiedup.remake.entities.AbstractTiedUpNpc npc
        ) {
            if (!npc.isCollarOwner(helper)) {
                // Non-owner: triple the untying time
                untyingSeconds *= 3;

                // Alert all collar owners
                alertCollarOwners(npc, helper);

                TiedUpMod.LOGGER.debug(
                    "[RESTRAINT] Non-owner {} trying to free {} ({}s)",
                    helper.getName().getString(),
                    npc.getNpcName(),
                    untyingSeconds
                );
            }
        }

        // Get current untying task (if any)
        UntyingTask currentTask = helperState.tasks().getCurrentUntyingTask();

        // Check if we should start a new task or continue existing one
        if (
            currentTask == null ||
            !currentTask.isSameTarget(targetEntity) ||
            currentTask.isStopped()
        ) {
            // Create new untying task (unified for Players and NPCs)
            UntyingPlayerTask newTask = new UntyingPlayerTask(
                targetState,
                targetEntity,
                untyingSeconds,
                helper.level(),
                helper
            );

            // Start new task
            helperState.tasks().setCurrentUntyingTask(newTask);
            newTask.setUpTargetState(); // Initialize target's restraint state
            newTask.start();
            currentTask = newTask;

            TiedUpMod.LOGGER.debug(
                "[RESTRAINT] {} started untying {} ({} seconds)",
                helper.getName().getString(),
                targetEntity.getName().getString(),
                untyingSeconds
            );
        } else {
            // Continue existing task - ensure helper is set
            if (currentTask instanceof UntyingPlayerTask playerTask) {
                playerTask.setHelper(helper);
            }
        }

        // Mark this tick as active (progress will increase in onPlayerTick)
        // The tick() method in onPlayerTick handles progress increment/decrement
        currentTask.update();
    }

    /**
     * Check if a player is looking at a specific entity (raycast).
     *
     * @param player The player
     * @param target The target entity
     * @param maxDistance Maximum distance to check
     * @return true if player is looking at the target entity
     */
    private static boolean isPlayerLookingAtEntity(
        Player player,
        net.minecraft.world.entity.LivingEntity target,
        double maxDistance
    ) {
        // Get player's look vector
        net.minecraft.world.phys.Vec3 eyePos = player.getEyePosition(1.0F);
        net.minecraft.world.phys.Vec3 lookVec = player.getLookAngle();
        net.minecraft.world.phys.Vec3 endPos = eyePos.add(
            lookVec.x * maxDistance,
            lookVec.y * maxDistance,
            lookVec.z * maxDistance
        );

        // Check if raycast hits the target entity
        net.minecraft.world.phys.AABB targetBounds = target
            .getBoundingBox()
            .inflate(0.3);
        java.util.Optional<net.minecraft.world.phys.Vec3> hit =
            targetBounds.clip(eyePos, endPos);

        return hit.isPresent();
    }

    /**
     * Get the untying duration in seconds from GameRule.
     *
     * @param player The player (for accessing world/GameRules)
     * @return Duration in seconds (default: 10)
     */
    private static int getUntyingDuration(Player player) {
        return SettingsAccessor.getUntyingPlayerTime(
            player.level().getGameRules()
        );
    }

    // ========== FORCE FEEDING MECHANIC ==========

    /**
     * Handle force feeding a gagged entity (right-click with food).
     *
     * When a player right-clicks a gagged entity (player or NPC) while holding food,
     * starts or continues a force feeding task.
     */
    @SubscribeEvent
    public static void onForceFeedingTarget(
        PlayerInteractEvent.EntityInteract event
    ) {
        if (event.getLevel().isClientSide) {
            return;
        }

        Entity target = event.getTarget();
        Player feeder = event.getEntity();

        if (
            !(target instanceof LivingEntity targetEntity) ||
            event.getHand() != InteractionHand.MAIN_HAND
        ) {
            return;
        }

        ItemStack heldItem = feeder.getMainHandItem();
        if (heldItem.isEmpty() || !heldItem.getItem().isEdible()) {
            return;
        }

        // Target must have IBondageState state and be gagged
        IBondageState targetState = KidnappedHelper.getKidnappedState(
            targetEntity
        );
        if (targetState == null || !targetState.isGagged()) {
            return;
        }

        // Feeder must not be tied up
        IBondageState feederState = KidnappedHelper.getKidnappedState(feeder);
        if (feederState != null && feederState.isTiedUp()) {
            return;
        }

        // Distance and line-of-sight validation
        double distance = feeder.distanceTo(targetEntity);
        if (distance > 4.0) {
            return;
        }
        if (!feeder.hasLineOfSight(targetEntity)) {
            return;
        }

        // Get feeder's PlayerBindState for task management
        PlayerBindState feederBindState = PlayerBindState.getInstance(feeder);
        if (feederBindState == null) {
            return;
        }

        // Block feeding while untying
        UntyingTask activeUntieTask = feederBindState.tasks().getCurrentUntyingTask();
        if (activeUntieTask != null && !activeUntieTask.isStopped()) {
            return;
        }

        // Get current feeding task (if any)
        TimedInteractTask currentTask = feederBindState.tasks().getCurrentFeedingTask();

        if (
            currentTask == null ||
            !currentTask.isSameTarget(targetEntity) ||
            currentTask.isStopped()
        ) {
            // Create new force feeding task (5 seconds)
            ForceFeedingTask newTask = new ForceFeedingTask(
                targetState,
                targetEntity,
                5,
                feeder.level(),
                feeder,
                heldItem,
                feeder.getInventory().selected
            );

            feederBindState.tasks().setCurrentFeedingTask(newTask);
            newTask.setUpTargetState();
            newTask.start();
            currentTask = newTask;

            TiedUpMod.LOGGER.debug(
                "[RESTRAINT] {} started force feeding {} (5 seconds)",
                feeder.getName().getString(),
                targetEntity.getName().getString()
            );
        } else {
            // Continue existing task - ensure feeder is set
            if (currentTask instanceof ForceFeedingTask feedTask) {
                feedTask.setFeeder(feeder);
            }
        }

        currentTask.update();

        // Cancel to prevent mobInteract (avoids instant NPC feed)
        event.setCancellationResult(InteractionResult.SUCCESS);
        event.setCanceled(true);
    }

    /**
     * Alert all collar owners that someone is trying to free their slave.
     *
     * @param slave The slave being freed
     * @param liberator The player trying to free them
     */
    private static void alertCollarOwners(
        com.tiedup.remake.entities.AbstractTiedUpNpc slave,
        Player liberator
    ) {
        if (!(slave.level() instanceof ServerLevel serverLevel)) return;

        ItemStack collar = slave.getEquipment(BodyRegionV2.NECK);
        if (collar.isEmpty() || !CollarHelper.isCollar(collar)) {
            return;
        }

        List<UUID> owners = CollarHelper.getOwners(collar);
        if (owners.isEmpty()) return;

        // Create alert packet
        PacketSlaveBeingFreed alertPacket = new PacketSlaveBeingFreed(
            slave.getNpcName(),
            liberator.getName().getString(),
            slave.blockPosition().getX(),
            slave.blockPosition().getY(),
            slave.blockPosition().getZ()
        );

        // Send to all online owners
        for (UUID ownerUUID : owners) {
            ServerPlayer owner = serverLevel
                .getServer()
                .getPlayerList()
                .getPlayer(ownerUUID);
            if (owner != null && owner != liberator) {
                ModNetwork.sendToPlayer(alertPacket, owner);
            }
        }
    }
}
