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

import com.tiedup.remake.cells.CampOwnership;
import com.tiedup.remake.cells.CellDataV2;
import com.tiedup.remake.cells.CellRegistryV2;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.core.SystemMessageManager.MessageCategory;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.EntityDialogueManager.DialogueCategory;
import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.prison.service.EscapeMonitorService;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
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 decide what to do after capturing a slave.
 *
 *
 * This goal activates when:
 * - Kidnapper has a slave
 * - Slave is NOT for sale
 * - No job is assigned
 * - Not in flee state
 * - NOT a camp kidnapper with available cells (maid handles those)
 *
 * Behavior:
 * 1. Wait a short delay (gives player time to react/escape)
 * 2. Randomly decide: sell slave OR assign job
 * 3. Transition to appropriate goal
 *
 * NOTE: For camp kidnappers with cells, this goal is DISABLED.
 * After delivering a captive to a cell, the maid handles processing.
 * This goal only activates for:
 * - Wild kidnappers (no camp affiliation)
 * - Camp kidnappers when all cells are full
 *
 * This bridges the gap between capture and sale/job systems.
 */
public class KidnapperDecideNextActionGoal extends Goal {

    private final EntityKidnapper kidnapper;

    /** Delay before deciding (ticks) - 30-60 seconds */
    private static final int MIN_DECIDE_DELAY = 600;
    private static final int MAX_DECIDE_DELAY = 1200;

    /**
     * Decision chances (0.0 - 1.0)
     *
     * BUG FIX: Integrated theft behavior into DecideNextAction.
     * Problem: ThiefGoal (P7) never executed because DecideNextAction (P6)
     * finished first and changed state, blocking ThiefGoal activation.
     * Solution: Added theft as a third option here.
     *
     * Distribution:
     * - 30% theft (steal items, release tied player, flee)
     * - 30% sell (bring to merchant)
     * - 40% job (assign labor task)
     */
    private static final float THEFT_CHANCE = 0.3f;
    private static final float SELL_CHANCE = 0.3f;
    // Job = remaining probability (1.0 - THEFT_CHANCE - SELL_CHANCE = 0.4)

    /** Items to steal during theft (1-3) */
    private static final int MIN_ITEMS_TO_STEAL = 1;
    private static final int MAX_ITEMS_TO_STEAL = 3;

    /** Current delay timer */
    private int decideTimer;

    /** Target delay for this decision */
    private int decideDelay;

    /** Whether decision has been announced */
    private boolean hasAnnounced;

    /**
     * Create a new decide next action goal.
     *
     * @param kidnapper The kidnapper entity
     */
    public KidnapperDecideNextActionGoal(EntityKidnapper kidnapper) {
        this.kidnapper = kidnapper;
        this.decideTimer = 0;
        this.decideDelay = 0;
        this.hasAnnounced = false;
        this.setFlags(EnumSet.of(Goal.Flag.LOOK));
    }

    @Override
    public boolean canUse() {
        // Don't interrupt dogwalk - WalkPrisonerGoal manages its own captive lifecycle
        if (this.kidnapper.isDogwalking()) {
            return false;
        }

        // Must have a slave
        if (!this.kidnapper.hasCaptives()) {
            return false;
        }

        // Must not already be selling
        if (this.kidnapper.isSellingCaptive()) {
            return false;
        }

        // Must not already have a job assigned
        if (this.kidnapper.isWaitingForJobToBeCompleted()) {
            return false;
        }

        // Must not be in flee state
        if (this.kidnapper.isGetOutState()) {
            return false;
        }

        // Must not be tied up
        if (this.kidnapper.isTiedUp()) {
            return false;
        }

        // Camp kidnappers with available cells should NOT use this goal
        // They deliver to cells and let the maid handle everything
        if (isCampKidnapperWithAvailableCells()) {
            return false;
        }

        return true;
    }

    /**
     * Check if BringToCellGoal would be able to find a cell.
     * If so, this goal should not activate - let BringToCellGoal handle it.
     *
     * FIX: Must mirror the EXACT logic of KidnapperBringToCellGoal.findNearestCell():
     * 1. First try camp cells via getCellsByCamp() index
     * 2. If no camp or camp cells full, fallback to findCellsNear() with 64 block radius
     *
     * Previous bug: Only checked camp cells, ignored the 64-block fallback,
     * causing DecideNextAction to activate when BringToCellGoal would have found a cell.
     */
    private boolean isCampKidnapperWithAvailableCells() {
        if (!(kidnapper.level() instanceof ServerLevel serverLevel)) {
            return false;
        }

        CellRegistryV2 cellRegistry = CellRegistryV2.get(serverLevel);
        CampOwnership campOwnership = CampOwnership.get(serverLevel);

        // Step 0: Check captive's campId (recaptured escaped prisoner)
        // Must mirror BringToCellGoal.findNearestCell() which checks captive's camp first
        IBondageState currentCaptive = this.kidnapper.getCaptive();
        if (currentCaptive != null && currentCaptive.asLivingEntity() != null) {
            com.tiedup.remake.prison.PrisonerManager manager =
                com.tiedup.remake.prison.PrisonerManager.get(serverLevel);
            com.tiedup.remake.prison.PrisonerRecord record = manager.getRecord(
                currentCaptive.asLivingEntity().getUUID()
            );
            java.util.UUID captiveCampId = record.getCampId();
            if (captiveCampId != null) {
                CampOwnership.CampData captiveCamp = campOwnership.getCamp(
                    captiveCampId
                );
                if (captiveCamp != null && captiveCamp.isAlive()) {
                    List<CellDataV2> captiveCampCells =
                        cellRegistry.getCellsByCamp(captiveCampId);
                    for (CellDataV2 cell : captiveCampCells) {
                        if (!cell.isFull()) {
                            return true; // Captive's original camp has available cell
                        }
                    }
                }
            }
        }

        // Step 1: Check camp cells (same as BringToCellGoal)
        java.util.UUID campId = kidnapper.getAssociatedStructure();
        if (campId != null) {
            CampOwnership.CampData campData = campOwnership.getCamp(campId);
            if (campData != null && campData.isAlive()) {
                // Check camp cells via index
                List<CellDataV2> campCells = cellRegistry.getCellsByCamp(
                    campId
                );

                // DEBUG: Log if camp is alive but has no cells indexed
                if (campCells.isEmpty()) {
                    TiedUpMod.LOGGER.warn(
                        "[DecideNextAction] {} has campId {} (alive) but NO cells in index! " +
                            "This is a bug - cells should be indexed when created.",
                        kidnapper.getNpcName(),
                        campId.toString().substring(0, 8)
                    );
                }

                for (CellDataV2 cell : campCells) {
                    if (!cell.isFull()) {
                        return true; // Camp has available cell
                    }
                }

                // All camp cells full - can we replace a lower-priority prisoner?
                // Only players can trigger replacement (they outrank NPCs)
                IBondageState captive = kidnapper.getCaptive();
                if (
                    captive != null &&
                    captive.asLivingEntity() instanceof Player
                ) {
                    // Check if there's actually a replaceable NPC prisoner in camp cells
                    boolean replacementPossible = false;
                    for (CellDataV2 cell : campCells) {
                        if (cell.isOccupied()) {
                            for (java.util.UUID prisonerId : cell.getPrisonerIds()) {
                                net.minecraft.world.entity.Entity prisonerEntity =
                                    serverLevel.getEntity(prisonerId);
                                // If we find a non-player prisoner, replacement is possible
                                if (
                                    prisonerEntity instanceof
                                        net.minecraft.world.entity.LivingEntity &&
                                    !(prisonerEntity instanceof Player)
                                ) {
                                    replacementPossible = true;
                                    break;
                                }
                            }
                        }
                        if (replacementPossible) break;
                    }

                    if (replacementPossible) {
                        return true; // BringToCellGoal can replace an NPC prisoner
                    }
                }

                TiedUpMod.LOGGER.debug(
                    "[DecideNextAction] {} camp {} has {} cells, all full",
                    kidnapper.getNpcName(),
                    campId.toString().substring(0, 8),
                    campCells.size()
                );
            } else {
                // MEDIUM FIX: Camp is dead or not found - kidnapper should NOT search for cells
                // Without this check, dead camp kidnappers continue searching for cells,
                // creating inconsistent behavior where they try to use other camps' cells.
                // Instead, return false to let DecideNextAction handle the captive (sell/job/theft).
                TiedUpMod.LOGGER.debug(
                    "[DecideNextAction] {} has campId {} but camp is DEAD or not found - will sell/assign job instead of searching cells",
                    kidnapper.getNpcName(),
                    campId.toString().substring(0, 8)
                );
                return false; // No cell search for dead camp kidnappers
            }
        }

        // Step 2: Find nearest alive camp and check its cells (for wild kidnappers)
        // This covers archers and other wild kidnappers who capture far from camps
        // but should still use nearby camp cells instead of selling
        CampOwnership.CampData nearestCamp = campOwnership.findNearestAliveCamp(
            kidnapper.blockPosition(),
            200 // 200-block radius covers archers who shoot from distance
        );
        if (nearestCamp != null) {
            List<CellDataV2> nearestCampCells = cellRegistry.getCellsByCamp(
                nearestCamp.getCampId()
            );
            for (CellDataV2 cell : nearestCampCells) {
                if (!cell.isFull()) {
                    return true; // Nearest camp has available cell
                }
            }
        }

        // Step 3: Fallback to nearby cells (same as BringToCellGoal SEARCH_RADIUS = 64)
        List<CellDataV2> nearbyCells = cellRegistry.findCellsNear(
            kidnapper.blockPosition(),
            64
        );
        for (CellDataV2 cell : nearbyCells) {
            if (!cell.isFull()) {
                // Skip cells from dead camps
                if (cell.isCampOwned()) {
                    java.util.UUID cellCampId = cell.getCampId();
                    if (
                        cellCampId != null &&
                        !campOwnership.isCampAlive(cellCampId)
                    ) {
                        continue;
                    }
                }
                return true; // Found available nearby cell
            }
        }

        return false; // No cells available anywhere
    }

    @Override
    public boolean canContinueToUse() {
        // Same conditions as canUse
        return canUse();
    }

    @Override
    public void start() {
        this.decideTimer = 0;
        this.decideDelay =
            MIN_DECIDE_DELAY +
            this.kidnapper.getRandom().nextInt(
                MAX_DECIDE_DELAY - MIN_DECIDE_DELAY
            );
        this.hasAnnounced = false;

        TiedUpMod.LOGGER.debug(
            "[KidnapperDecideNextActionGoal] {} thinking what to do ({}s)",
            this.kidnapper.getNpcName(),
            this.decideDelay / 20
        );
    }

    @Override
    public void stop() {
        this.decideTimer = 0;
        this.hasAnnounced = false;
    }

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

        // Announce thinking after a short delay
        if (!this.hasAnnounced && this.decideTimer > 40) {
            announceThinking();
            this.hasAnnounced = true;
        }

        // Time to decide
        if (this.decideTimer >= this.decideDelay) {
            makeDecision();
        }
    }

    /**
     * Announce that kidnapper is thinking about what to do.
     */
    private void announceThinking() {
        IBondageState slave = this.kidnapper.getCaptive();
        if (slave == null) return;

        // Talk to slave
        if (slave.asLivingEntity() instanceof Player player) {
            this.kidnapper.talkTo(player, DialogueCategory.GENERIC_TAUNT);
        }
    }

    /**
     * Make the decision: theft, sell, or assign job.
     *
     * BUG FIX: Integrated theft behavior from ThiefGoal.
     * Now decides between 3 options: 30% theft, 30% sell, 40% job.
     *
     * Jobs only work for Players (NPCs can't gather items).
     * Theft only works for Players (NPCs are always sold).
     */
    private void makeDecision() {
        IBondageState slave = this.kidnapper.getCaptive();
        if (slave == null) return;

        boolean isPlayer = slave.asLivingEntity() instanceof Player;

        // ESCAPED SYSTEM: Check if this is a recaptured ESCAPED prisoner
        // Recaptured prisoners (campId != null) should be brought back to their camp by BringToCellGoal
        // DO NOT sell them or assign jobs - they belong to a camp!
        if (
            isPlayer &&
            this.kidnapper.level() instanceof
                net.minecraft.server.level.ServerLevel serverLevel
        ) {
            com.tiedup.remake.prison.PrisonerManager manager =
                com.tiedup.remake.prison.PrisonerManager.get(serverLevel);
            com.tiedup.remake.prison.PrisonerRecord record = manager.getRecord(
                slave.asLivingEntity().getUUID()
            );
            java.util.UUID campId = record.getCampId();

            if (campId != null) {
                // This is a recaptured camp prisoner - DO NOT sell or assign job
                // BringToCellGoal will handle bringing them back to their original camp
                //
                // Flow: ESCAPED → CAPTURED (campId preserved) → BringToCellGoal → IMPRISONED
                //
                // Verify the camp is still alive before skipping
                com.tiedup.remake.cells.CampOwnership ownership =
                    com.tiedup.remake.cells.CampOwnership.get(serverLevel);
                if (ownership.isCampAlive(campId)) {
                    TiedUpMod.LOGGER.info(
                        "[KidnapperDecideNextActionGoal] {} recaptured escaped prisoner {} - returning to camp {}",
                        this.kidnapper.getNpcName(),
                        slave.getKidnappedName(),
                        campId.toString().substring(0, 8)
                    );
                    // Goal will stop on next tick - BringToCellGoal will take over
                    return;
                } else {
                    // Camp is dead - release with grace period and treat as new prisoner
                    TiedUpMod.LOGGER.info(
                        "[KidnapperDecideNextActionGoal] {} captured escaped prisoner {} but camp {} is dead - treating as new prisoner",
                        this.kidnapper.getNpcName(),
                        slave.getKidnappedName(),
                        campId.toString().substring(0, 8)
                    );
                    // Release prisoner from dead camp - PrisonerManager clears ownership data
                    manager.release(
                        slave.asLivingEntity().getUUID(),
                        serverLevel.getGameTime(),
                        0
                    );
                    // Continue with normal decision flow
                }
            }
        }

        float roll = this.kidnapper.getRandom().nextFloat();

        // NPCs (Damsels) can't do jobs or be robbed - only sell them
        if (!isPlayer) {
            // Always sell NPCs
            if (this.kidnapper.startSale()) {
                TiedUpMod.LOGGER.info(
                    "[KidnapperDecideNextActionGoal] {} decided to sell NPC {}",
                    this.kidnapper.getNpcName(),
                    slave.getKidnappedName()
                );
            } else {
                // Can't sell NPC, just flee
                TiedUpMod.LOGGER.warn(
                    "[KidnapperDecideNextActionGoal] {} couldn't sell NPC, fleeing",
                    this.kidnapper.getNpcName()
                );
                this.kidnapper.setGetOutState(true);
            }
            return;
        }

        // For players: theft, sell, or job
        if (roll < THEFT_CHANCE) {
            // THEFT: Steal items, release (but keep tied), flee
            performTheft((ServerPlayer) slave.asLivingEntity());
        } else if (roll < THEFT_CHANCE + SELL_CHANCE) {
            // SELL: Bring to merchant
            if (this.kidnapper.startSale()) {
                TiedUpMod.LOGGER.info(
                    "[KidnapperDecideNextActionGoal] {} decided to sell {}",
                    this.kidnapper.getNpcName(),
                    slave.getKidnappedName()
                );
            } else {
                // Sale failed, try job instead
                assignJobFallback(slave);
            }
        } else {
            // JOB: Assign labor task
            if (this.kidnapper.assignRandomJob()) {
                TiedUpMod.LOGGER.info(
                    "[KidnapperDecideNextActionGoal] {} assigned job to {}",
                    this.kidnapper.getNpcName(),
                    slave.getKidnappedName()
                );

                // Announce job to slave
                Player player = (Player) slave.asLivingEntity();
                this.kidnapper.talkTo(player, DialogueCategory.JOB_ASSIGNED);

                // System message with job details
                var job = this.kidnapper.getCurrentJob();
                if (job != null) {
                    SystemMessageManager.sendToPlayer(
                        player,
                        MessageCategory.SLAVE_JOB_ASSIGNED,
                        job.toDisplayString()
                    );
                }
            } else {
                // Job failed, try sale instead
                if (this.kidnapper.startSale()) {
                    TiedUpMod.LOGGER.info(
                        "[KidnapperDecideNextActionGoal] {} decided to sell {} (job fallback)",
                        this.kidnapper.getNpcName(),
                        slave.getKidnappedName()
                    );
                } else {
                    // Both failed - try theft as last resort
                    performTheft((ServerPlayer) slave.asLivingEntity());
                }
            }
        }

        // Goal will stop on next canContinueToUse check (conditions changed)
    }

    /**
     * Fallback to assigning a job if sale fails.
     * Only works for Players - NPCs just trigger flee.
     */
    private void assignJobFallback(IBondageState slave) {
        // NPCs can't do jobs - just flee
        if (!(slave.asLivingEntity() instanceof Player player)) {
            TiedUpMod.LOGGER.info(
                "[KidnapperDecideNextActionGoal] {} can't sell NPC, fleeing",
                this.kidnapper.getNpcName()
            );
            this.kidnapper.setGetOutState(true);
            return;
        }

        if (this.kidnapper.assignRandomJob()) {
            TiedUpMod.LOGGER.info(
                "[KidnapperDecideNextActionGoal] {} assigned job to {} (sale fallback)",
                this.kidnapper.getNpcName(),
                slave.getKidnappedName()
            );

            this.kidnapper.talkTo(player, DialogueCategory.JOB_ASSIGNED);
        } else {
            // Both failed - just flee
            TiedUpMod.LOGGER.warn(
                "[KidnapperDecideNextActionGoal] {} couldn't decide, fleeing",
                this.kidnapper.getNpcName()
            );
            this.kidnapper.setGetOutState(true);
        }
    }

    /**
     * Perform theft: steal items, take collar, release, grant immunity, flee.
     *
     * BUG FIX: Integrated from KidnapperThiefGoal.
     * This is now the "theft" decision option (30% chance).
     *
     * Steps:
     * 1. Steal 1-3 random items from player inventory
     * 2. Take collar back
     * 3. Release from captivity (but keep tied up!)
     * 4. Grant 2-minute robbed immunity
     * 5. Flee
     */
    private void performTheft(ServerPlayer player) {
        IBondageState captive = this.kidnapper.getCaptive();
        if (captive == null) return;

        TiedUpMod.LOGGER.info(
            "[KidnapperDecideNextActionGoal] {} performing theft on {}",
            this.kidnapper.getNpcName(),
            player.getName().getString()
        );

        // 1. Steal random items from inventory
        List<ItemStack> stolenItems = stealRandomItems(player);

        // 2. Take collar back
        ItemStack collar = captive.forceUnequip(BodyRegionV2.NECK); // Force remove

        // 3. Release from captivity (but keep tied up!)
        // This releases the leash but does NOT remove the bind
        captive.free(false); // false = don't drop leash (we take it)

        // 3b. Clean up PrisonerManager state (CAPTURED -> FREE)
        EscapeMonitorService.get().escape(
            (ServerLevel) player.level(),
            player.getUUID(),
            "theft_release"
        );

        // 4. Grant robbed immunity (2 minutes)
        this.kidnapper.grantRobbedImmunity(player.getUUID());

        // 5. Notify player
        StringBuilder message = new StringBuilder();
        message.append("The kidnapper robbed you");
        if (!stolenItems.isEmpty()) {
            message.append(" of ");
            for (int i = 0; i < stolenItems.size(); i++) {
                if (i > 0) message.append(", ");
                ItemStack item = stolenItems.get(i);
                message
                    .append(item.getCount())
                    .append("x ")
                    .append(item.getHoverName().getString());
            }
        }
        message.append(" and fled!");

        player.sendSystemMessage(
            Component.literal(message.toString()).withStyle(ChatFormatting.RED)
        );

        player.sendSystemMessage(
            Component.translatable(
                "goal.tiedup.kidnapper_decide.still_tied"
            ).withStyle(ChatFormatting.YELLOW)
        );

        // 6. Kidnapper flees
        this.kidnapper.setGetOutState(true);

        TiedUpMod.LOGGER.info(
            "[KidnapperDecideNextActionGoal] {} completed theft and is fleeing",
            this.kidnapper.getNpcName()
        );
    }

    /**
     * Steal random items from the player's inventory.
     *
     * @param player The player to steal from
     * @return List of stolen items (for notification)
     */
    private List<ItemStack> stealRandomItems(ServerPlayer player) {
        List<ItemStack> stolenItems = new ArrayList<>();
        int itemsToSteal =
            MIN_ITEMS_TO_STEAL +
            this.kidnapper.getRandom().nextInt(
                MAX_ITEMS_TO_STEAL - MIN_ITEMS_TO_STEAL + 1
            );

        // Get non-empty inventory slots (excluding armor and offhand)
        List<Integer> nonEmptySlots = new ArrayList<>();
        for (int i = 0; i < player.getInventory().items.size(); i++) {
            ItemStack stack = player.getInventory().items.get(i);
            if (!stack.isEmpty()) {
                nonEmptySlots.add(i);
            }
        }

        // Shuffle and steal
        java.util.Collections.shuffle(
            nonEmptySlots,
            new java.util.Random(this.kidnapper.getRandom().nextLong())
        );

        for (int i = 0; i < Math.min(itemsToSteal, nonEmptySlots.size()); i++) {
            int slotIndex = nonEmptySlots.get(i);
            ItemStack original = player.getInventory().items.get(slotIndex);

            // Take the whole stack
            ItemStack stolen = original.copy();
            player.getInventory().items.set(slotIndex, ItemStack.EMPTY);

            stolenItems.add(stolen);

            TiedUpMod.LOGGER.debug(
                "[KidnapperDecideNextActionGoal] Stole {} from slot {}",
                stolen.getHoverName().getString(),
                slotIndex
            );
        }

        return stolenItems;
    }
}
