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

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

/**
 * AI Goal for EntityKidnapper to patrol far from camp looking for prey.
 *
 * This goal activates for "hunter" kidnappers and makes them patrol
 * at a distance of 80-150 blocks from the camp center.
 *
 * Purpose: Spread out NPCs so they don't all cluster in the camp structure.
 * Hunters actively look for players in the wilderness around the camp.
 */
public class KidnapperHuntGoal extends Goal {

    private final EntityKidnapper kidnapper;

    /** Minimum hunt radius from camp center */
    private static final int MIN_HUNT_RADIUS = 80;

    /** Maximum hunt radius from camp center */
    private static final int MAX_HUNT_RADIUS = 150;

    /** Maximum distance before forced return (further than hunt radius) */
    private static final int MAX_DISTANCE_FROM_CAMP = 200;

    /** Ticks between hunt point changes */
    private static final int HUNT_INTERVAL = 400; // 20 seconds

    /** Current hunt target position */
    private BlockPos huntTarget;

    /** Ticks since last hunt point change */
    private int huntTimer;

    /** Speed when hunting (moderate pace) */
    private static final double HUNT_SPEED = 0.9D;

    /** Speed when returning to camp (faster) */
    private static final double RETURN_SPEED = 1.0D;

    /** Whether currently returning to camp */
    private boolean returningToCamp;

    /** Cooldown after failing to find a hunt point */
    private int huntFailureCooldown = 0;

    /** Cooldown duration after hunt point selection fails */
    private static final int HUNT_FAILURE_COOLDOWN = 100;

    /** Duration of a hunt session before returning to camp (5-10 minutes) */
    private int huntSessionDuration;

    /** Ticks spent in current hunt session */
    private int huntSessionTimer;

    /** Minimum hunt session duration (5 minutes) */
    private static final int MIN_HUNT_SESSION = 6000;

    /** Maximum hunt session duration (10 minutes) */
    private static final int MAX_HUNT_SESSION = 12000;

    /** Cooldown before starting another hunt (30-60 seconds) */
    private int huntCooldown = 0;

    /** Stuck detection — 2 detour attempts then pick new hunt point */
    private final StuckDetector stuckDetector = new StuckDetector(60, 2);

    /** Minimum cooldown between hunts (30 seconds) */
    private static final int MIN_HUNT_COOLDOWN = 600;

    /** Maximum cooldown between hunts (60 seconds) */
    private static final int MAX_HUNT_COOLDOWN = 1200;

    public KidnapperHuntGoal(EntityKidnapper kidnapper) {
        this.kidnapper = kidnapper;
        this.huntTarget = null;
        this.huntTimer = 0;
        this.returningToCamp = false;
        this.setFlags(EnumSet.of(Goal.Flag.MOVE));
    }

    @Override
    public boolean canUse() {
        // Only hunters can use this goal
        if (!this.kidnapper.isHunter()) {
            return false;
        }

        // Cooldown check
        if (this.huntCooldown > 0) {
            this.huntCooldown--;
            return false;
        }

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

        // Don't hunt if has a target (should be chasing)
        if (this.kidnapper.getTarget() != null) {
            return false;
        }

        // Don't hunt if has a captive (should be transporting)
        if (this.kidnapper.hasCaptives()) {
            return false;
        }

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

        return true;
    }

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

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

        // Stop if got a captive
        if (this.kidnapper.hasCaptives()) {
            return false;
        }

        // Stop if no longer associated with a camp
        if (this.kidnapper.getAssociatedStructure() == null) {
            return false;
        }

        // Stop if hunt session is over
        if (this.huntSessionTimer >= this.huntSessionDuration) {
            return false;
        }

        return true;
    }

    @Override
    public void start() {
        this.huntTimer = 0;
        this.huntTarget = null;
        this.returningToCamp = false;
        this.huntSessionTimer = 0;
        this.stuckDetector.reset();
        this.huntSessionDuration =
            MIN_HUNT_SESSION +
            this.kidnapper.getRandom().nextInt(
                MAX_HUNT_SESSION - MIN_HUNT_SESSION
            );

        // Set state to HUNT
        this.kidnapper.setCurrentState(KidnapperState.HUNT);

        TiedUpMod.LOGGER.debug(
            "[KidnapperHuntGoal] {} starting hunt session (duration: {} ticks)",
            this.kidnapper.getNpcName(),
            this.huntSessionDuration
        );
    }

    @Override
    public void stop() {
        this.kidnapper.getNavigation().stop();
        this.huntTarget = null;
        this.returningToCamp = false;
        this.stuckDetector.reset();

        // Set cooldown before next hunt
        this.huntCooldown =
            MIN_HUNT_COOLDOWN +
            this.kidnapper.getRandom().nextInt(
                MAX_HUNT_COOLDOWN - MIN_HUNT_COOLDOWN
            );

        // Return to IDLE state if still in HUNT
        if (this.kidnapper.getCurrentState() == KidnapperState.HUNT) {
            this.kidnapper.setCurrentState(KidnapperState.IDLE);
        }

        TiedUpMod.LOGGER.debug(
            "[KidnapperHuntGoal] {} ending hunt, cooldown: {} ticks",
            this.kidnapper.getNpcName(),
            this.huntCooldown
        );
    }

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

        // Get camp center
        BlockPos campCenter = getCampCenter();
        if (campCenter == null) return;

        double distanceFromCamp = this.kidnapper.distanceToSqr(
            campCenter.getX(),
            campCenter.getY(),
            campCenter.getZ()
        );
        double maxDistSq = MAX_DISTANCE_FROM_CAMP * MAX_DISTANCE_FROM_CAMP;

        // Too far! Return to camp perimeter
        if (distanceFromCamp > maxDistSq) {
            if (!this.returningToCamp) {
                this.returningToCamp = true;
                TiedUpMod.LOGGER.debug(
                    "[KidnapperHuntGoal] {} too far from camp, returning",
                    this.kidnapper.getNpcName()
                );
            }
            returnToCampPerimeter(campCenter);
            return;
        }

        // Back in range, resume hunt
        if (this.returningToCamp) {
            this.returningToCamp = false;
            this.huntTarget = null;
            this.huntTimer = 0;
        }

        // Update hunt timer
        this.huntTimer++;

        // Handle failure cooldown
        if (this.huntFailureCooldown > 0) {
            this.huntFailureCooldown--;
            return;
        }

        // Check if need new hunt point
        if (
            this.huntTarget == null ||
            this.huntTimer >= HUNT_INTERVAL ||
            reachedHuntTarget()
        ) {
            selectNewHuntPoint(campCenter);
            this.huntTimer = 0;
            this.stuckDetector.reset();

            if (this.huntTarget == null) {
                this.huntFailureCooldown = HUNT_FAILURE_COOLDOWN;
            }
        }

        // Update stuck detection
        this.stuckDetector.update(this.kidnapper);

        if (this.stuckDetector.isStuck()) {
            if (this.stuckDetector.shouldTeleport()) {
                // Pick a new hunt point instead of teleporting
                this.huntTarget = null;
                this.stuckDetector.reset();
                TiedUpMod.LOGGER.debug(
                    "[KidnapperHuntGoal] {} stuck, picking new hunt point",
                    this.kidnapper.getNpcName()
                );
            } else if (
                this.kidnapper.level() instanceof ServerLevel serverLevel
            ) {
                BlockPos detour = this.stuckDetector.tryDetour(
                    serverLevel,
                    this.kidnapper.blockPosition(),
                    5
                );
                if (detour != null) {
                    this.kidnapper.getNavigation().moveTo(
                        detour.getX() + 0.5,
                        detour.getY(),
                        detour.getZ() + 0.5,
                        HUNT_SPEED
                    );
                }
            }
            return;
        }

        // Skip normal navigation if in detour
        if (this.stuckDetector.isInDetour()) {
            return;
        }

        // Move to hunt target
        if (
            this.huntTarget != null &&
            (this.huntTimer % 10 == 0 ||
                !this.kidnapper.getNavigation().isInProgress())
        ) {
            boolean pathFound = this.kidnapper.getNavigation().moveTo(
                this.huntTarget.getX() + 0.5,
                this.huntTarget.getY(),
                this.huntTarget.getZ() + 0.5,
                HUNT_SPEED
            );
            if (!pathFound) {
                this.stuckDetector.onPathFailed();
            }
        }
    }

    /**
     * Get the camp center position.
     */
    @javax.annotation.Nullable
    private BlockPos getCampCenter() {
        if (!(this.kidnapper.level() instanceof ServerLevel serverLevel)) {
            return null;
        }

        java.util.UUID campId = this.kidnapper.getAssociatedStructure();
        if (campId == null) {
            return null;
        }

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

        return null;
    }

    /**
     * Return to camp perimeter (not center, stay at hunt distance).
     */
    private void returnToCampPerimeter(BlockPos campCenter) {
        // Calculate direction from camp to kidnapper
        double dx = this.kidnapper.getX() - campCenter.getX();
        double dz = this.kidnapper.getZ() - campCenter.getZ();
        double dist = Math.sqrt(dx * dx + dz * dz);

        if (dist < 1) {
            // Already at camp, pick random direction
            double angle =
                this.kidnapper.getRandom().nextDouble() * Math.PI * 2;
            dx = Math.cos(angle);
            dz = Math.sin(angle);
        } else {
            dx /= dist;
            dz /= dist;
        }

        // Target: edge of hunt zone (MIN_HUNT_RADIUS from camp)
        double targetX = campCenter.getX() + dx * MIN_HUNT_RADIUS;
        double targetZ = campCenter.getZ() + dz * MIN_HUNT_RADIUS;

        this.kidnapper.getNavigation().moveTo(
            targetX,
            campCenter.getY(),
            targetZ,
            RETURN_SPEED
        );
    }

    /**
     * Check if reached current hunt target.
     */
    private boolean reachedHuntTarget() {
        if (this.huntTarget == null) return true;

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

        return distSq < 9.0; // Within 3 blocks
    }

    /**
     * Select a new hunt point in the hunt zone (80-150 blocks from camp).
     */
    private void selectNewHuntPoint(BlockPos campCenter) {
        Level level = this.kidnapper.level();

        // Try to find a valid hunt point
        for (int attempts = 0; attempts < 15; attempts++) {
            // Random angle
            double angle =
                this.kidnapper.getRandom().nextDouble() * Math.PI * 2;

            // Random distance within hunt zone
            int distance =
                MIN_HUNT_RADIUS +
                this.kidnapper.getRandom().nextInt(
                    MAX_HUNT_RADIUS - MIN_HUNT_RADIUS
                );

            int targetX =
                campCenter.getX() + (int) (Math.cos(angle) * distance);
            int targetZ =
                campCenter.getZ() + (int) (Math.sin(angle) * distance);
            int targetY = campCenter.getY();

            BlockPos testPos = new BlockPos(targetX, targetY, targetZ);

            // Search up and down for valid ground
            for (int yOffset = -10; yOffset <= 10; yOffset++) {
                BlockPos checkPos = testPos.offset(0, yOffset, 0);

                if (isValidHuntPoint(level, checkPos)) {
                    this.huntTarget = checkPos;
                    TiedUpMod.LOGGER.debug(
                        "[KidnapperHuntGoal] {} new hunt point: {} ({} blocks from camp)",
                        this.kidnapper.getNpcName(),
                        checkPos,
                        distance
                    );
                    return;
                }
            }
        }

        // Failed to find valid point
        this.huntTarget = null;
    }

    /**
     * Check if a position is valid for hunting (solid ground, air above).
     */
    private boolean isValidHuntPoint(Level level, BlockPos pos) {
        if (!KidnapperAIHelper.isValidGroundPosition(level, pos)) {
            return false;
        }

        // Check path is possible (not too far vertically from current position)
        double yDiff = Math.abs(pos.getY() - this.kidnapper.getY());
        if (yDiff > 20) {
            return false;
        }

        return true;
    }
}
