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

import com.tiedup.remake.cells.CampOwnership;
import com.tiedup.remake.cells.CellDataV2;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.DialogueBridge;
import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.prison.LaborRecord;
import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.prison.PrisonerState;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.util.TiedUpSounds;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;

/**
 * AI Goal for EntityKidnapper to guard occupied cells.
 *
 * Behavior:
 * 1. Activates when there are nearby occupied cells (with guard limit)
 * 2. Patrols around the cell (not standing still!)
 * 3. Looks at prisoners periodically
 * 4. Detects escape attempts
 * 5. Short guard duration (30-60 seconds), then switches to patrol
 * 6. Maximum 1-2 guards per cell to avoid crowding
 */
public class KidnapperGuardGoal extends Goal {

    private final EntityKidnapper kidnapper;

    /** Visual detection radius (blocks) */
    private static final double VISUAL_DETECTION_RADIUS = 10.0;

    /** Patrol radius around cell */
    private static final double PATROL_RADIUS = 6.0;

    /** Minimum guard duration (30 seconds = 600 ticks) */
    private static final int MIN_GUARD_DURATION = 600;

    /** Maximum guard duration (60 seconds = 1200 ticks) */
    private static final int MAX_GUARD_DURATION = 1200;

    /** Check interval for escape detection (every second) */
    private static final int ESCAPE_CHECK_INTERVAL = 20;

    /** Interval between patrol waypoints (4 seconds) */
    private static final int PATROL_WAYPOINT_INTERVAL = 80;

    /** Maximum guards per cell */
    private static final int MAX_GUARDS_PER_CELL = 2;

    /** Current cell being guarded */
    private CellDataV2 guardedCell;

    /** Current patrol waypoint */
    private BlockPos currentWaypoint;

    /** Ticks spent guarding */
    private int guardTicks;

    /** Duration to guard before switching to patrol */
    private int guardDuration;

    /** Tick counter for escape checks */
    private int escapeCheckCounter;

    /** Tick counter for waypoint changes */
    private int waypointCounter;

    /** Tick counter for random spank behavior */
    private int spankCooldown;

    /** Minimum ticks between spanks (30 seconds) */
    private static final int SPANK_COOLDOWN_MIN = 600;

    /** Maximum ticks between spanks (2 minutes) */
    private static final int SPANK_COOLDOWN_MAX = 2400;

    /** Chance to spank when cooldown is ready (1/200 = 0.5% per tick = ~10% over 20 ticks) */
    private static final int SPANK_CHANCE = 200;

    /** Cooldown interval for canUse() checks (40 ticks = 2 seconds) */
    private static final int CAN_USE_CHECK_INTERVAL = 40;

    /** Next tick when canUse() can run expensive checks */
    private int nextCanUseCheckTick = 0;

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

    @Override
    public boolean canUse() {
        // Cheap checks first (no cooldown needed)
        if (this.kidnapper.isTiedUp()) {
            return false;
        }

        if (this.kidnapper.hasCaptives()) {
            return false;
        }

        if (this.kidnapper.getTarget() != null) {
            return false;
        }

        // Hunters don't guard cells - they hunt in the wilderness
        if (this.kidnapper.isHunter()) {
            return false;
        }

        if (this.kidnapper.getCurrentState() == KidnapperState.ALERT) {
            return false;
        }

        if (this.kidnapper.isWaitingForJobToBeCompleted()) {
            return false;
        }

        if (this.kidnapper.isGetOutState()) {
            return false;
        }

        // Throttle expensive cell/guard checks
        int currentTick = this.kidnapper.tickCount;
        if (currentTick < this.nextCanUseCheckTick) {
            return false;
        }
        this.nextCanUseCheckTick = currentTick + CAN_USE_CHECK_INTERVAL;

        // Expensive checks: cell search and guard count
        List<CellDataV2> occupiedCells =
            this.kidnapper.getNearbyCellsWithPrisoners();
        if (occupiedCells.isEmpty()) {
            return false;
        }

        this.guardedCell = selectCellNeedingGuard(occupiedCells);
        return this.guardedCell != null;
    }

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

        if (this.kidnapper.hasCaptives()) {
            return false;
        }

        if (this.kidnapper.getTarget() != null) {
            return false;
        }

        // Stop if state changed (e.g., to ALERT)
        if (this.kidnapper.getCurrentState() == KidnapperState.ALERT) {
            return false;
        }

        // Stop if entering "get out" state (should flee)
        if (this.kidnapper.isGetOutState()) {
            return false;
        }

        // Stop if guard duration exceeded (switch to patrol)
        if (this.guardTicks >= this.guardDuration) {
            return false;
        }

        // Stop if cell is no longer occupied
        if (this.guardedCell == null || !this.guardedCell.isOccupied()) {
            return false;
        }

        return true;
    }

    @Override
    public void start() {
        this.guardTicks = 0;
        this.escapeCheckCounter = 0;
        this.waypointCounter = 0;
        this.spankCooldown =
            SPANK_COOLDOWN_MIN +
            this.kidnapper.getRandom().nextInt(
                SPANK_COOLDOWN_MAX - SPANK_COOLDOWN_MIN
            );

        // Short random guard duration (30-60 seconds)
        this.guardDuration =
            MIN_GUARD_DURATION +
            this.kidnapper.getRandom().nextInt(
                MAX_GUARD_DURATION - MIN_GUARD_DURATION
            );

        // Start at a random patrol point
        this.currentWaypoint = calculateRandomPatrolPoint();

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

        TiedUpMod.LOGGER.debug(
            "[KidnapperGuardGoal] {} started guarding cell for {}s",
            this.kidnapper.getNpcName(),
            this.guardDuration / 20
        );
    }

    @Override
    public void stop() {
        // Return to IDLE state if not transitioning to ALERT
        if (this.kidnapper.getCurrentState() == KidnapperState.GUARD) {
            this.kidnapper.setCurrentState(KidnapperState.IDLE);
        }

        this.guardedCell = null;
        this.currentWaypoint = null;
        this.kidnapper.getNavigation().stop();
    }

    @Override
    public void tick() {
        this.guardTicks++;
        this.escapeCheckCounter++;
        this.waypointCounter++;

        // Decrement spank cooldown
        if (this.spankCooldown > 0) {
            this.spankCooldown--;
        }

        // Change waypoint periodically for patrol movement
        if (this.waypointCounter >= PATROL_WAYPOINT_INTERVAL) {
            this.waypointCounter = 0;
            this.currentWaypoint = calculateRandomPatrolPoint();
        }

        // Move towards current waypoint (patrol around cell)
        if (this.currentWaypoint != null) {
            double distSq = this.kidnapper.distanceToSqr(
                this.currentWaypoint.getX() + 0.5,
                this.currentWaypoint.getY(),
                this.currentWaypoint.getZ() + 0.5
            );

            // If far from waypoint, navigate there
            if (distSq > 4.0) {
                if (this.guardTicks % 20 == 0) {
                    // Recalc path every second
                    this.kidnapper.getNavigation().moveTo(
                        this.currentWaypoint.getX() + 0.5,
                        this.currentWaypoint.getY(),
                        this.currentWaypoint.getZ() + 0.5,
                        0.6 // Slow patrol speed
                    );
                }
            } else {
                // Reached waypoint - look at prisoners while standing
                lookAtPrisoners();

                // Random chance to spank a nearby prisoner
                tryRandomSpank();
            }
        }

        // Check for escapes periodically
        if (this.escapeCheckCounter >= ESCAPE_CHECK_INTERVAL) {
            this.escapeCheckCounter = 0;
            checkForEscapes();
        }
    }

    /**
     * Select a cell that needs guarding, respecting guard limits.
     * Returns null if all cells have enough guards.
     */
    private CellDataV2 selectCellNeedingGuard(List<CellDataV2> cells) {
        if (!(this.kidnapper.level() instanceof ServerLevel serverLevel)) {
            return null;
        }

        CellDataV2 bestCell = null;
        double bestScore = Double.MAX_VALUE;

        for (CellDataV2 cell : cells) {
            // Count current guards for this cell
            int guardCount = countGuardsForCell(serverLevel, cell);

            // Skip if cell has enough guards
            if (guardCount >= MAX_GUARDS_PER_CELL) {
                continue;
            }

            // Score based on distance (prefer closer) and guard count (prefer less guarded)
            double distSq = this.kidnapper.blockPosition().distSqr(
                cell.getCorePos()
            );
            double score = distSq + (guardCount * 1000); // Penalize already-guarded cells

            if (score < bestScore) {
                bestScore = score;
                bestCell = cell;
            }
        }

        return bestCell;
    }

    /**
     * Count how many kidnappers are currently guarding a specific cell.
     */
    private int countGuardsForCell(ServerLevel level, CellDataV2 cell) {
        BlockPos cellPos = cell.getCorePos();
        int count = 0;

        for (EntityKidnapper other : level.getEntitiesOfClass(
            EntityKidnapper.class,
            this.kidnapper.getBoundingBox().inflate(30)
        )) {
            if (other == this.kidnapper) continue;
            if (other.getCurrentState() != KidnapperState.GUARD) continue;

            // Check if this kidnapper is near the same cell
            double distSq = other.blockPosition().distSqr(cellPos);
            if (distSq < 100) {
                // Within 10 blocks of cell
                count++;
            }
        }

        return count;
    }

    /**
     * Calculate a random patrol point around the cell.
     */
    private BlockPos calculateRandomPatrolPoint() {
        if (this.guardedCell == null) {
            return this.kidnapper.blockPosition();
        }

        BlockPos center = this.guardedCell.getCorePos();

        // Random angle
        double angle = this.kidnapper.getRandom().nextDouble() * Math.PI * 2;

        // Random distance (between 3 and PATROL_RADIUS)
        double distance =
            3.0 +
            this.kidnapper.getRandom().nextDouble() * (PATROL_RADIUS - 3.0);

        int offsetX = (int) (Math.cos(angle) * distance);
        int offsetZ = (int) (Math.sin(angle) * distance);

        return center.offset(offsetX, 0, offsetZ);
    }

    /**
     * Look at prisoners in the guarded cell.
     */
    private void lookAtPrisoners() {
        if (this.guardedCell == null) return;
        if (
            !(this.kidnapper.level() instanceof ServerLevel serverLevel)
        ) return;

        // Get prisoner UUIDs
        List<UUID> prisonerIds = this.guardedCell.getPrisonerIds();
        if (prisonerIds.isEmpty()) return;

        // Look at a random prisoner
        UUID targetId = prisonerIds.get(
            this.kidnapper.getRandom().nextInt(prisonerIds.size())
        );

        Player prisoner = serverLevel
            .getServer()
            .getPlayerList()
            .getPlayer(targetId);
        if (prisoner != null && prisoner.isAlive()) {
            this.kidnapper.getLookControl().setLookAt(prisoner, 30.0F, 30.0F);

            // Occasional dialogue when watching (every ~30 seconds on average)
            if (
                this.guardTicks % 600 == 0 &&
                this.kidnapper.getRandom().nextFloat() < 0.5f
            ) {
                DialogueBridge.talkTo(
                    this.kidnapper,
                    prisoner,
                    "guard.watching"
                );
            }
        }
    }

    /**
     * Try to randomly spank a nearby prisoner.
     * Has a small chance to happen when cooldown is ready.
     */
    private void tryRandomSpank() {
        // Check cooldown
        if (this.spankCooldown > 0) {
            return;
        }

        // Random chance check
        if (this.kidnapper.getRandom().nextInt(SPANK_CHANCE) != 0) {
            return;
        }

        if (this.guardedCell == null) return;
        if (
            !(this.kidnapper.level() instanceof ServerLevel serverLevel)
        ) return;

        // Find a nearby tied prisoner
        List<UUID> prisonerIds = this.guardedCell.getPrisonerIds();
        if (prisonerIds.isEmpty()) return;

        // Pick a random prisoner
        UUID targetId = prisonerIds.get(
            this.kidnapper.getRandom().nextInt(prisonerIds.size())
        );

        ServerPlayer prisoner = serverLevel
            .getServer()
            .getPlayerList()
            .getPlayer(targetId);
        if (prisoner == null || !prisoner.isAlive()) return;

        // Must be close enough to spank
        if (this.kidnapper.distanceTo(prisoner) > 3.0) return;

        // Must be tied up
        IBondageState state = KidnappedHelper.getKidnappedState(prisoner);
        if (state == null || !state.isTiedUp()) return;

        // Execute the spank!
        executeSpank(prisoner, serverLevel);

        // Reset cooldown
        this.spankCooldown =
            SPANK_COOLDOWN_MIN +
            this.kidnapper.getRandom().nextInt(
                SPANK_COOLDOWN_MAX - SPANK_COOLDOWN_MIN
            );
    }

    /**
     * Execute the spank action on a prisoner.
     * Equips paddle, plays sound, shows particles.
     *
     * @param prisoner The prisoner to spank
     * @param serverLevel The server level
     */
    private void executeSpank(ServerPlayer prisoner, ServerLevel serverLevel) {
        // Save current held item
        ItemStack previousItem = this.kidnapper.getMainHandItem().copy();

        // Equip paddle temporarily
        this.kidnapper.setItemInHand(
            InteractionHand.MAIN_HAND,
            new ItemStack(ModItems.PADDLE.get())
        );

        // Play spank sound
        TiedUpSounds.playSlapSound(prisoner);

        // Spawn particles at prisoner's behind area
        serverLevel.sendParticles(
            ParticleTypes.SMOKE,
            prisoner.getX(),
            prisoner.getY() + 0.5,
            prisoner.getZ(),
            5,
            0.2,
            0.1,
            0.2,
            0.02
        );

        // Swing arm animation
        this.kidnapper.swing(InteractionHand.MAIN_HAND);

        // Look at prisoner
        this.kidnapper.getLookControl().setLookAt(prisoner, 30.0F, 30.0F);

        TiedUpMod.LOGGER.debug(
            "[KidnapperGuardGoal] {} spanked prisoner {}",
            this.kidnapper.getNpcName(),
            prisoner.getName().getString()
        );

        // Schedule paddle removal after a short delay (restore previous item)
        // We'll do it in next tick check via a simple flag approach
        // For simplicity, just put back the original item immediately
        // (the visual effect is enough)
        this.kidnapper.setItemInHand(InteractionHand.MAIN_HAND, previousItem);
    }

    /**
     * Check if any prisoners have escaped their cells.
     */
    private void checkForEscapes() {
        if (this.guardedCell == null) return;
        if (
            !(this.kidnapper.level() instanceof ServerLevel serverLevel)
        ) return;

        for (UUID prisonerId : this.guardedCell.getPrisonerIds()) {
            Player prisoner = serverLevel
                .getServer()
                .getPlayerList()
                .getPlayer(prisonerId);
            if (prisoner == null || !prisoner.isAlive()) {
                continue;
            }

            // FIX: Check if prisoner is on authorized labor task (extraction, working, returning)
            // Don't trigger escape alert for prisoners legitimately outside their cell
            PrisonerManager manager = PrisonerManager.get(serverLevel);
            PrisonerState state = manager.getState(prisonerId);
            if (state == PrisonerState.WORKING) {
                continue; // Skip - prisoner is on authorized labor task
            }
            // Skip PROTECTED (released) and FREE prisoners - they're no longer captive
            if (
                state == PrisonerState.PROTECTED || state == PrisonerState.FREE
            ) {
                continue; // Skip - prisoner was released or freed
            }
            // Also check labor phase for more detailed work state
            LaborRecord.WorkPhase workPhase = manager.getWorkPhase(prisonerId);
            if (workPhase != LaborRecord.WorkPhase.IDLE) {
                continue; // Skip - prisoner is in some work phase (extracting, working, returning)
            }

            // Check if prisoner is within visual detection range
            double distance = this.kidnapper.distanceTo(prisoner);
            if (distance > VISUAL_DETECTION_RADIUS) {
                continue;
            }

            // Check if prisoner is outside their cell
            BlockPos prisonerPos = prisoner.blockPosition();
            if (!this.kidnapper.isInsideCell(prisonerPos, this.guardedCell)) {
                // ESCAPE DETECTED!
                onEscapeDetected(prisoner);
                return;
            }
        }
    }

    /**
     * Handle escape detection - transition to ALERT state.
     */
    private void onEscapeDetected(LivingEntity escapee) {
        TiedUpMod.LOGGER.info(
            "[KidnapperGuardGoal] {} detected escape! {} is outside cell",
            this.kidnapper.getNpcName(),
            escapee.getName().getString()
        );

        // Dialogue when detecting escape
        if (escapee instanceof Player player) {
            DialogueBridge.talkTo(
                this.kidnapper,
                player,
                "guard.escape_detected"
            );
        }

        // Check if prisoner is still tied up
        IBondageState state = KidnappedHelper.getKidnappedState(escapee);
        if (state == null || !state.isTiedUp()) {
            // Prisoner freed themselves - set as alert target
            this.kidnapper.setAlertTarget(escapee);
            this.kidnapper.setCurrentState(KidnapperState.ALERT);
            this.kidnapper.broadcastAlert(escapee);
        } else {
            // Prisoner still tied but moved - could be glitch or teleport
            // Set as direct target for quick recapture
            this.kidnapper.setTarget(escapee);
            this.kidnapper.setAlertTarget(escapee);
            this.kidnapper.setCurrentState(KidnapperState.ALERT);
            this.kidnapper.broadcastAlert(escapee);
        }
    }

    @Override
    public boolean requiresUpdateEveryTick() {
        return false; // Don't need every tick, default interval is fine
    }
}
