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

import com.tiedup.remake.cells.CellDataV2;
import com.tiedup.remake.cells.CellRegistryV2;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityLaborGuard;
import com.tiedup.remake.labor.LaborTask;
import com.tiedup.remake.prison.LaborRecord;
import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.prison.PrisonerRecord;
import com.tiedup.remake.prison.service.EscapeMonitorService;
import com.tiedup.remake.state.IRestrainable;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.util.MessageDispatcher;
import com.tiedup.remake.util.RestraintApplicator;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
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;

/**
 * AI Goal for monitoring prisoner activity, distance, and task progress.
 *
 * Replaces the fragile MaidIdleGoal.monitorWorkers() with guard-based monitoring.
 * Runs in parallel with other goals (Flag.NONE).
 *
 * Key improvements over MaidIdleGoal:
 * - Shock only resets on TASK PROGRESS, not mere movement
 * - Distance-based escape with 15-second countdown
 * - Escalating punishment (4 levels)
 */
public class GuardMonitorGoal extends Goal {

    private final EntityLaborGuard guard;

    // Check intervals (ticks)
    private static final int DISTANCE_CHECK_INTERVAL = 20; // 1 sec
    private static final int ACTIVITY_CHECK_INTERVAL = 60; // 3 sec
    private static final int TASK_CHECK_INTERVAL = 100; // 5 sec

    // Escape countdown
    private static final int ESCAPE_COUNTDOWN_TICKS = 300; // 15 sec
    private int escapeCountdown = -1; // -1 = not active
    private boolean warningIssued = false;

    // Activity tracking
    private int lastKnownProgress = -1;

    // Punishment cooldown
    private static final int SHOCK_COOLDOWN_TICKS = 400; // 20 sec
    private int shockCooldownTimer = 0;

    // Inactivity tracking
    private long lastProgressTime = 0;
    private static final long INACTIVITY_THRESHOLD_TICKS = 600; // 30 sec
    private static final long COMBAT_INACTIVITY_THRESHOLD_TICKS = 2400; // 2 min (mobs are time-of-day dependent)

    // Counters
    private int distanceCounter = 0;
    private int activityCounter = 0;
    private int taskCounter = 0;

    // Return-to-camp tracking (PENDING_RETURN phase)
    private static final int RETURN_CHECK_INTERVAL = 300; // 15 sec
    private int returnCheckCounter = 0;
    private double closestDistanceToCamp = Double.MAX_VALUE;

    @javax.annotation.Nullable
    private BlockPos campPos = null;

    public GuardMonitorGoal(EntityLaborGuard guard) {
        this.guard = guard;
        // No flags - runs in parallel with Follow and other goals
        this.setFlags(EnumSet.noneOf(Goal.Flag.class));
    }

    @Override
    public boolean canUse() {
        if (guard.isTiedUp()) {
            return false;
        }

        ServerPlayer prisoner = guard.getPrisoner();
        return prisoner != null && prisoner.isAlive();
    }

    @Override
    public boolean canContinueToUse() {
        return canUse();
    }

    @Override
    public void start() {
        this.escapeCountdown = -1;
        this.warningIssued = false;
        this.lastKnownProgress = -1;
        this.shockCooldownTimer = 0;
        this.lastProgressTime = guard.level().getGameTime();
        this.returnCheckCounter = 0;
        this.closestDistanceToCamp = Double.MAX_VALUE;
        this.campPos = null;
    }

    @Override
    public void stop() {
        this.escapeCountdown = -1;
        this.warningIssued = false;
    }

    @Override
    public void tick() {
        if (!(guard.level() instanceof ServerLevel level)) {
            return;
        }

        ServerPlayer prisoner = guard.getPrisoner();
        if (prisoner == null || !prisoner.isAlive()) return;

        // Decrement cooldowns
        if (shockCooldownTimer > 0) {
            shockCooldownTimer--;
        }

        // Tick escape countdown
        if (escapeCountdown > 0) {
            escapeCountdown--;

            // Shock every 100 ticks (5 sec) during countdown
            if (escapeCountdown % 100 == 0 && escapeCountdown > 0) {
                guardSay(
                    prisoner,
                    "guard.labor.escape_warning",
                    "Get back here! NOW!"
                );
                shockPrisoner(prisoner, "Return to the guard NOW!", 1.0f);
            }

            if (escapeCountdown == 0) {
                // Escape triggered
                triggerEscape(level, prisoner, "fled from guard");
                return;
            }
        }

        // Distance check
        distanceCounter++;
        if (distanceCounter >= DISTANCE_CHECK_INTERVAL) {
            distanceCounter = 0;
            checkDistance(level, prisoner);
        }

        // Activity check
        activityCounter++;
        if (activityCounter >= ACTIVITY_CHECK_INTERVAL) {
            activityCounter = 0;
            checkActivity(level, prisoner);
        }

        // Task progress check
        taskCounter++;
        if (taskCounter >= TASK_CHECK_INTERVAL) {
            taskCounter = 0;
            checkTaskProgress(level, prisoner);
        }

        // Return-to-camp check (PENDING_RETURN phase only)
        returnCheckCounter++;
        if (returnCheckCounter >= RETURN_CHECK_INTERVAL) {
            returnCheckCounter = 0;
            checkReturnProgress(level, prisoner);
        }
    }

    /**
     * Check distance between guard and prisoner.
     * Triggers escape countdown if prisoner is too far.
     */
    private void checkDistance(ServerLevel level, ServerPlayer prisoner) {
        double distance = guard.distanceTo(prisoner);

        if (distance > EntityLaborGuard.WARNING_RADIUS) {
            // Prisoner is outside warning radius
            if (escapeCountdown < 0) {
                // Start escape countdown
                escapeCountdown = ESCAPE_COUNTDOWN_TICKS;
                warningIssued = true;

                guardSay(
                    prisoner,
                    "guard.labor.escape_warning",
                    "Where do you think you're going?!"
                );
                shockPrisoner(
                    prisoner,
                    "Return to the guard immediately!",
                    1.0f
                );

                prisoner.sendSystemMessage(
                    Component.translatable(
                        "goal.tiedup.guard_monitor.too_far"
                    ).withStyle(ChatFormatting.RED, ChatFormatting.BOLD)
                );

                TiedUpMod.LOGGER.info(
                    "[GuardMonitorGoal] Prisoner {} is {} blocks from guard - escape countdown started",
                    prisoner.getName().getString(),
                    distance
                );
            }
        } else {
            // Prisoner is within warning radius
            if (escapeCountdown >= 0) {
                // Reset countdown - prisoner returned
                escapeCountdown = -1;
                warningIssued = false;

                guardSay(
                    prisoner,
                    "guard.labor.returned",
                    "Don't try that again."
                );

                prisoner.sendSystemMessage(
                    Component.translatable(
                        "goal.tiedup.guard_monitor.returned"
                    ).withStyle(ChatFormatting.YELLOW)
                );
            }
        }
    }

    /**
     * Check prisoner activity (task progress based, NOT movement based).
     */
    private void checkActivity(ServerLevel level, ServerPlayer prisoner) {
        UUID prisonerId = prisoner.getUUID();
        PrisonerManager manager = PrisonerManager.get(level);
        LaborRecord labor = manager.getLaborRecord(prisonerId);

        if (labor.getPhase() != LaborRecord.WorkPhase.WORKING) {
            return;
        }

        long currentTime = level.getGameTime();
        long timeSinceProgress = currentTime - lastProgressTime;

        // Combat tasks get a longer grace period since mobs are time-of-day dependent
        LaborTask task = labor.getTask();
        long threshold = (task != null && task.isCombatTask())
            ? COMBAT_INACTIVITY_THRESHOLD_TICKS
            : INACTIVITY_THRESHOLD_TICKS;

        if (timeSinceProgress > threshold && shockCooldownTimer <= 0) {
            // Before escalating, check real-time progress (task.checkProgress runs on 5s interval,
            // but activity check runs on 3s - progress may be stale)
            if (task != null) {
                task.checkProgress(prisoner, level);
                int currentProgress = task.getProgress();
                if (
                    lastKnownProgress >= 0 &&
                    currentProgress > lastKnownProgress
                ) {
                    // Player made progress - reset instead of punishing
                    lastProgressTime = currentTime;
                    labor.resetShockLevel();
                    lastKnownProgress = currentProgress;
                    return;
                }
            }

            // Truly inactive - escalate punishment
            int shockLevel = labor.getShockLevel();
            escalatePunishment(prisoner, labor, currentTime, shockLevel);
        }
    }

    /**
     * Check task completion progress.
     */
    private void checkTaskProgress(ServerLevel level, ServerPlayer prisoner) {
        UUID prisonerId = prisoner.getUUID();
        PrisonerManager manager = PrisonerManager.get(level);
        LaborRecord labor = manager.getLaborRecord(prisonerId);

        if (labor.getPhase() != LaborRecord.WorkPhase.WORKING) {
            return;
        }

        LaborTask task = labor.getTask();
        if (task == null) return;

        long currentTime = level.getGameTime();

        // Check progress
        task.checkProgress(prisoner, level);

        if (task.isComplete()) {
            // Task complete
            labor.completeTask(currentTime);

            // Tell prisoner to walk back to camp
            UUID campId = guard.getCampId();
            if (campId != null) {
                List<CellDataV2> campCells = CellRegistryV2.get(
                    level
                ).getCellsByCamp(campId);
                if (!campCells.isEmpty()) {
                    BlockPos campCenter = campCells.get(0).getCorePos();
                    String direction = getCardinalDirection(
                        prisoner.blockPosition(),
                        campCenter
                    );
                    int distance = (int) Math.sqrt(
                        prisoner.blockPosition().distSqr(campCenter)
                    );
                    guardSay(
                        prisoner,
                        "guard.labor.task_complete",
                        "Good work. Now walk back to camp."
                    );
                    guard.guardSay(
                        prisoner,
                        "Camp is " +
                            distance +
                            " blocks to the " +
                            direction +
                            ". Follow me."
                    );
                } else {
                    guardSay(
                        prisoner,
                        "guard.labor.task_complete",
                        "Good work. Now walk back to camp."
                    );
                }
            } else {
                guardSay(
                    prisoner,
                    "guard.labor.task_complete",
                    "Good work. Now walk back to camp."
                );
            }

            TiedUpMod.LOGGER.info(
                "[GuardMonitorGoal] {} completed task",
                prisoner.getName().getString()
            );
            return;
        }

        // Check if progress increased
        int currentProgress = task.getProgress();
        if (lastKnownProgress >= 0 && currentProgress > lastKnownProgress) {
            // Progress made - reset inactivity tracking
            lastProgressTime = currentTime;
            labor.resetShockLevel();

            labor.updateActivity(
                currentTime,
                prisoner.blockPosition().getX(),
                prisoner.blockPosition().getY(),
                prisoner.blockPosition().getZ()
            );
        }
        lastKnownProgress = currentProgress;
    }

    /**
     * Check if prisoner is walking back toward camp (PENDING_RETURN phase).
     * If moving away from their closest recorded distance, shock them.
     */
    private void checkReturnProgress(ServerLevel level, ServerPlayer prisoner) {
        UUID prisonerId = prisoner.getUUID();
        PrisonerManager manager = PrisonerManager.get(level);
        LaborRecord labor = manager.getLaborRecord(prisonerId);

        if (labor.getPhase() != LaborRecord.WorkPhase.PENDING_RETURN) {
            // Reset tracking when not in return phase
            closestDistanceToCamp = Double.MAX_VALUE;
            campPos = null;
            return;
        }

        // Resolve camp position once
        if (campPos == null) {
            UUID campId = guard.getCampId();
            if (campId != null) {
                List<CellDataV2> campCells = CellRegistryV2.get(
                    level
                ).getCellsByCamp(campId);
                if (!campCells.isEmpty()) {
                    campPos = campCells.get(0).getCorePos();
                }
            }
            if (campPos == null) return;
        }

        double currentDist = Math.sqrt(
            prisoner.blockPosition().distSqr(campPos)
        );

        if (currentDist < closestDistanceToCamp) {
            // Getting closer — update closest
            closestDistanceToCamp = currentDist;
        } else if (currentDist > closestDistanceToCamp + 10) {
            // Moving away (10 block tolerance) — shock
            shockPrisoner(prisoner, "Walk back to camp!", 1.0f);
            guardSay(
                prisoner,
                "guard.labor.return_warning",
                "Wrong way! Get back to camp NOW!"
            );

            // Show direction
            String direction = getCardinalDirection(
                prisoner.blockPosition(),
                campPos
            );
            int dist = (int) currentDist;
            guard.guardSay(
                prisoner,
                "Camp is " + dist + " blocks to the " + direction + "."
            );
        }
    }

    /**
     * Escalate punishment based on shock level.
     */
    private void escalatePunishment(
        ServerPlayer prisoner,
        LaborRecord labor,
        long currentTime,
        int shockLevel
    ) {
        switch (shockLevel) {
            case 0 -> {
                // Warning: randomly verbal or physical (whip)
                if (guard.getRandom().nextBoolean()) {
                    // Verbal warning
                    guardSay(
                        prisoner,
                        "guard.labor.warning",
                        "I'm watching you. Get to work!"
                    );
                } else {
                    // Walk up and whip
                    guard.setNeedsWhip(true);
                }
                labor.incrementShockLevel();
                shockCooldownTimer = SHOCK_COOLDOWN_TICKS;
            }
            case 1 -> {
                // First shock
                shockPrisoner(prisoner, "Get back to work!", 1.0f);
                guardSay(
                    prisoner,
                    "guard.labor.shock_1",
                    "Warning (1/3): Work or face punishment!"
                );
                labor.incrementShockLevel();
                shockCooldownTimer = SHOCK_COOLDOWN_TICKS;
            }
            case 2 -> {
                // Second shock + tighten binds
                shockPrisoner(prisoner, "Last warning!", 2.0f);

                IRestrainable cap = KidnappedHelper.getKidnappedState(prisoner);
                if (cap != null) {
                    RestraintApplicator.tightenBind(cap, prisoner);
                }

                guardSay(
                    prisoner,
                    "guard.labor.shock_2",
                    "Warning (2/3): Your binds have been tightened!"
                );
                labor.incrementShockLevel();
                shockCooldownTimer = SHOCK_COOLDOWN_TICKS;
            }
            default -> {
                // Max punishment - fail task
                shockPrisoner(prisoner, "Task failed!", 3.0f);
                labor.failTask(currentTime);

                guardSay(
                    prisoner,
                    "guard.labor.task_failed",
                    "Task failed due to inactivity! You will be returned to your cell."
                );

                TiedUpMod.LOGGER.info(
                    "[GuardMonitorGoal] {} task failed due to inactivity (shock level 3)",
                    prisoner.getName().getString()
                );
            }
        }
    }

    private void shockPrisoner(
        ServerPlayer prisoner,
        String message,
        float intensity
    ) {
        if (!prisoner.isAlive()) return;
        IRestrainable cap = KidnappedHelper.getKidnappedState(prisoner);
        if (cap != null) {
            cap.shockKidnapped(message, intensity);
        }
    }

    private void guardSay(
        ServerPlayer prisoner,
        String dialogueId,
        String fallback
    ) {
        String text = com.tiedup.remake.dialogue.DialogueBridge.getDialogue(
            guard,
            prisoner,
            dialogueId
        );
        if (text == null) {
            text = fallback;
        }
        MessageDispatcher.talkTo(guard, prisoner, text);
    }

    private static String getCardinalDirection(BlockPos from, BlockPos to) {
        int dx = to.getX() - from.getX();
        int dz = to.getZ() - from.getZ();

        String ns = "";
        String ew = "";
        if (Math.abs(dz) > 2) ns = dz < 0 ? "north" : "south";
        if (Math.abs(dx) > 2) ew = dx > 0 ? "east" : "west";

        if (!ns.isEmpty() && !ew.isEmpty()) return ns + "-" + ew;
        if (!ns.isEmpty()) return ns;
        if (!ew.isEmpty()) return ew;
        return "nearby";
    }

    private void triggerEscape(
        ServerLevel level,
        ServerPlayer prisoner,
        String reason
    ) {
        TiedUpMod.LOGGER.info(
            "[GuardMonitorGoal] Escape triggered for {} - reason: {}",
            prisoner.getName().getString(),
            reason
        );

        // Clear guardId before escape so PrisonerService doesn't double-despawn
        PrisonerManager manager = PrisonerManager.get(level);
        LaborRecord labor = manager.getLaborRecord(prisoner.getUUID());
        labor.setGuardId(null);

        EscapeMonitorService.get().escape(level, prisoner.getUUID(), reason);

        // Discard the guard entity itself
        guard.discard();
    }
}
