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

import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.personality.NpcCommand;
import com.tiedup.remake.personality.PersonalityState;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.core.NonNullList;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.AABB;

/**
 * AI Goal: NPC guards a position and alerts on intruders.
 *
 * Personality System Phase D: AI Goals
 *
 * <p><b>Behavior:</b></p>
 * <ul>
 *   <li>Only active when NPC has GUARD command</li>
 *   <li>Stays at guard position</li>
 *   <li>Looks around for hostile mobs</li>
 *   <li>Alerts master when hostiles are detected</li>
 *   <li>Awards XP periodically while guarding</li>
 * </ul>
 */
public class NpcGuardCommandGoal extends Goal {

    private final EntityDamsel npc;

    /** Detection radius for hostiles */
    private static final double DETECTION_RADIUS = 16.0;

    /** Detection radius for slaves (work zone) */
    private static final double SLAVE_DETECTION_RADIUS = 16.0;

    /** Maximum drift from guard position */
    private static final double MAX_DRIFT = 2.0;

    /** Ticks between detection scans */
    private static final int SCAN_INTERVAL = 40; // 2 seconds

    /** Ticks between XP awards */
    private static final int XP_INTERVAL = 2400; // 2 minutes

    /** Cooldown between alert messages */
    private static final int ALERT_COOLDOWN = 200; // 10 seconds

    /** Chase speed modifier */
    private static final double CHASE_SPEED = 1.4;

    /** Distance to consider slave "caught" */
    private static final double CATCH_DISTANCE = 2.0;

    /** Guard behavior state */
    private enum GuardState {
        GUARDING, // Normal guard behavior
        CHASING, // Pursuing fleeing slave
        RETURNING_SLAVE, // Bringing slave back to their home
    }

    private GuardState guardState = GuardState.GUARDING;

    @Nullable
    private BlockPos guardPosition;

    private int scanTimer;
    private int xpTimer;
    private int alertCooldown;

    @Nullable
    private LivingEntity lastDetectedThreat;

    /** Current slave being chased/escorted */
    @Nullable
    private EntityDamsel chaseTarget;

    /** Home position to return slave to */
    @Nullable
    private BlockPos targetHome;

    public NpcGuardCommandGoal(EntityDamsel npc) {
        this.npc = npc;
        this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK));
    }

    @Override
    public boolean canUse() {
        if (npc.getActiveCommand() != NpcCommand.GUARD) {
            return false;
        }
        if (!npc.hasCollar()) {
            return false;
        }
        return true;
    }

    @Override
    public boolean canContinueToUse() {
        if (npc.getActiveCommand() != NpcCommand.GUARD) {
            return false;
        }
        if (!npc.hasCollar()) {
            return false;
        }
        return true;
    }

    @Override
    public void start() {
        NpcGoalHelper.ensureOutsideCell(npc);
        var state = npc.getPersonalityState();
        if (state != null && state.getCommandTarget() != null) {
            this.guardPosition = state.getCommandTarget();
        } else {
            this.guardPosition = npc.blockPosition();
        }

        this.scanTimer = 0;
        this.xpTimer = 0;
        this.alertCooldown = 0;
        this.lastDetectedThreat = null;
        this.guardState = GuardState.GUARDING;
        this.chaseTarget = null;
        this.targetHome = null;

        npc.getNavigation().stop();
    }

    @Override
    public void stop() {
        this.guardPosition = null;
        this.lastDetectedThreat = null;
        this.guardState = GuardState.GUARDING;
        this.chaseTarget = null;
        this.targetHome = null;
    }

    @Override
    public void tick() {
        switch (guardState) {
            case GUARDING -> tickGuarding();
            case CHASING -> tickChasing();
            case RETURNING_SLAVE -> tickReturning();
        }
    }

    /**
     * Normal guard behavior - maintain position, scan for threats and fleeing slaves.
     */
    private void tickGuarding() {
        // Maintain guard position
        if (this.guardPosition != null) {
            double distSq = npc.distanceToSqr(
                guardPosition.getX() + 0.5,
                guardPosition.getY(),
                guardPosition.getZ() + 0.5
            );

            if (distSq > MAX_DRIFT * MAX_DRIFT) {
                npc
                    .getNavigation()
                    .moveTo(
                        guardPosition.getX() + 0.5,
                        guardPosition.getY(),
                        guardPosition.getZ() + 0.5,
                        1.0
                    );
            } else if (npc.getNavigation().isInProgress()) {
                npc.getNavigation().stop();
            }
        }

        // Scan for threats and fleeing slaves
        if (--this.scanTimer <= 0) {
            this.scanTimer = SCAN_INTERVAL;
            scanForThreats();
            scanForFleeingSlaves();
        }

        // Look at detected threat
        if (
            this.lastDetectedThreat != null && this.lastDetectedThreat.isAlive()
        ) {
            npc.getLookControl().setLookAt(lastDetectedThreat, 30.0F, 30.0F);
        } else {
            // Look around randomly
            if (npc.getRandom().nextInt(60) == 0) {
                float yRot =
                    npc.getYRot() + (npc.getRandom().nextFloat() - 0.5f) * 90;
                npc
                    .getLookControl()
                    .setLookAt(
                        npc.getX() + Math.sin((-yRot * Math.PI) / 180) * 10,
                        npc.getEyeY(),
                        npc.getZ() + Math.cos((-yRot * Math.PI) / 180) * 10
                    );
            }
        }

        // Decrement alert cooldown
        if (this.alertCooldown > 0) {
            this.alertCooldown--;
        }

        // Award XP periodically
        if (++this.xpTimer >= XP_INTERVAL) {
            this.xpTimer = 0;
            awardXP(2);
        }
    }

    /**
     * Chase a fleeing slave.
     */
    private void tickChasing() {
        if (chaseTarget == null || !chaseTarget.isAlive()) {
            returnToGuarding();
            return;
        }

        double distSq = npc.distanceToSqr(chaseTarget);

        // Caught the slave
        if (distSq < CATCH_DISTANCE * CATCH_DISTANCE) {
            captureAndReturn();
            return;
        }

        // Lost the slave (too far)
        if (distSq > SLAVE_DETECTION_RADIUS * SLAVE_DETECTION_RADIUS * 4) {
            returnToGuarding();
            return;
        }

        // Continue chase
        npc.getNavigation().moveTo(chaseTarget, CHASE_SPEED);
    }

    /**
     * Return a captured slave to their home.
     */
    private void tickReturning() {
        if (chaseTarget == null || targetHome == null) {
            returnToGuarding();
            return;
        }

        // Check if slave is at their home
        double slaveDistToHome = chaseTarget.distanceToSqr(
            targetHome.getX() + 0.5,
            targetHome.getY(),
            targetHome.getZ() + 0.5
        );

        if (slaveDistToHome < 4.0) {
            // 2 blocks
            // Slave returned home - give command to stay
            PersonalityState slaveState = chaseTarget.getPersonalityState();
            if (slaveState != null) {
                slaveState.setActiveCommand(
                    NpcCommand.STAY,
                    slaveState.getCommandingPlayer(),
                    targetHome
                );
            }
            returnToGuarding();
            return;
        }

        // Navigate slave to their home (guard escorts them)
        double guardDistToSlave = npc.distanceToSqr(chaseTarget);
        if (guardDistToSlave > 9.0) {
            // 3 blocks
            // Stay close to slave
            npc.getNavigation().moveTo(chaseTarget, CHASE_SPEED);
        } else {
            // Push slave toward home
            npc
                .getNavigation()
                .moveTo(
                    targetHome.getX() + 0.5,
                    targetHome.getY(),
                    targetHome.getZ() + 0.5,
                    1.0
                );
        }
    }

    /**
     * Return to normal guarding state.
     */
    private void returnToGuarding() {
        this.guardState = GuardState.GUARDING;
        this.chaseTarget = null;
        this.targetHome = null;

        // Return to guard position
        if (this.guardPosition != null) {
            npc
                .getNavigation()
                .moveTo(
                    guardPosition.getX() + 0.5,
                    guardPosition.getY(),
                    guardPosition.getZ() + 0.5,
                    1.0
                );
        }
    }

    /**
     * Scan for slaves trying to flee within work zone.
     * Criteria: Has collar with same master, not tied, far from home, high fear OR no command.
     */
    private void scanForFleeingSlaves() {
        // Don't scan if already chasing
        if (guardState != GuardState.GUARDING) return;

        var state = npc.getPersonalityState();
        if (state == null) return;

        UUID masterUUID = state.getCommandingPlayer();
        if (masterUUID == null) return;

        AABB scanArea = npc.getBoundingBox().inflate(SLAVE_DETECTION_RADIUS);
        List<EntityDamsel> slaves = npc
            .level()
            .getEntitiesOfClass(
                EntityDamsel.class,
                scanArea,
                d -> d != this.npc
            );

        for (EntityDamsel slave : slaves) {
            if (isSlaveAttemptingEscape(slave, masterUUID)) {
                // Found fleeing slave - start chase
                this.chaseTarget = slave;
                this.guardState = GuardState.CHASING;
                alertMasterAboutFleeing(slave);
                return;
            }
        }
    }

    /**
     * Check if a slave is attempting to escape.
     */
    private boolean isSlaveAttemptingEscape(
        EntityDamsel slave,
        UUID masterUUID
    ) {
        // Must have collar owned by same master
        if (!slave.hasCollar()) return false;
        UUID collarOwner = getCollarOwnerUUID(slave);
        if (
            collarOwner == null || !collarOwner.equals(masterUUID)
        ) return false;

        // Must not be tied up (already restrained)
        if (slave.isTiedUp()) return false;

        PersonalityState slaveState = slave.getPersonalityState();
        if (slaveState == null) return false;

        // Check if fleeing: no active command AND far from home
        if (slaveState.getActiveCommand() == NpcCommand.NONE) {
            BlockPos slaveHome = slaveState.getHomePos();
            if (slaveHome == null) return true; // No home = wandering = escape attempt

            double distFromHome = slave.distanceToSqr(
                slaveHome.getX() + 0.5,
                slaveHome.getY(),
                slaveHome.getZ() + 0.5
            );
            if (distFromHome > 100) {
                // 10+ blocks from home
                return true;
            }
        }

        return false;
    }

    /**
     * Capture the fleeing slave and prepare to return them.
     */
    private void captureAndReturn() {
        if (chaseTarget == null) return;

        // Try to tie up the slave (if guard has bind item in inventory)
        tryTieUpSlave(chaseTarget);

        // Apply capture effects: mood penalty
        PersonalityState slaveState = chaseTarget.getPersonalityState();
        if (slaveState != null) {
            slaveState.modifyMood(-5);

            // Get slave's home for return destination
            this.targetHome = slaveState.getHomePos();
        }

        if (this.targetHome == null) {
            // No home - just tie them up and return to guarding
            returnToGuarding();
            return;
        }

        // Start returning slave to their home
        this.guardState = GuardState.RETURNING_SLAVE;
    }

    /**
     * Try to tie up the slave using a bind item from guard's inventory.
     */
    private boolean tryTieUpSlave(EntityDamsel slave) {
        // Search guard's inventory for bind item
        NonNullList<ItemStack> inventory = npc.getNpcInventory();
        for (int i = 0; i < inventory.size(); i++) {
            ItemStack stack = inventory.get(i);
            if (BindModeHelper.isBindItem(stack)) {
                // Apply bind to slave
                slave.equip(BodyRegionV2.ARMS, stack.copy());
                stack.shrink(1);
                return true;
            }
        }
        return false;
    }

    /**
     * Alert the master about a fleeing slave.
     */
    private void alertMasterAboutFleeing(EntityDamsel slave) {
        var state = npc.getPersonalityState();
        if (state == null) return;

        UUID commanderUUID = state.getCommandingPlayer();
        if (commanderUUID == null) return;

        for (Player player : npc.level().players()) {
            if (player.getUUID().equals(commanderUUID)) {
                String slaveName = slave.getNpcName();
                player.displayClientMessage(
                    net.minecraft.network.chat.Component.translatable(
                        "goal.tiedup.guard_command.chasing", npc.getNpcName(), slaveName
                    ).withStyle(net.minecraft.ChatFormatting.RED),
                    true // Action bar
                );
                break;
            }
        }
    }

    /**
     * Find the commanding player entity.
     */
    @Nullable
    private Player findCommandingPlayer() {
        var state = npc.getPersonalityState();
        if (state == null) return null;

        UUID commanderUUID = state.getCommandingPlayer();
        if (commanderUUID == null) return null;

        for (Player player : npc.level().players()) {
            if (player.getUUID().equals(commanderUUID)) {
                return player;
            }
        }
        return null;
    }

    /**
     * Get the collar owner UUID from a slave.
     */
    @Nullable
    private UUID getCollarOwnerUUID(EntityDamsel slave) {
        if (!slave.hasCollar()) return null;
        ItemStack collar = slave.getEquipment(BodyRegionV2.NECK);
        if (CollarHelper.isCollar(collar)) {
            java.util.List<UUID> owners = CollarHelper.getOwners(collar);
            if (!owners.isEmpty()) {
                return owners.get(0);
            }
        }
        return null;
    }

    /**
     * Scan for hostile entities nearby.
     */
    private void scanForThreats() {
        AABB scanArea = npc.getBoundingBox().inflate(DETECTION_RADIUS);
        List<Monster> monsters = npc
            .level()
            .getEntitiesOfClass(Monster.class, scanArea);

        if (!monsters.isEmpty()) {
            Monster closest = null;
            double closestDist = Double.MAX_VALUE;

            for (Monster monster : monsters) {
                double dist = npc.distanceToSqr(monster);
                if (dist < closestDist) {
                    closest = monster;
                    closestDist = dist;
                }
            }

            if (closest != null) {
                this.lastDetectedThreat = closest;

                // Alert master if cooldown allows
                if (this.alertCooldown <= 0) {
                    alertMaster(closest);
                    this.alertCooldown = ALERT_COOLDOWN;
                }
            }
        } else {
            this.lastDetectedThreat = null;
        }
    }

    /**
     * Alert the master about a threat.
     */
    private void alertMaster(Monster threat) {
        var state = npc.getPersonalityState();
        if (state == null) return;

        UUID commanderUUID = state.getCommandingPlayer();
        if (commanderUUID == null) return;

        for (Player player : npc.level().players()) {
            if (player.getUUID().equals(commanderUUID)) {
                // Send alert message (could be enhanced with dialogue system)
                String threatName = threat.getName().getString();
                player.displayClientMessage(
                    net.minecraft.network.chat.Component.translatable(
                        "goal.tiedup.guard_command.spotted", npc.getNpcName(), threatName
                    ).withStyle(net.minecraft.ChatFormatting.YELLOW),
                    true // Action bar
                );

                // Award bonus XP for alerting
                break;
            }
        }
    }

    /**
     * Award XP to the commanding player.
     */
    private void awardXP(int amount) {
        NpcGoalHelper.awardXPToMaster(npc, amount);
    }

    @Override
    public boolean requiresUpdateEveryTick() {
        return true;
    }
}
