package com.tiedup.remake.entities.kidnapper.components;

import com.tiedup.remake.cells.CampOwnership;
import com.tiedup.remake.compat.mca.MCACompat;
import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.EntityKidnapperElite;
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.prison.PrisonerRecord;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.List;
import java.util.UUID;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.AABB;
import org.jetbrains.annotations.Nullable;

/**
 * KidnapperTargetSelector - Manages target selection and validation logic.
 *
 * Handles:
 * - isSuitableTarget() - Complex targeting logic with 8 categories of checks
 * - isTargetStillValidForChase() - Mid-chase validation (no line-of-sight)
 * - isTargetBeingPursuedByOther() - Competition detection
 * - getClosestSuitableTarget() - Proximity-based targeting
 * - Target tracking (currentTarget field)
 *
 * <p><b>VERY HIGH complexity</b> - Critical logic controlling kidnapper behavior.
 * Bugs here cause kidnappers to be passive (ignore everyone) or too aggressive
 * (target protected players). Preserve all conditions exactly.</p>
 */
public class KidnapperTargetSelector {

    // FIELDS

    /** Host callbacks */
    private final ITargetHost host;

    /** Entity reference (needed for type checks and competition detection) */
    private final EntityKidnapper entity;

    /** Current target being pursued */
    @Nullable
    private LivingEntity currentTarget;

    // CONSTRUCTOR

    public KidnapperTargetSelector(EntityKidnapper entity, ITargetHost host) {
        this.entity = entity;
        this.host = host;
    }

    // TARGET TRACKING

    /**
     * Get the current target being pursued.
     *
     * @return The target entity, or null if none
     */
    @Nullable
    public LivingEntity getTarget() {
        return this.currentTarget;
    }

    /**
     * Set the current target.
     *
     * @param target The target to pursue
     */
    public void setTarget(@Nullable LivingEntity target) {
        this.currentTarget = target;
    }

    // TARGET SUITABILITY (CRITICAL METHOD)

    /**
     * Check if an entity is a suitable target for capture.
     * This is the CRITICAL method controlling kidnapper behavior.
     *
     * Validation categories:
     * 1. Basic validity (null, alive, self, type)
     * 2. Kidnapper state (has captive, tied up, waiting for job, fleeing)
     * 3. Job worker protection
     * 4. Player-specific (creative/spectator, robbed immunity, grace period, ransom/labor, token)
     * 5. Kidnapped state (already captive, collar ownership)
     * 6. Kidnapping mode (whitelist/blacklist)
     * 7. Competition check (other kidnappers pursuing)
     * 8. Line of sight (expensive, checked last)
     *
     * @param entity The entity to evaluate
     * @return true if entity is a valid target, false otherwise
     */
    public boolean isSuitableTarget(LivingEntity entity) {
        if (entity == null) return false;
        if (!entity.isAlive()) return false;
        if (entity.isInvisible()) return false;
        // Note: hasLineOfSight check moved to end (expensive raycast)

        // Can't target self
        if (entity == this.entity) return false;

        // Can't target other kidnappers
        if (entity instanceof EntityKidnapper) return false;

        // Can't target labor guards (they're allies, not capture targets)
        if (
            entity instanceof com.tiedup.remake.entities.EntityLaborGuard
        ) return false;

        // Can't target if we already have a captive
        if (host.hasCaptives()) return false;

        // Can't target if we're tied up
        if (host.isTiedUp()) return false;

        // Can't target if we're waiting for a job to be completed
        if (host.isWaitingForJobToBeCompleted()) return false;

        // Can't target if we're fleeing (get out state)
        if (host.isGetOutState()) return false;

        // Can't target the current job worker
        UUID workerUUID = host.getJobManager().getJobWorkerUUID();
        if (workerUUID != null && entity.getUUID().equals(workerUUID)) {
            return false;
        }

        // FIX: Can't target job workers of OTHER kidnappers (wild jobs protection)
        // Prevents kidnapper B from capturing a player doing a job for kidnapper A
        if (host.level() instanceof ServerLevel serverLevel) {
            AABB searchBox = new AABB(entity.blockPosition()).inflate(64);
            List<EntityKidnapper> nearbyKidnappers =
                serverLevel.getEntitiesOfClass(
                    EntityKidnapper.class,
                    searchBox
                );
            for (EntityKidnapper otherKidnapper : nearbyKidnappers) {
                if (otherKidnapper == this.entity) continue;
                if (
                    otherKidnapper.getJobManager() != null &&
                    entity
                        .getUUID()
                        .equals(
                            otherKidnapper.getJobManager().getJobWorkerUUID()
                        )
                ) {
                    return false; // Protected - active job worker for another kidnapper
                }
            }
        }

        // Player-specific checks
        if (entity instanceof Player player) {
            if (player.isCreative() || player.isSpectator()) return false;

            // Robbed immunity check - this kidnapper recently robbed this player
            if (
                host.getAggressionSystem().hasRobbedImmunity(player.getUUID())
            ) {
                return false;
            }

            // UNIFIED CAPTIVITY CHECK - check if player is detained in a camp/cell
            // This prevents kidnappers from targeting players who are:
            // 1. Being transported (CAPTURED, BEING_TRANSPORTED)
            // 2. Imprisoned (in cell, working, returning, resting)
            // 3. Under post-release protection (grace period)
            if (host.level() instanceof ServerLevel serverLevel) {
                PrisonerManager manager = PrisonerManager.get(serverLevel);
                long currentTime = serverLevel.getGameTime();

                // Can't target if captive (transported/imprisoned) OR protected (grace period)
                if (!manager.isTargetable(player.getUUID(), currentTime)) {
                    return false; // Player is captive or protected
                }
            }

            // Token check - only protects from kidnappers LINKED to a camp
            // Non-linked kidnappers (wild spawns) ignore the token
            if (
                hasTokenInInventory(player) &&
                host.getAssociatedStructure() != null
            ) {
                return false; // Token protects from camp-linked kidnappers
            }
        }

        // Get entity's kidnapped state
        IBondageState state = KidnappedHelper.getKidnappedState(entity);
        if (state == null) return false;

        // Can't target if entity is already someone's captive (unless on pole)
        if (state.isCaptive() && !state.isTiedToPole()) {
            return false;
        }

        // Can't target if entity has a collar (is a slave) - UNLESS we own that collar
        // OR the collared player escaped from our camp (same camp kidnappers can recapture)
        // Self-collared entities (exploit) are treated as uncolllared — kidnappers ignore self-collars
        if (state.hasCollar()) {
            ItemStack collar = state.getEquipment(BodyRegionV2.NECK);
            if (CollarHelper.isCollar(collar)) {
                java.util.List<UUID> owners = CollarHelper.getOwners(collar);
                // Filter out self-collar (owner == wearer = exploit)
                java.util.List<UUID> realOwners = owners
                    .stream()
                    .filter(id -> !id.equals(entity.getUUID()))
                    .toList();
                if (
                    !realOwners.isEmpty() &&
                    !realOwners.contains(host.getUUID())
                ) {
                    // Not our collar directly - check if escaped prisoner from our camp
                    boolean sameCampEscapee = false;
                    if (
                        entity instanceof Player collarPlayer &&
                        host.level() instanceof ServerLevel sl
                    ) {
                        PrisonerRecord record = PrisonerManager.get(
                            sl
                        ).getRecord(collarPlayer.getUUID());
                        UUID prisonerCampId = record.getCampId();
                        UUID ourCampId = host.getAssociatedStructure();
                        if (
                            prisonerCampId != null &&
                            ourCampId != null &&
                            prisonerCampId.equals(ourCampId)
                        ) {
                            sameCampEscapee = true; // Escaped from our camp - recapture
                        }
                    }
                    if (!sameCampEscapee) {
                        return false; // Not our collar and not from our camp
                    }
                }
                // Our collar, self-collar, or same-camp escapee - continue targeting
            } else {
                return false; // Unknown collar type, don't target
            }
        }

        // If kidnapping mode is enabled, check blacklist/whitelist (players only)
        if (entity instanceof Player player && host.isKidnappingModeEnabled()) {
            if (!host.getCollarConfig().isValidKidnappingTarget(player)) {
                return false;
            }
        }

        // Check if another kidnapper is already pursuing this target (elite kidnappers bypass this)
        if (isTargetBeingPursuedByOther(entity)) {
            return false;
        }

        // Expensive raycast check - do this last after all cheap checks pass
        if (!host.getSensing().hasLineOfSight(entity)) return false;

        return true;
    }

    // CHASE VALIDATION

    /**
     * Check if target is still valid during active chase.
     * More lenient than isSuitableTarget - no line-of-sight requirement.
     * Allows chasing through forests and around obstacles.
     *
     * @param entity The target being chased
     * @return true if chase should continue, false otherwise
     */
    public boolean isTargetStillValidForChase(LivingEntity entity) {
        if (entity == null) return false;
        if (!entity.isAlive()) return false;
        if (entity.isInvisible()) return false;

        // Can't continue if we got tied up
        if (host.isTiedUp()) return false;

        // Can't continue if we're fleeing
        if (host.isGetOutState()) return false;

        // Player-specific checks
        if (entity instanceof Player player) {
            if (player.isCreative() || player.isSpectator()) return false;

            // UNIFIED CAPTIVITY CHECK - same as isSuitableTarget
            if (host.level() instanceof ServerLevel serverLevel) {
                PrisonerManager manager = PrisonerManager.get(serverLevel);
                long currentTime = serverLevel.getGameTime();

                // Can't chase if captive (transported/imprisoned) OR protected (grace period)
                if (!manager.isTargetable(player.getUUID(), currentTime)) {
                    return false; // Player is captive or protected
                }
            }
        }

        // Check if captured by someone else
        IBondageState state = KidnappedHelper.getKidnappedState(entity);
        if (state != null && state.isCaptive() && !state.isTiedToPole()) {
            // Captured by another - stop chase
            return false;
        }

        // Check if collared by a PLAYER (player may have collared during chase)
        // Other kidnappers' collars are fair game — kidnapper can steal from kidnapper
        if (state != null && state.hasCollar()) {
            ItemStack collar = state.getEquipment(BodyRegionV2.NECK);
            if (CollarHelper.isCollar(collar)) {
                java.util.List<UUID> owners = CollarHelper.getOwners(collar);
                if (!owners.isEmpty() && !owners.contains(host.getUUID())) {
                    // Check if any owner is a DIFFERENT player (not self-collared, not a kidnapper)
                    if (host.level() instanceof ServerLevel sl) {
                        for (UUID ownerId : owners) {
                            // Skip self-collared (exploit prevention: player puts collar on themselves)
                            if (ownerId.equals(entity.getUUID())) continue;
                            if (
                                sl
                                    .getServer()
                                    .getPlayerList()
                                    .getPlayer(ownerId) !=
                                null
                            ) {
                                return false; // Owned by another player — stop chase
                            }
                        }
                    }
                    // All owners are NPCs/kidnappers — continue chase
                }
            }
        }

        // NO LINE-OF-SIGHT CHECK - allow chasing through forests
        return true;
    }

    // COMPETITION DETECTION

    /**
     * Check if another kidnapper is already effectively pursuing this target.
     * Elite kidnappers bypass this check (always return false).
     *
     * Considers a target "pursued" only if:
     * - Pursuer is in active pursuit state (HUNT/CHASE)
     * - Pursuer is within 40 blocks
     * - Pursuer has active navigation OR is very close (< 5 blocks)
     *
     * @param target The target entity
     * @return true if another kidnapper is effectively pursuing, false otherwise
     */
    public boolean isTargetBeingPursuedByOther(LivingEntity target) {
        if (target == null) return false;

        // Elite kidnappers can always pursue, regardless of other kidnappers
        if (this.entity instanceof EntityKidnapperElite) {
            return false;
        }

        if (!(host.level() instanceof ServerLevel serverLevel)) {
            return false;
        }

        // Check nearby kidnappers (within 50 blocks)
        List<EntityKidnapper> nearbyKidnappers = serverLevel.getEntitiesOfClass(
            EntityKidnapper.class,
            target.getBoundingBox().inflate(50, 20, 50),
            k -> k != this.entity && k.isAlive()
        );

        for (EntityKidnapper other : nearbyKidnappers) {
            LivingEntity otherTarget = other.getTarget();
            if (otherTarget != null && otherTarget.equals(target)) {
                // Check if the other kidnapper is actually pursuing effectively
                KidnapperState otherState = other.getCurrentState();

                // Only consider "pursued" if kidnapper is in active pursuit state
                if (!otherState.isPursuit()) {
                    continue; // IDLE/GUARD/PATROL with stale target - ignore
                }

                // Check distance - if pursuer is too far (> 40 blocks), they're not effective
                double distSq = other.distanceToSqr(target);
                if (distSq > 1600) {
                    // > 40 blocks squared
                    continue;
                }

                // Check if pursuer has valid navigation path (can reach target)
                if (!other.getNavigation().isInProgress() && distSq > 25) {
                    // Not moving and far away - probably stuck
                    continue;
                }

                // This kidnapper is effectively pursuing the target
                return true;
            }
        }

        return false;
    }

    // PROXIMITY TARGETING

    /**
     * Find the closest suitable target within a given radius.
     * Search order: Players → Damsels → MCA villagers (if loaded)
     *
     * @param radius Search radius in blocks
     * @return The closest suitable target, or null if none found
     */
    @Nullable
    public LivingEntity getClosestSuitableTarget(int radius) {
        AABB searchBox = host.getBoundingBox().inflate(radius);

        // Search for players first (highest priority)
        List<Player> players = host
            .level()
            .getEntitiesOfClass(Player.class, searchBox);
        for (Player player : players) {
            if (this.isSuitableTarget(player)) {
                return player;
            }
        }

        // Then search for damsels
        List<EntityDamsel> damsels = host
            .level()
            .getEntitiesOfClass(EntityDamsel.class, searchBox);
        for (EntityDamsel damsel : damsels) {
            // EntityDamsel no longer includes kidnappers (separate hierarchies)
            if (this.isSuitableTarget(damsel)) {
                return damsel;
            }
        }

        // MCA Compatibility: Search for MCA villagers
        if (MCACompat.isMCALoaded()) {
            List<LivingEntity> entities = host
                .level()
                .getEntitiesOfClass(
                    LivingEntity.class,
                    searchBox,
                    MCACompat::isMCAVillager // Filter only MCA villagers
                );
            for (LivingEntity mcaVillager : entities) {
                if (this.isSuitableTarget(mcaVillager)) {
                    return mcaVillager;
                }
            }
        }

        return null;
    }

    // DISTANCE HELPERS

    /**
     * Check if close to current target (within 2 blocks).
     *
     * @return true if close to target, false otherwise
     */
    public boolean isCloseToTarget() {
        if (this.currentTarget == null) return false;
        return host.distanceTo(this.currentTarget) <= 2.0f;
    }

    /**
     * Check if too far from current target.
     *
     * @param radius Maximum acceptable distance
     * @return true if beyond radius, false otherwise
     */
    public boolean isTooFarFromTarget(int radius) {
        if (this.currentTarget == null) return false;
        return host.distanceTo(this.currentTarget) > radius;
    }

    // HELPER METHODS

    /**
     * Check if a player has a protection token in their inventory.
     * Tokens protect from camp-linked kidnappers only.
     *
     * @param player The player to check
     * @return true if player has token, false otherwise
     */
    public static boolean hasTokenInInventory(Player player) {
        for (int i = 0; i < player.getInventory().getContainerSize(); i++) {
            ItemStack stack = player.getInventory().getItem(i);
            if (!stack.isEmpty() && stack.getItem() == ModItems.TOKEN.get()) {
                return true;
            }
        }
        return false;
    }

    // NBT SERIALIZATION

    /**
     * Save target selector data to NBT.
     * Note: currentTarget is transient and not saved (recalculated on load).
     */
    public void saveToNBT(net.minecraft.nbt.CompoundTag tag) {
        // Target selector uses only transient runtime state
        // No persistent data needs to be saved
    }

    /**
     * Load target selector data from NBT.
     * Note: currentTarget is transient and not loaded.
     */
    public void loadFromNBT(net.minecraft.nbt.CompoundTag tag) {
        // Target selector uses only transient runtime state
        // No persistent data needs to be loaded
    }
}
