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

import com.tiedup.remake.cells.CampOwnership;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityKidnapper;
import java.util.EnumSet;
import java.util.List;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.ai.goal.Goal;

/**
 * AI Goal that forces kidnappers to disperse when too many are near the camp center.
 *
 * Problem: All kidnappers cluster around cells/trader, creating unrealistic crowds.
 * Solution: If more than MAX_NEAR_CENTER kidnappers are within CHECK_RADIUS of camp center,
 * excess kidnappers in idle states are forced to move away.
 *
 * Only affects: GUARD, PATROL, IDLE, HUNT states
 * Does NOT affect: CAPTURE, TRANSPORT, PUNISH, ALERT, SELLING, JOB_WATCH (active jobs)
 *
 * Hunters are sent further away (50 blocks) than regular kidnappers (40 blocks).
 */
public class KidnapperDispersalGoal extends Goal {

    private final EntityKidnapper kidnapper;

    /** Maximum kidnappers allowed near camp center */
    private static final int MAX_NEAR_CENTER = 8;

    /** Radius to check for overcrowding (blocks) - covers typical fortress size */
    private static final int CHECK_RADIUS = 40;

    /** Distance to send regular kidnappers away */
    private static final int DISPERSAL_DISTANCE = 40;

    /** Distance to send hunters away */
    private static final int HUNTER_DISPERSAL_DISTANCE = 50;

    /** Speed when dispersing */
    private static final double DISPERSAL_SPEED = 0.9D;

    /** Target position to move to */
    private BlockPos dispersalTarget;

    /** Ticks since dispersal started */
    private int dispersalTicks;

    /** Max ticks for dispersal (60 seconds) */
    private static final int MAX_DISPERSAL_TICKS = 1200;

    /** Ticks without progress before teleporting out */
    private static final int STUCK_TELEPORT_THRESHOLD = 200; // 10 seconds

    /** Last recorded position for stuck detection */
    private BlockPos lastPosition;

    /** Ticks spent stuck in same position */
    private int stuckTicks;

    /** Cooldown between dispersal checks (5 seconds) */
    private int checkCooldown = 0;
    private static final int CHECK_COOLDOWN_DURATION = 100;

    public KidnapperDispersalGoal(EntityKidnapper kidnapper) {
        this.kidnapper = kidnapper;
        this.setFlags(EnumSet.of(Goal.Flag.MOVE));
    }

    @Override
    public boolean canUse() {
        // Cooldown check
        if (this.checkCooldown > 0) {
            this.checkCooldown--;
            return false;
        }
        this.checkCooldown = CHECK_COOLDOWN_DURATION;

        // Must be associated with a camp
        if (this.kidnapper.getAssociatedStructure() == null) {
            return false;
        }

        // Don't disperse if tied up
        if (this.kidnapper.isTiedUp()) {
            return false;
        }

        // Only disperse if in an "idle" state (not doing active job)
        KidnapperState state = this.kidnapper.getCurrentState();
        if (!isDispersibleState(state)) {
            return false;
        }

        // Check if overcrowded
        return shouldDisperse();
    }

    /**
     * Check if the current state allows dispersal.
     * Only idle-like states should be forced to disperse.
     */
    private boolean isDispersibleState(KidnapperState state) {
        return (
            state == KidnapperState.GUARD ||
            state == KidnapperState.PATROL ||
            state == KidnapperState.IDLE ||
            state == KidnapperState.HUNT
        );
    }

    /**
     * Check if this kidnapper should disperse due to overcrowding.
     */
    private boolean shouldDisperse() {
        if (!(this.kidnapper.level() instanceof ServerLevel serverLevel)) {
            return false;
        }

        // Get camp center
        BlockPos campCenter = getCampCenter(serverLevel);
        if (campCenter == null) {
            return false;
        }

        // Check distance to camp center
        double distToCenterSq = this.kidnapper.distanceToSqr(
            campCenter.getX() + 0.5,
            campCenter.getY(),
            campCenter.getZ() + 0.5
        );

        // If already far from center, no need to disperse
        double checkRadiusSq = CHECK_RADIUS * CHECK_RADIUS;
        if (distToCenterSq > checkRadiusSq) {
            return false;
        }

        // Count kidnappers near center
        List<EntityKidnapper> nearbyKidnappers = serverLevel.getEntitiesOfClass(
            EntityKidnapper.class,
            new net.minecraft.world.phys.AABB(
                campCenter.getX() - CHECK_RADIUS,
                campCenter.getY() - 10,
                campCenter.getZ() - CHECK_RADIUS,
                campCenter.getX() + CHECK_RADIUS,
                campCenter.getY() + 10,
                campCenter.getZ() + CHECK_RADIUS
            ),
            k ->
                k.getAssociatedStructure() != null &&
                k
                    .getAssociatedStructure()
                    .equals(this.kidnapper.getAssociatedStructure())
        );

        // If not overcrowded, no need to disperse
        if (nearbyKidnappers.size() <= MAX_NEAR_CENTER) {
            return false;
        }

        // Overcrowded! But only disperse if we're not in the "top 5" by some priority
        // Priority: active states first, then by distance (closest stay)
        // Sort by: 1) active state (stays), 2) distance to center (closest stays)
        nearbyKidnappers.sort((a, b) -> {
            // Active states have priority to stay
            boolean aActive = !isDispersibleState(a.getCurrentState());
            boolean bActive = !isDispersibleState(b.getCurrentState());
            if (aActive != bActive) {
                return aActive ? -1 : 1; // Active stays (sorted first)
            }
            // Otherwise sort by distance (closest first)
            double aDist = a.distanceToSqr(
                campCenter.getX(),
                campCenter.getY(),
                campCenter.getZ()
            );
            double bDist = b.distanceToSqr(
                campCenter.getX(),
                campCenter.getY(),
                campCenter.getZ()
            );
            return Double.compare(aDist, bDist);
        });

        // Check if this kidnapper is in the "must disperse" group (beyond top 5)
        int ourIndex = nearbyKidnappers.indexOf(this.kidnapper);
        return ourIndex >= MAX_NEAR_CENTER;
    }

    @Override
    public boolean canContinueToUse() {
        // Stop if tied up
        if (this.kidnapper.isTiedUp()) {
            return false;
        }

        // Stop if got a target (should be chasing)
        if (this.kidnapper.getTarget() != null) {
            return false;
        }

        // Stop if reached target
        if (this.dispersalTarget != null && reachedTarget()) {
            return false;
        }

        // Stop after max time
        if (this.dispersalTicks >= MAX_DISPERSAL_TICKS) {
            return false;
        }

        // Stop if state changed to active job
        if (!isDispersibleState(this.kidnapper.getCurrentState())) {
            return false;
        }

        return true;
    }

    @Override
    public void start() {
        this.dispersalTicks = 0;
        this.stuckTicks = 0;
        this.lastPosition = this.kidnapper.blockPosition();
        this.dispersalTarget = calculateDispersalTarget();

        if (this.dispersalTarget != null) {
            TiedUpMod.LOGGER.debug(
                "[KidnapperDispersalGoal] {} dispersing to {} (hunter: {})",
                this.kidnapper.getNpcName(),
                this.dispersalTarget,
                this.kidnapper.isHunter()
            );
        }
    }

    @Override
    public void stop() {
        this.kidnapper.getNavigation().stop();
        this.dispersalTarget = null;
        // Reset cooldown to avoid immediate re-check
        this.checkCooldown = CHECK_COOLDOWN_DURATION * 2;
    }

    @Override
    public void tick() {
        this.dispersalTicks++;

        if (this.dispersalTarget == null) {
            return;
        }

        // Check if stuck (not making progress)
        BlockPos currentPos = this.kidnapper.blockPosition();
        if (currentPos.equals(this.lastPosition)) {
            this.stuckTicks++;

            // If stuck for too long, teleport to dispersal target
            if (this.stuckTicks >= STUCK_TELEPORT_THRESHOLD) {
                teleportToDispersalTarget();
                return;
            }
        } else {
            // Made progress, reset stuck counter
            this.stuckTicks = 0;
            this.lastPosition = currentPos;
        }

        // Navigate to dispersal target
        if (
            this.dispersalTicks % 20 == 0 ||
            !this.kidnapper.getNavigation().isInProgress()
        ) {
            boolean pathStarted = this.kidnapper.getNavigation().moveTo(
                this.dispersalTarget.getX() + 0.5,
                this.dispersalTarget.getY(),
                this.dispersalTarget.getZ() + 0.5,
                DISPERSAL_SPEED
            );

            // If path couldn't be calculated, count as stuck
            if (!pathStarted) {
                this.stuckTicks += 20;
            }
        }
    }

    /**
     * Teleport kidnapper to dispersal target when stuck (can't path out of fortress).
     * FIX: After teleport, mark target as reached to prevent infinite teleport loop.
     */
    private void teleportToDispersalTarget() {
        if (!(this.kidnapper.level() instanceof ServerLevel serverLevel)) {
            return;
        }

        // Find a safe landing spot near the dispersal target
        int targetX = this.dispersalTarget.getX();
        int targetZ = this.dispersalTarget.getZ();

        // FIX: Try to find a valid ground position instead of using heightmap blindly
        // Heightmap returns surface level which may be a roof in structures
        BlockPos safePos = findSafeGroundPosition(
            serverLevel,
            targetX,
            targetZ
        );

        // Teleport
        this.kidnapper.teleportTo(
            safePos.getX() + 0.5,
            safePos.getY(),
            safePos.getZ() + 0.5
        );
        this.kidnapper.getNavigation().stop();

        TiedUpMod.LOGGER.info(
            "[KidnapperDispersalGoal] {} teleported out of fortress to {}",
            this.kidnapper.getNpcName(),
            safePos.toShortString()
        );

        // FIX: Mark target as reached to prevent re-teleporting in a loop
        // Update dispersalTarget to actual position so reachedTarget() returns true
        this.dispersalTarget = safePos;
        this.stuckTicks = 0;
        this.lastPosition = this.kidnapper.blockPosition();
    }

    /**
     * Find a safe ground position for teleportation.
     * Searches for a valid 2-block air space with solid ground below.
     */
    private BlockPos findSafeGroundPosition(
        ServerLevel level,
        int targetX,
        int targetZ
    ) {
        // Start from heightmap surface and search downward for valid ground
        int surfaceY = level.getHeight(
            net.minecraft.world.level.levelgen.Heightmap.Types.MOTION_BLOCKING_NO_LEAVES,
            targetX,
            targetZ
        );

        // Search downward for a valid position (2 blocks air, 1 block solid below)
        for (int y = surfaceY; y > level.getMinBuildHeight() + 2; y--) {
            BlockPos testPos = new BlockPos(targetX, y, targetZ);
            if (isValidStandingPosition(level, testPos)) {
                return testPos;
            }
        }

        // Fallback: use heightmap position
        return new BlockPos(targetX, surfaceY, targetZ);
    }

    /**
     * Check if a position is valid for standing (2 blocks air, solid floor).
     */
    private boolean isValidStandingPosition(ServerLevel level, BlockPos pos) {
        // Need air at feet and head level
        if (!level.getBlockState(pos).isAir()) {
            return false;
        }
        if (!level.getBlockState(pos.above()).isAir()) {
            return false;
        }
        // Need solid ground below
        return level.getBlockState(pos.below()).isSolid();
    }

    /**
     * Check if reached dispersal target.
     */
    private boolean reachedTarget() {
        if (this.dispersalTarget == null) return true;

        double distSq = this.kidnapper.distanceToSqr(
            this.dispersalTarget.getX() + 0.5,
            this.dispersalTarget.getY(),
            this.dispersalTarget.getZ() + 0.5
        );

        return distSq < 16.0; // Within 4 blocks
    }

    /**
     * Calculate a position to disperse to.
     */
    private BlockPos calculateDispersalTarget() {
        if (!(this.kidnapper.level() instanceof ServerLevel serverLevel)) {
            return null;
        }

        BlockPos campCenter = getCampCenter(serverLevel);
        if (campCenter == null) {
            return null;
        }

        int dispersalDist = this.kidnapper.isHunter()
            ? HUNTER_DISPERSAL_DISTANCE
            : DISPERSAL_DISTANCE;

        // Calculate direction away from camp center
        double dx = this.kidnapper.getX() - campCenter.getX();
        double dz = this.kidnapper.getZ() - campCenter.getZ();
        double dist = Math.sqrt(dx * dx + dz * dz);

        // If very close to center, pick random direction
        if (dist < 1) {
            double angle =
                this.kidnapper.getRandom().nextDouble() * Math.PI * 2;
            dx = Math.cos(angle);
            dz = Math.sin(angle);
        } else {
            dx /= dist;
            dz /= dist;
        }

        // Add some randomness to the direction (±30 degrees)
        double angleOffset =
            ((this.kidnapper.getRandom().nextDouble() - 0.5) * Math.PI) / 3;
        double cos = Math.cos(angleOffset);
        double sin = Math.sin(angleOffset);
        double newDx = dx * cos - dz * sin;
        double newDz = dx * sin + dz * cos;

        // Calculate target position
        int targetX = campCenter.getX() + (int) (newDx * dispersalDist);
        int targetZ = campCenter.getZ() + (int) (newDz * dispersalDist);

        // FIX: Use safe ground position finder instead of raw heightmap
        return findSafeGroundPosition(serverLevel, targetX, targetZ);
    }

    /**
     * Get the camp center position.
     */
    private BlockPos getCampCenter(ServerLevel serverLevel) {
        java.util.UUID campId = this.kidnapper.getAssociatedStructure();
        if (campId == null) {
            return null;
        }

        CampOwnership ownership = CampOwnership.get(serverLevel);
        CampOwnership.CampData camp = ownership.getCamp(campId);
        if (camp != null) {
            return camp.getCenter();
        }

        return null;
    }
}
