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 com.tiedup.remake.util.teleport.Position;
import java.util.ArrayList;
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;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;

/**
 * AI Goal for EntityKidnapper to patrol around their home area.
 *
 *
 * This goal:
 * 1. Activates when kidnapper has no target and no slave
 * 2. Makes kidnapper patrol around their home position
 * 3. Keeps kidnapper within patrol radius
 * 4. Returns to home if too far away
 *
 * Patrol behavior creates more realistic "guard" NPCs that protect an area.
 */
public class KidnapperPatrolGoal extends Goal {

    private final EntityKidnapper kidnapper;

    /** Default patrol radius if none set */
    private static final int DEFAULT_PATROL_RADIUS = 40;

    /** Maximum distance before forced return to home */
    private static final int MAX_DISTANCE_FROM_HOME = 80;

    /** Ticks between patrol point changes */
    private static final int PATROL_INTERVAL = 200; // 10 seconds

    /** Current patrol target */
    private BlockPos patrolTarget;

    /** Ticks since last patrol point change */
    private int patrolTimer;

    /** Speed when patrolling (slower than chasing) */
    private static final double PATROL_SPEED = 0.8D;

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

    /** Whether currently returning to home */
    private boolean returningHome;

    /** Cached patrol markers for efficiency */
    private List<BlockPos> cachedPatrolMarkers = new ArrayList<>();

    /** Current index in the patrol marker sequence */
    private int currentPatrolIndex = 0;

    /** Ticks until next marker cache refresh */
    private int markerCacheRefreshTimer = 0;

    /** Cooldown after failing to find a patrol point (avoids expensive retries) */
    private int patrolFailureCooldown = 0;

    /** Cooldown duration after patrol point selection fails (60 ticks = 3 seconds) */
    private static final int PATROL_FAILURE_COOLDOWN = 60;

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

    /** How often to refresh the marker cache (30 seconds) */
    private static final int MARKER_CACHE_REFRESH_INTERVAL = 600;

    /**
     * Create a new patrol goal.
     *
     * @param kidnapper The kidnapper entity
     */
    public KidnapperPatrolGoal(EntityKidnapper kidnapper) {
        this.kidnapper = kidnapper;
        this.patrolTarget = null;
        this.patrolTimer = 0;
        this.returningHome = false;
        this.setFlags(EnumSet.of(Goal.Flag.MOVE));
    }

    @Override
    public boolean canUse() {
        // Don't patrol if tied up
        if (this.kidnapper.isTiedUp()) {
            return false;
        }

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

        // Don't patrol if has a slave (should be doing slave management)
        if (this.kidnapper.hasCaptives()) {
            return false;
        }

        // Hunters use HUNT goal instead of PATROL (they patrol far from camp)
        if (this.kidnapper.isHunter()) {
            return false;
        }

        // Need a cell assigned OR be associated with a camp to patrol
        if (
            !this.kidnapper.hasCellAssignedFromCollar() &&
            this.kidnapper.getAssociatedStructure() == null
        ) {
            return false;
        }

        // 10% chance to skip patrol and allow free exploration via WaterAvoidingRandomStrollGoal
        if (this.kidnapper.getRandom().nextInt(10) == 0) {
            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 slave
        if (this.kidnapper.hasCaptives()) {
            return false;
        }

        // Stop if no cell assigned AND not associated with a camp
        if (
            !this.kidnapper.hasCellAssignedFromCollar() &&
            this.kidnapper.getAssociatedStructure() == null
        ) {
            return false;
        }

        return true;
    }

    @Override
    public void start() {
        this.patrolTimer = 0;
        this.patrolTarget = null;
        this.returningHome = false;
        this.stuckDetector.reset();

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

        // Refresh patrol markers on start
        refreshPatrolMarkers();

        TiedUpMod.LOGGER.debug(
            "[KidnapperPatrolGoal] {} started patrolling ({} markers found)",
            this.kidnapper.getNpcName(),
            this.cachedPatrolMarkers.size()
        );
    }

    /**
     * Refresh the cached patrol markers.
     */
    private void refreshPatrolMarkers() {
        this.cachedPatrolMarkers = this.kidnapper.getNearbyPatrolMarkers(64);
        this.markerCacheRefreshTimer = MARKER_CACHE_REFRESH_INTERVAL;

        // Sort markers by distance for a more logical patrol route
        if (!this.cachedPatrolMarkers.isEmpty()) {
            this.cachedPatrolMarkers.sort((a, b) -> {
                // Sort by angle from current position for circular patrol
                BlockPos kidnapperPos = this.kidnapper.blockPosition();
                double angleA = Math.atan2(
                    a.getZ() - kidnapperPos.getZ(),
                    a.getX() - kidnapperPos.getX()
                );
                double angleB = Math.atan2(
                    b.getZ() - kidnapperPos.getZ(),
                    b.getX() - kidnapperPos.getX()
                );
                return Double.compare(angleA, angleB);
            });
        }
    }

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

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

    @Override
    public void tick() {
        // Get cell spawn point as patrol center
        BlockPos cellSpawn = getCellSpawnPoint();
        if (cellSpawn == null) return;

        // Refresh marker cache periodically
        this.markerCacheRefreshTimer--;
        if (this.markerCacheRefreshTimer <= 0) {
            refreshPatrolMarkers();
        }

        // Check if too far from cell
        double distanceFromCell = this.kidnapper.distanceToSqr(
            cellSpawn.getX(),
            cellSpawn.getY(),
            cellSpawn.getZ()
        );
        double maxDistSq = MAX_DISTANCE_FROM_HOME * MAX_DISTANCE_FROM_HOME;

        if (distanceFromCell > maxDistSq) {
            // Too far! Return to cell area
            if (!this.returningHome) {
                this.returningHome = true;
                TiedUpMod.LOGGER.debug(
                    "[KidnapperPatrolGoal] {} too far from home, returning",
                    this.kidnapper.getNpcName()
                );
            }
            returnToCell(cellSpawn);
            return;
        }

        // Back in range, resume patrol
        if (this.returningHome) {
            this.returningHome = false;
            this.patrolTarget = null;
            this.patrolTimer = 0;
        }

        // Update patrol timer
        this.patrolTimer++;

        // Handle failure cooldown - wait before retrying expensive patrol point selection
        if (this.patrolFailureCooldown > 0) {
            this.patrolFailureCooldown--;
            return;
        }

        // Check if need new patrol point
        if (
            this.patrolTarget == null ||
            this.patrolTimer >= PATROL_INTERVAL ||
            reachedPatrolTarget()
        ) {
            selectNewPatrolPoint(cellSpawn);
            this.patrolTimer = 0;
            this.stuckDetector.reset();

            // If selection failed, apply cooldown to avoid expensive retries every tick
            if (this.patrolTarget == null) {
                this.patrolFailureCooldown = PATROL_FAILURE_COOLDOWN;
            }
        }

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

        if (this.stuckDetector.isStuck()) {
            if (this.stuckDetector.shouldTeleport()) {
                // Pick a new patrol point instead of teleporting
                this.patrolTarget = null;
                this.stuckDetector.reset();
                TiedUpMod.LOGGER.debug(
                    "[KidnapperPatrolGoal] {} stuck, picking new patrol 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,
                        PATROL_SPEED
                    );
                }
            }
            return;
        }

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

        // Move to patrol target - recalculate every few ticks for smooth movement
        if (
            this.patrolTarget != null &&
            (this.patrolTimer % 5 == 0 ||
                !this.kidnapper.getNavigation().isInProgress())
        ) {
            boolean pathFound = this.kidnapper.getNavigation().moveTo(
                this.patrolTarget.getX() + 0.5,
                this.patrolTarget.getY(),
                this.patrolTarget.getZ() + 0.5,
                PATROL_SPEED
            );
            if (!pathFound) {
                this.stuckDetector.onPathFailed();
            }
        }
    }

    /**
     * Get the patrol anchor point.
     * Priority: cell spawn point > camp center > null
     */
    @javax.annotation.Nullable
    private BlockPos getCellSpawnPoint() {
        if (
            !(this.kidnapper.level() instanceof
                    net.minecraft.server.level.ServerLevel serverLevel)
        ) {
            return null;
        }

        // First try: cell from collar
        java.util.UUID cellId = this.kidnapper.getCellIdFromCollar();
        if (cellId != null) {
            com.tiedup.remake.cells.CellDataV2 cell =
                com.tiedup.remake.cells.CellRegistryV2.get(serverLevel).getCell(
                    cellId
                );
            if (cell != null) {
                return cell.getSpawnPoint();
            }
        }

        // Second try: camp center (for camp kidnappers without collar cell)
        java.util.UUID campId = this.kidnapper.getAssociatedStructure();
        if (campId != 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 cell area.
     */
    private void returnToCell(BlockPos cellSpawn) {
        this.kidnapper.getNavigation().moveTo(
            cellSpawn.getX() + 0.5,
            cellSpawn.getY(),
            cellSpawn.getZ() + 0.5,
            RETURN_SPEED
        );
    }

    /**
     * Check if reached current patrol target.
     */
    private boolean reachedPatrolTarget() {
        if (this.patrolTarget == null) return true;

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

        return distSq < 4.0; // Within 2 blocks
    }

    /**
     * Select a new patrol point.
     * Uses patrol markers if available, otherwise falls back to random points.
     */
    private void selectNewPatrolPoint(BlockPos cellSpawn) {
        // First, try to use patrol markers
        if (!this.cachedPatrolMarkers.isEmpty()) {
            // Go to next marker in sequence
            this.currentPatrolIndex =
                (this.currentPatrolIndex + 1) % this.cachedPatrolMarkers.size();
            this.patrolTarget = this.cachedPatrolMarkers.get(
                this.currentPatrolIndex
            );

            TiedUpMod.LOGGER.debug(
                "[KidnapperPatrolGoal] {} moving to patrol marker {}/{}: {}",
                this.kidnapper.getNpcName(),
                this.currentPatrolIndex + 1,
                this.cachedPatrolMarkers.size(),
                this.patrolTarget
            );
            return;
        }

        // No patrol markers - fall back to random patrol
        selectRandomPatrolPoint(cellSpawn);
    }

    /**
     * Select a random patrol point within radius of cell.
     * Used as fallback when no patrol markers are available.
     */
    private void selectRandomPatrolPoint(BlockPos cellSpawn) {
        Level level = this.kidnapper.level();
        int patrolRadius = DEFAULT_PATROL_RADIUS;

        // Try to find a valid patrol point
        for (int attempts = 0; attempts < 10; attempts++) {
            // Random offset within radius
            int offsetX =
                this.kidnapper.getRandom().nextInt(patrolRadius * 2 + 1) -
                patrolRadius;
            int offsetZ =
                this.kidnapper.getRandom().nextInt(patrolRadius * 2 + 1) -
                patrolRadius;

            int targetX = cellSpawn.getX() + offsetX;
            int targetZ = cellSpawn.getZ() + offsetZ;
            int targetY = cellSpawn.getY();

            // Find ground level at target position
            BlockPos testPos = new BlockPos(targetX, targetY, targetZ);

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

                if (isValidPatrolPoint(level, checkPos)) {
                    this.patrolTarget = checkPos;
                    TiedUpMod.LOGGER.debug(
                        "[KidnapperPatrolGoal] {} new random patrol point: {}",
                        this.kidnapper.getNpcName(),
                        checkPos
                    );
                    return;
                }
            }
        }

        // LOW FIX: Failed to find valid point - leave patrolTarget null to trigger cooldown
        // instead of setting it to cellSpawn (which would bypass the cooldown check)
        this.patrolTarget = null;
        TiedUpMod.LOGGER.debug(
            "[KidnapperPatrolGoal] {} failed to find valid patrol point after 10 attempts, cooldown applied",
            this.kidnapper.getNpcName()
        );
    }

    /**
     * Check if a position is valid for patrolling (solid ground, air above).
     * Uses KidnapperAIHelper for base ground check, adds Y distance constraint.
     */
    private boolean isValidPatrolPoint(Level level, BlockPos pos) {
        // Base ground position check
        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 > 10) {
            return false;
        }

        return true;
    }
}
