package com.tiedup.remake.entities;

import com.tiedup.remake.cells.CellDataV2;
import com.tiedup.remake.cells.CellRegistryV2;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.SpeakerType;
import com.tiedup.remake.entities.ai.guard.GuardFightBackGoal;
import com.tiedup.remake.entities.ai.guard.GuardFollowPrisonerGoal;
import com.tiedup.remake.entities.ai.guard.GuardHuntMonstersGoal;
import com.tiedup.remake.entities.ai.guard.GuardMonitorGoal;
import com.tiedup.remake.entities.skins.LaborGuardSkinManager;
import com.tiedup.remake.labor.LaborTask;
import com.tiedup.remake.prison.LaborRecord;
import com.tiedup.remake.prison.PrisonerManager;
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.List;
import java.util.UUID;
import javax.annotation.Nullable;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;

/**
 * Labor Guard entity - Physical guard that follows and monitors a prisoner during labor.
 *
 * Extends EntityDamsel for the player model, skin system, and IRestrainable interface
 * (the guard itself can be captured/tied up as an escape mechanic).
 *
 * Spawned by MaidExtractGoal after extraction, despawned by MaidReturnGoal when
 * the prisoner returns to cell.
 */
public class EntityLaborGuard extends EntityDamsel {

    // ==================== CONSTANTS ====================

    /** Distance at which prisoner gets warning + escape countdown starts */
    public static final double WARNING_RADIUS = 20.0;

    /** Ticks before escape after leaving warning radius (15 seconds) */
    public static final int ESCAPE_COUNTDOWN_TICKS = 300;

    /** Distance at which guard teleports to prisoner */
    public static final double TELEPORT_DISTANCE = 32.0;

    /** Guard name color — steel blue */
    public static final int GUARD_NAME_COLOR = 0x4682B4;

    // ==================== SERVER FIELDS ====================

    @Nullable
    private UUID prisonerUUID;

    @Nullable
    private UUID campId;

    @Nullable
    private UUID spawnerMaidId;

    /** Prevents registerGoals from being called twice by parent constructor */
    private boolean goalsRegistered = false;

    /** Prevents triggerEscapeOnIncapacitated from firing multiple times */
    private boolean escapeTriggered = false;

    /** Cooldown to prevent double-speak from Forge's dual interaction packets */
    private long lastInteractTick = -100;

    /** Set by GuardMonitorGoal when prisoner needs physical punishment */
    private boolean needsWhip = false;

    // ==================== CONSTRUCTOR ====================

    public EntityLaborGuard(
        EntityType<? extends EntityLaborGuard> type,
        Level level
    ) {
        super(type, level);
    }

    // ==================== ATTRIBUTES ====================

    public static AttributeSupplier.Builder createAttributes() {
        return Mob.createMobAttributes()
            .add(Attributes.MAX_HEALTH, 30.0)
            .add(Attributes.MOVEMENT_SPEED, 0.30)
            .add(Attributes.ATTACK_DAMAGE, 6.0)
            .add(Attributes.FOLLOW_RANGE, 40.0)
            .add(Attributes.KNOCKBACK_RESISTANCE, 0.5);
    }

    // ==================== AI GOALS ====================

    /**
     * Override registerGoals to set up guard-specific AI.
     * Complete override - does not use DamselAIController goals.
     *
     * IMPORTANT: This is called twice during construction:
     * 1. By Mob() constructor (via super chain)
     * 2. By EntityDamsel() constructor at line 455
     * We use a flag to ensure goals are only registered once.
     */
    @Override
    protected void registerGoals() {
        if (this.goalsRegistered) {
            return;
        }
        // Also skip if called during Mob's constructor before goalSelector is set
        // (shouldn't happen, but safety check)
        if (this.goalSelector == null) {
            return;
        }

        this.goalSelector.addGoal(0, new FloatGoal(this));
        this.goalSelector.addGoal(1, new GuardHuntMonstersGoal(this));
        this.goalSelector.addGoal(2, new GuardFightBackGoal(this));
        this.goalSelector.addGoal(3, new MeleeAttackGoal(this, 1.2, false));
        this.goalSelector.addGoal(4, new GuardFollowPrisonerGoal(this));
        this.goalSelector.addGoal(
            5,
            new com.tiedup.remake.entities.ai.guard.GuardWhipGoal(this)
        );
        this.goalSelector.addGoal(6, new GuardMonitorGoal(this));
        this.goalSelector.addGoal(
            8,
            new LookAtPlayerGoal(this, Player.class, 8.0F)
        );
        this.goalSelector.addGoal(9, new RandomLookAroundGoal(this));

        // Register command goals so the guard responds to player commands
        com.tiedup.remake.entities.damsel.components.DamselAIController.registerCommandGoals(
            this.goalSelector,
            this,
            7
        );

        this.goalsRegistered = true;
    }

    // ==================== SKIN TEXTURE ====================

    /**
     * Override to use guard/ texture folder instead of damsel/.
     * DamselAppearance.getSkinTexture() hardcodes "textures/entity/damsel/",
     * so we must override to point to "textures/entity/guard/".
     */
    @Override
    public ResourceLocation getSkinTexture() {
        String variantId = this.getVariantId();
        if (!variantId.isEmpty()) {
            return ResourceLocation.fromNamespaceAndPath(
                "tiedup",
                "textures/entity/guard/" + variantId + ".png"
            );
        }
        // Fallback to first registered guard skin
        DamselVariant fallback = LaborGuardSkinManager.CORE.getVariantForEntity(
            this.getUUID()
        );
        if (fallback != null) {
            return fallback.texture();
        }
        return ResourceLocation.fromNamespaceAndPath(
            "tiedup",
            "textures/entity/guard/feifei.png"
        );
    }

    // ==================== VARIANT SYSTEM ====================

    /**
     * Override to use LaborGuardSkinManager instead of DamselSkinManager.
     */
    @Override
    public void onAddedToWorld() {
        super.onAddedToWorld();

        // Override variant with guard skin (server-side only)
        if (
            !this.level().isClientSide &&
            (this.getVariantId().isEmpty() ||
                LaborGuardSkinManager.CORE.getVariant(this.getVariantId()) ==
                null)
        ) {
            DamselVariant variant =
                LaborGuardSkinManager.CORE.getVariantForEntity(this.getUUID());
            if (variant != null) {
                this.setVariant(variant);
            }
        }

        // Safety: ensure guard always has a name (belt-and-suspenders for variant loading failures)
        if (!this.level().isClientSide && this.getNpcName().isEmpty()) {
            this.setNpcName("Guard");
        }
    }

    // ==================== DISPLAY ====================

    @Override
    public Component getDisplayName() {
        return Component.literal(this.getNpcName()).withStyle(
            Style.EMPTY.withColor(GUARD_NAME_COLOR)
        );
    }

    // ==================== DIALOGUE (IDialogueSpeaker overrides) ====================

    @Override
    public SpeakerType getSpeakerType() {
        return SpeakerType.GUARD;
    }

    @Override
    @Nullable
    public com.tiedup.remake.personality.PersonalityType getSpeakerPersonality() {
        return com.tiedup.remake.personality.PersonalityType.FIERCE;
    }

    @Override
    public int getSpeakerMood() {
        return 50; // Neutral
    }

    @Override
    @Nullable
    public String getTargetRelation(Player player) {
        if (prisonerUUID != null && player.getUUID().equals(prisonerUUID)) {
            return "prisoner";
        }
        return null;
    }

    // ==================== TICK ====================

    @Override
    public void tick() {
        super.tick();

        if (
            !this.level().isClientSide &&
            this.level() instanceof ServerLevel level
        ) {
            // Auto-cleanup: if tied up, trigger escape (guard is incapacitated)
            if (this.isTiedUp() && !escapeTriggered) {
                triggerEscapeOnIncapacitated(level);
                return;
            }

            // Orphan/duplication check every 5 seconds
            if (prisonerUUID != null && tickCount % 100 == 0) {
                PrisonerManager manager = PrisonerManager.get(level);
                LaborRecord labor = manager.getLaborRecord(prisonerUUID);
                UUID assignedGuard = labor.getGuardId();

                if (assignedGuard == null) {
                    // Guard reference was cleared (escape, reset, etc.) - orphan, discard self
                    TiedUpMod.LOGGER.info(
                        "[EntityLaborGuard] Orphan guard detected (guardId=null), discarding self {}",
                        this.getUUID().toString().substring(0, 8)
                    );
                    this.discard();
                    return;
                }

                if (!assignedGuard.equals(this.getUUID())) {
                    // Different guard is assigned - duplicate, discard self
                    TiedUpMod.LOGGER.warn(
                        "[EntityLaborGuard] Duplicate guard detected, discarding self (assigned={}, self={})",
                        assignedGuard.toString().substring(0, 8),
                        this.getUUID().toString().substring(0, 8)
                    );
                    this.discard();
                    return;
                }
            }
        }
    }

    // ==================== DAMAGE HANDLING ====================

    /**
     * Override hurt to:
     * 1. Reduce monster damage by 50%
     * 2. Punish prisoner if they attack the guard
     */
    @Override
    public boolean hurt(DamageSource source, float amount) {
        if (!this.level().isClientSide) {
            // 50% damage reduction from monsters
            if (
                source.getEntity() instanceof
                    net.minecraft.world.entity.monster.Monster
            ) {
                amount *= 0.5f;
            }

            // If attacked by the prisoner, punish them (don't fight back)
            if (
                source.getEntity() instanceof ServerPlayer player &&
                prisonerUUID != null &&
                player.getUUID().equals(prisonerUUID)
            ) {
                punishPrisonerAttack(player);
                return false; // Guard ignores prisoner damage
            }
        }

        return super.hurt(source, amount);
    }

    /**
     * Punish prisoner for attacking the guard.
     */
    private void punishPrisonerAttack(ServerPlayer prisoner) {
        IRestrainable cap = KidnappedHelper.getKidnappedState(prisoner);
        if (cap != null) {
            cap.shockKidnapped("Don't touch your guard!", 2.0f);
            RestraintApplicator.tightenBind(cap, prisoner);
        }

        prisoner.sendSystemMessage(
            Component.translatable("entity.tiedup.guard.attack_punished").withStyle(
                ChatFormatting.DARK_RED
            )
        );
    }

    // ==================== INTERACTION ====================

    @Override
    protected InteractionResult mobInteract(
        Player player,
        InteractionHand hand
    ) {
        if (hand != InteractionHand.MAIN_HAND) {
            return super.mobInteract(player, hand);
        }

        if (
            !this.level().isClientSide &&
            player instanceof ServerPlayer serverPlayer &&
            prisonerUUID != null &&
            player.getUUID().equals(prisonerUUID) &&
            this.level() instanceof ServerLevel serverLevel
        ) {
            // Cooldown: Forge sends INTERACT_AT + INTERACT per right-click
            long currentTick = this.tickCount;
            if (currentTick - lastInteractTick < 5) {
                return InteractionResult.SUCCESS;
            }
            lastInteractTick = currentTick;

            PrisonerManager manager = PrisonerManager.get(serverLevel);
            LaborRecord labor = manager.getLaborRecord(prisonerUUID);
            LaborTask task = labor.getTask();
            LaborRecord.WorkPhase phase = labor.getPhase();

            if (phase == LaborRecord.WorkPhase.WORKING) {
                if (task != null) {
                    // Refresh progress before showing
                    task.checkProgress(serverPlayer, serverLevel);

                    int progress = task.getProgress();
                    int quota = task.getQuota();
                    int percent = task.getProgressPercent();
                    guardSay(
                        serverPlayer,
                        "Task: " +
                            task.getDescription() +
                            " — " +
                            progress +
                            "/" +
                            quota +
                            " (" +
                            percent +
                            "%)"
                    );
                } else {
                    guardSay(serverPlayer, "Get to work!");
                }

                showCampDirection(serverPlayer, serverLevel);
                return InteractionResult.SUCCESS;
            }

            if (phase == LaborRecord.WorkPhase.PENDING_RETURN) {
                guardSay(
                    serverPlayer,
                    "guard.labor.pending_return",
                    "Walk back to camp. Follow me."
                );
                showCampDirection(serverPlayer, serverLevel);
                return InteractionResult.SUCCESS;
            }

            // Catch-all for other phases (EXTRACTING, RETURNING, etc.)
            guardSay(serverPlayer, "Follow me.");
            return InteractionResult.SUCCESS;
        }

        return super.mobInteract(player, hand);
    }

    // ==================== HELPERS ====================

    /**
     * Send a formatted guard speech message to a player.
     * Tries JSON dialogue first, falls back to provided message.
     */
    public void guardSay(
        ServerPlayer player,
        String dialogueId,
        String fallback
    ) {
        String text = com.tiedup.remake.dialogue.DialogueBridge.getDialogue(
            this,
            player,
            dialogueId
        );
        if (text == null) {
            text = fallback;
        }
        MessageDispatcher.talkTo(this, player, text);
    }

    /**
     * Send a formatted guard speech message (no dialogue ID, direct message).
     */
    public void guardSay(ServerPlayer player, String message) {
        MessageDispatcher.talkTo(this, player, message);
    }

    /**
     * Show camp direction to a player.
     */
    private void showCampDirection(ServerPlayer player, ServerLevel level) {
        if (campId == null) return;
        List<CellDataV2> campCells = CellRegistryV2.get(level).getCellsByCamp(
            campId
        );
        if (campCells.isEmpty()) return;
        BlockPos campCenter = campCells.get(0).getCorePos();
        String direction = getCardinalDirection(
            player.blockPosition(),
            campCenter
        );
        int distance = (int) Math.sqrt(
            player.blockPosition().distSqr(campCenter)
        );
        guardSay(
            player,
            "Camp is " + distance + " blocks to the " + direction + "."
        );
    }

    /**
     * Get cardinal direction from one position to another.
     */
    private static String getCardinalDirection(BlockPos from, BlockPos to) {
        int dx = to.getX() - from.getX();
        int dz = to.getZ() - from.getZ();

        // In Minecraft: -Z = north, +Z = south, +X = east, -X = west
        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";
    }

    // ==================== DEATH / REMOVAL ====================

    /**
     * Shared cleanup: clear guard reference, trigger prisoner escape, notify.
     * Guarded by escapeTriggered to prevent double-fire from die() + remove().
     */
    private void performPrisonerCleanup(String reason) {
        if (escapeTriggered) return;
        if (this.level().isClientSide) return;
        if (!(this.level() instanceof ServerLevel level)) return;
        if (prisonerUUID == null) return;

        escapeTriggered = true;

        TiedUpMod.LOGGER.info(
            "[EntityLaborGuard] Guard {} - triggering escape for prisoner {}",
            reason,
            prisonerUUID.toString().substring(0, 8)
        );

        // Clear guard reference
        PrisonerManager manager = PrisonerManager.get(level);
        LaborRecord labor = manager.getLaborRecord(prisonerUUID);
        labor.setGuardId(null);

        // Trigger escape
        EscapeMonitorService.get().escape(level, prisonerUUID, reason);

        // Notify prisoner
        ServerPlayer prisoner = level
            .getServer()
            .getPlayerList()
            .getPlayer(prisonerUUID);
        if (prisoner != null) {
            prisoner.sendSystemMessage(
                Component.translatable(
                    "entity.tiedup.guard.eliminated_free"
                ).withStyle(ChatFormatting.GREEN, ChatFormatting.BOLD)
            );
        }
    }

    /**
     * When the guard dies, trigger IMMEDIATE escape for the prisoner.
     */
    @Override
    public void die(DamageSource source) {
        performPrisonerCleanup("guard eliminated");
        super.die(source);
    }

    /**
     * Handle non-death removal (e.g. /kill, chunk unload, dimension change).
     * DISCARDED is intentional (orphan self-cleanup) — already handled by tick check.
     */
    @Override
    public void remove(RemovalReason reason) {
        if (reason != RemovalReason.DISCARDED) {
            performPrisonerCleanup("guard removed (" + reason.name() + ")");
        }
        super.remove(reason);
    }

    /**
     * Trigger escape when the guard is incapacitated (tied up / captured).
     */
    private void triggerEscapeOnIncapacitated(ServerLevel level) {
        performPrisonerCleanup("guard captured");
        // Discard the guard after triggering escape
        this.discard();
    }

    // ==================== GETTERS/SETTERS ====================

    @Nullable
    public UUID getPrisonerUUID() {
        return prisonerUUID;
    }

    public void setPrisonerUUID(@Nullable UUID prisonerUUID) {
        this.prisonerUUID = prisonerUUID;
    }

    @Nullable
    public UUID getCampId() {
        return campId;
    }

    public void setCampId(@Nullable UUID campId) {
        this.campId = campId;
    }

    @Nullable
    public UUID getSpawnerMaidId() {
        return spawnerMaidId;
    }

    public void setSpawnerMaidId(@Nullable UUID spawnerMaidId) {
        this.spawnerMaidId = spawnerMaidId;
    }

    public boolean needsWhip() {
        return needsWhip;
    }

    public void setNeedsWhip(boolean needsWhip) {
        this.needsWhip = needsWhip;
    }

    /**
     * Get the prisoner player entity (server-side).
     *
     * @return The prisoner player, or null if offline or no prisoner assigned
     */
    @Nullable
    public ServerPlayer getPrisoner() {
        if (prisonerUUID == null) return null;
        if (!(this.level() instanceof ServerLevel level)) return null;
        return level.getServer().getPlayerList().getPlayer(prisonerUUID);
    }

    // ==================== NBT PERSISTENCE ====================

    @Override
    public void addAdditionalSaveData(CompoundTag tag) {
        super.addAdditionalSaveData(tag);

        if (prisonerUUID != null) {
            tag.putUUID("PrisonerUUID", prisonerUUID);
        }
        if (campId != null) {
            tag.putUUID("CampId", campId);
        }
        if (spawnerMaidId != null) {
            tag.putUUID("SpawnerMaidId", spawnerMaidId);
        }
    }

    @Override
    public void readAdditionalSaveData(CompoundTag tag) {
        super.readAdditionalSaveData(tag);

        if (tag.contains("PrisonerUUID")) {
            this.prisonerUUID = tag.getUUID("PrisonerUUID");
        }
        if (tag.contains("CampId")) {
            this.campId = tag.getUUID("CampId");
        }
        if (tag.contains("SpawnerMaidId")) {
            this.spawnerMaidId = tag.getUUID("SpawnerMaidId");
        }
    }

    // ==================== DESPAWN PROTECTION ====================

    @Override
    public boolean removeWhenFarAway(double distanceToClosestPlayer) {
        return false;
    }

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