package com.tiedup.remake.entities.damsel.components;

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.EntityDialogueManager;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.personality.*;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.*;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;

/**
 * Manages all personality-related systems for EntityDamsel:
 * - PersonalityState (personality type, training, commands, needs, relationships)
 * - Dialogue systems (idle, approach, environment, needs)
 * - Rest/fatigue modifiers
 * - Command execution
 *
 */
public class DamselPersonalitySystem {

    private final com.tiedup.remake.entities.EntityDamsel entity;
    private final IPersonalityTickContext context;

    // DIALOGUE SYSTEM CONSTANTS

    /** Minimum cooldown between idle dialogues (4 minutes). */
    private static final int IDLE_DIALOGUE_COOLDOWN_MIN = 4800;

    /** Maximum cooldown between idle dialogues (10 minutes). */
    private static final int IDLE_DIALOGUE_COOLDOWN_MAX = 12000;

    /** Cooldown per player for approach detection (2 minutes). */
    private static final int APPROACH_COOLDOWN = 2400;

    /** Cooldown for environmental dialogue (6 minutes). */
    private static final int ENVIRONMENT_DIALOGUE_COOLDOWN = 7200;

    /** Approach detection radius (blocks). */
    private static final double APPROACH_RADIUS = 5.0;

    // REST SYSTEM CONSTANTS

    /** UUID for rest-based speed modifier. */
    private static final UUID REST_SPEED_MODIFIER_UUID = UUID.fromString(
        "8f4c8c9e-0e5f-5d8b-9f60-2b3c4d5e6f7a"
    );

    /** UUID for rest-based damage modifier. */
    private static final UUID REST_DAMAGE_MODIFIER_UUID = UUID.fromString(
        "9a5d9d0f-1f6a-6e9c-0a70-3c4d5e6f7a8b"
    );

    /** Ticks between rest modifier updates (1 second). */
    private static final int REST_MODIFIER_UPDATE_INTERVAL = 20;

    // PERSONALITY STATE

    /**
     * Complete personality state including:
     * - Personality type (TIMID, FIERCE, etc.)
     * - Training level (WILD → DEVOTED)
     * - Secondary traits (TRAINED, DEVOTED, SUBJUGATED, etc.)
     * - Needs (hunger, comfort, rest, dignity)
     * - Relationships (per-player affinity, trust, fear, respect)
     * - Active command (FOLLOW, STAY, PATROL, etc.)
     *
     * Lazy-initialized on first access (server-side only).
     */
    @Nullable
    private PersonalityState personalityState;

    // DIALOGUE COOLDOWNS

    /** Cooldown for idle dialogue (in ticks). */
    private int idleDialogueCooldown = 0;

    /** Tracks cooldown for player approach reactions. */
    private final Map<UUID, Integer> approachCooldowns = new HashMap<>();

    /** Tracks which players were nearby last tick. */
    private final Set<UUID> playersNearbyLastTick = new HashSet<>();

    /** Throttle for approach detection - only scan every 10 ticks (0.5 sec). */
    private int approachCheckCooldown = 0;

    /** Cooldown for environment dialogue. */
    private int environmentDialogueCooldown = 0;

    /** Track last time each dialogue category was used */
    private final Map<
        EntityDialogueManager.DialogueCategory,
        Long
    > dialogueCooldowns = new HashMap<>();

    // REST SYSTEM MODIFIERS

    /** Last applied speed modifier from rest system */
    private float lastRestSpeedMod = 0.0f;

    /** Last applied damage modifier from rest system */
    private float lastRestDamageMod = 0.0f;

    // ANTI-FLEE SYSTEM

    /**
     * World time when last whipped.
     * Used to prevent fleeing for a short duration after discipline.
     */
    private long lastWhipTime = 0;

    // CONSTRUCTOR

    public DamselPersonalitySystem(
        com.tiedup.remake.entities.EntityDamsel entity,
        IPersonalityTickContext context
    ) {
        this.entity = entity;
        this.context = context;
    }

    // INITIALIZATION & SYNC

    /**
     * Initialize personality state with random generation.
     * Called on first spawn or lazy init.
     */
    public void initializePersonality() {
        // Generate random personality for Damsel
        this.personalityState = PersonalityState.generateForDamsel(
            entity.getUUID()
        );

        // Sync to client
        syncPersonalityData();

        TiedUpMod.LOGGER.debug(
            "[EntityDamsel] {} initialized with personality: {}",
            context.getAppearance().getNpcName(),
            this.personalityState.getPersonality().name()
        );
    }

    /**
     * Sync personality data to clients via EntityDataAccessors.
     * Called after personality changes.
     */
    public void syncPersonalityData() {
        if (this.personalityState != null) {
            entity
                .getEntityData()
                .set(
                    com.tiedup.remake.entities.EntityDamsel.DATA_PERSONALITY_TYPE,
                    this.personalityState.getPersonality().name()
                );
            entity
                .getEntityData()
                .set(
                    com.tiedup.remake.entities.EntityDamsel.DATA_ACTIVE_COMMAND,
                    this.personalityState.getActiveCommand().name()
                );
        }
    }

    // TICK LOGIC

    /**
     * Main personality tick - delegates to PersonalityState and handles side effects.
     * Call this from EntityDamsel.aiStep().
     */
    public void tickPersonality() {
        if (this.personalityState == null) return;

        // Get current master UUID from collar (if any)
        UUID masterUUID = null;
        if (context.hasCollar()) {
            ItemStack collar = context.getEquipment(BodyRegionV2.NECK);
            if (CollarHelper.isCollar(collar)) {
                List<UUID> owners = CollarHelper.getOwners(collar);
                if (!owners.isEmpty()) {
                    masterUUID = owners.get(0);
                }
            }
        }

        // Check if it's night (for rest decay)
        boolean isNight = !context.level().isDay();

        // Delegate tick to PersonalityState (handles needs, mood, relationships, leash effects)
        var transitions = this.personalityState.tick(
            entity,
            isNight,
            masterUUID,
            context.getBondageManager().isCaptive()
        );

        // Hunger is a player→NPC feature only; keep hunger full for unowned NPCs
        // (prevents camp kidnappers/traders/maids from starving and complaining)
        if (masterUUID == null) {
            this.personalityState.getNeeds().setHunger(NpcNeeds.MAX_VALUE);
        }

        // Trigger dialogues for need transitions (if not gagged)
        if (transitions.hasAny() && !context.getBondageManager().isGagged()) {
            triggerNeedDialogue(transitions);
        }

        // Update rest-based attribute modifiers (every 20 ticks)
        if (context.getTickCount() % 20 == 0) {
            updateRestModifiers();
        }
    }

    /**
     * Trigger dialogue when a need crosses a threshold.
     * Priority: critical states (starving, exhausted) > regular (hungry, tired, etc.)
     *
     * @param transitions The need transitions that occurred this tick
     */
    private void triggerNeedDialogue(NpcNeeds.NeedTransitions transitions) {
        // Find closest player to speak to
        Player nearestPlayer = context.level().getNearestPlayer(entity, 16);
        if (nearestPlayer == null) return;

        // Priority order: critical > regular
        String dialogueId = null;

        // Critical states first (use regular IDs for now, context makes them urgent)
        if (transitions.becameStarving) {
            dialogueId = "needs.starving";
        } else if (transitions.becameTired) {
            dialogueId = "needs.dignity_low";
        }
        // Regular states
        else if (transitions.becameHungry) {
            dialogueId = "needs.hungry";
        }

        if (dialogueId != null) {
            EntityDialogueManager.talkByDialogueId(
                entity,
                nearestPlayer,
                dialogueId
            );
        }
    }

    /**
     * Update attribute modifiers based on rest level.
     * Tired NPCs move slower and deal less damage.
     * Called every REST_MODIFIER_UPDATE_INTERVAL ticks.
     */
    private void updateRestModifiers() {
        if (this.personalityState == null) return;

        NpcNeeds needs = this.personalityState.getNeeds();
        float rest = needs.getRest();

        // Calculate modifiers based on rest level
        float speedMod = 0.0f;
        float damageMod = 0.0f;

        if (rest < 25) {
            // Exhausted
            speedMod = -0.3f; // -30% speed
            damageMod = -0.4f; // -40% damage
        } else if (rest < 50) {
            // Tired
            speedMod = -0.15f; // -15% speed
            damageMod = -0.2f; // -20% damage
        }

        // Only update if changed (avoid spamming attribute system)
        if (speedMod != lastRestSpeedMod) {
            AttributeInstance speedAttr = entity.getAttribute(
                Attributes.MOVEMENT_SPEED
            );
            if (speedAttr != null) {
                speedAttr.removeModifier(REST_SPEED_MODIFIER_UUID);
                if (speedMod != 0) {
                    speedAttr.addTransientModifier(
                        new AttributeModifier(
                            REST_SPEED_MODIFIER_UUID,
                            "Rest fatigue",
                            speedMod,
                            AttributeModifier.Operation.MULTIPLY_TOTAL
                        )
                    );
                }
            }
            lastRestSpeedMod = speedMod;
        }

        if (damageMod != lastRestDamageMod) {
            AttributeInstance damageAttr = entity.getAttribute(
                Attributes.ATTACK_DAMAGE
            );
            if (damageAttr != null) {
                damageAttr.removeModifier(REST_DAMAGE_MODIFIER_UUID);
                if (damageMod != 0) {
                    damageAttr.addTransientModifier(
                        new AttributeModifier(
                            REST_DAMAGE_MODIFIER_UUID,
                            "Rest fatigue",
                            damageMod,
                            AttributeModifier.Operation.MULTIPLY_TOTAL
                        )
                    );
                }
            }
            lastRestDamageMod = damageMod;
        }
    }

    // DIALOGUE SYSTEMS

    /**
     * Tick idle dialogue system (random chatter).
     * Call this from EntityDamsel.aiStep().
     */
    public void tickIdleDialogue() {
        // Decrement cooldown
        if (this.idleDialogueCooldown > 0) {
            this.idleDialogueCooldown--;
            return;
        }

        // Random chance to speak FIRST (~1 in 1000 per tick = once per ~50 seconds on average)
        // Check this before expensive getNearestPlayer() call - saves 99.9% of lookups
        if (entity.getRandom().nextFloat() > 0.001f) {
            return;
        }

        // Don't speak if gagged
        if (context.getBondageManager().isGagged()) {
            return;
        }

        // Don't speak if following a command (busy)
        if (
            this.personalityState != null &&
            this.personalityState.getActiveCommand() != NpcCommand.NONE
        ) {
            return;
        }

        // Find a player to talk to
        Player nearestPlayer = context.level().getNearestPlayer(entity, 16);
        if (nearestPlayer == null) {
            return;
        }

        // Select dialogue based on state
        String dialogueId = selectIdleDialogueId();
        if (dialogueId != null) {
            EntityDialogueManager.talkByDialogueId(
                entity,
                nearestPlayer,
                dialogueId
            );
            // Reset cooldown (4-10 minutes)
            this.idleDialogueCooldown =
                IDLE_DIALOGUE_COOLDOWN_MIN +
                entity
                    .getRandom()
                    .nextInt(
                        IDLE_DIALOGUE_COOLDOWN_MAX - IDLE_DIALOGUE_COOLDOWN_MIN
                    );
        }
    }

    /**
     * Select appropriate idle dialogue ID based on current state.
     * Priority: needs > mood > generic idle.
     *
     * @return Dialogue ID or null
     */
    @Nullable
    private String selectIdleDialogueId() {
        if (this.personalityState == null) {
            return "idle.free";
        }

        NpcNeeds needs = this.personalityState.getNeeds();

        // Priority 1: Critical needs
        if (needs.isStarving()) return "needs.starving";

        // Priority 2: Regular needs
        if (needs.isHungry()) return "needs.hungry";
        if (needs.isTired()) return "needs.dignity_low";

        // Priority 3: Mood-based (if very low)
        float mood = this.personalityState.getMood();
        if (mood < 10) return "mood.miserable";
        if (mood < 40) return "mood.sad";

        // Priority 4: State-based idle
        if (context.getBondageManager().isTiedUp()) {
            return "idle.captive";
        }

        return "idle.free";
    }

    /**
     * Detect when players approach and trigger reaction dialogue.
     * Uses per-player cooldowns to avoid spam.
     * Throttled to every 10 ticks (0.5 sec) to reduce entity search overhead.
     * Call this from EntityDamsel.aiStep().
     */
    public void tickApproachDetection() {
        // Throttle approach detection - only scan every 10 ticks (0.5 sec)
        if (--this.approachCheckCooldown > 0) {
            return;
        }
        this.approachCheckCooldown = 10;

        // Decrement all cooldowns
        approachCooldowns
            .entrySet()
            .removeIf(e -> {
                int newVal = e.getValue() - 10; // Decrement by 10 since we only check every 10 ticks
                if (newVal <= 0) return true;
                e.setValue(newVal);
                return false;
            });

        // Don't react if gagged
        if (context.getBondageManager().isGagged()) {
            playersNearbyLastTick.clear();
            return;
        }

        // Find nearby players
        Set<UUID> currentNearby = new HashSet<>();
        List<Player> nearbyPlayers = context
            .level()
            .getEntitiesOfClass(
                Player.class,
                entity.getBoundingBox().inflate(APPROACH_RADIUS),
                p -> p.isAlive() && entity.distanceTo(p) <= APPROACH_RADIUS
            );

        for (Player player : nearbyPlayers) {
            UUID uuid = player.getUUID();
            currentNearby.add(uuid);

            // Skip if player was already nearby last tick
            if (playersNearbyLastTick.contains(uuid)) {
                continue;
            }

            // Skip if on cooldown
            if (approachCooldowns.containsKey(uuid)) {
                continue;
            }

            // Player just approached - trigger reaction
            onPlayerApproach(player);
            approachCooldowns.put(uuid, APPROACH_COOLDOWN);
        }

        // Update tracking set
        playersNearbyLastTick.clear();
        playersNearbyLastTick.addAll(currentNearby);
    }

    /**
     * Called when a player approaches (enters APPROACH_RADIUS).
     * Triggers appropriate dialogue based on relationship.
     */
    private void onPlayerApproach(Player player) {
        if (this.personalityState == null) return;

        // Use DialogueTriggerSystem to select appropriate approach dialogue
        String dialogueId =
            com.tiedup.remake.dialogue.DialogueTriggerSystem.selectApproachDialogue(
                entity,
                player
            );

        if (dialogueId != null) {
            EntityDialogueManager.talkByDialogueId(entity, player, dialogueId);
        }
    }

    /**
     * Tick environment dialogue (comments on weather/time).
     * Call this from EntityDamsel.aiStep().
     */
    public void tickEnvironmentDialogue() {
        // Decrement cooldown
        if (this.environmentDialogueCooldown > 0) {
            this.environmentDialogueCooldown--;
            return;
        }

        // Random chance (~1 in 2000 per tick)
        if (entity.getRandom().nextFloat() > 0.0005f) {
            return;
        }

        // Don't speak if gagged
        if (context.getBondageManager().isGagged()) {
            return;
        }

        // Find a player to talk to
        Player nearestPlayer = context.level().getNearestPlayer(entity, 16);
        if (nearestPlayer == null) {
            return;
        }

        // Use DialogueTriggerSystem to select environment dialogue
        String dialogueId =
            com.tiedup.remake.dialogue.DialogueTriggerSystem.selectEnvironmentDialogue(
                entity
            );

        if (dialogueId != null) {
            EntityDialogueManager.talkByDialogueId(
                entity,
                nearestPlayer,
                dialogueId
            );
            this.environmentDialogueCooldown = ENVIRONMENT_DIALOGUE_COOLDOWN;
        }
    }

    // PUBLIC PERSONALITY API

    /**
     * Get the personality state (lazy-init on server side).
     * @return PersonalityState instance, or null on client
     */
    @Nullable
    public PersonalityState getPersonalityState() {
        // Lazy init on server side
        if (this.personalityState == null && !context.level().isClientSide) {
            initializePersonality();
        }
        return this.personalityState;
    }

    /**
     * Get the personality type (client-safe via synced data).
     * @return PersonalityType enum value
     */
    public PersonalityType getPersonalityType() {
        String typeName = entity
            .getEntityData()
            .get(com.tiedup.remake.entities.EntityDamsel.DATA_PERSONALITY_TYPE);
        try {
            return PersonalityType.valueOf(typeName);
        } catch (IllegalArgumentException e) {
            return PersonalityType.CALM; // Default fallback
        }
    }

    /**
     * Set the personality type (debug/testing only).
     * Creates a new PersonalityState with the given type, preserving training XP.
     *
     * @param newType The new personality type
     */
    public void setPersonalityType(PersonalityType newType) {
        if (context.level().isClientSide) return;

        // Create new state with new personality
        this.personalityState = new PersonalityState(newType);

        // Sync to clients
        syncPersonalityData();

        TiedUpMod.LOGGER.info(
            "[EntityDamsel] {} personality set to: {}",
            context.getAppearance().getNpcName(),
            newType.name()
        );
    }

    /**
     * Get the active command (client-safe via synced data).
     * @return NpcCommand enum value
     */
    public NpcCommand getActiveCommand() {
        String cmdName = entity
            .getEntityData()
            .get(com.tiedup.remake.entities.EntityDamsel.DATA_ACTIVE_COMMAND);
        return NpcCommand.fromString(cmdName);
    }

    /**
     * Check if recently whipped.
     * @return true if whipped within last 1200 ticks (1 minute)
     */
    public boolean wasRecentlyWhipped() {
        return (context.level().getGameTime() - lastWhipTime) < 1200;
    }

    /**
     * Get the world time when this NPC was last whipped.
     * @return World time of last whip, or 0 if never whipped
     */
    public long getLastWhipTime() {
        return this.lastWhipTime;
    }

    /**
     * Set the world time when this NPC was whipped.
     * @param time Current world game time
     */
    public void setLastWhipTime(long time) {
        this.lastWhipTime = time;
    }

    // NBT PERSISTENCE

    /**
     * Save personality data to NBT.
     * @param tag The compound tag to save to
     */
    public void saveToTag(CompoundTag tag) {
        if (this.personalityState != null) {
            tag.put("Personality", this.personalityState.save());
        }
    }

    /**
     * Load personality data from NBT.
     * @param tag The compound tag to load from
     */
    public void loadFromTag(CompoundTag tag) {
        if (tag.contains("Personality")) {
            this.personalityState = PersonalityState.load(
                tag.getCompound("Personality")
            );
            // CRITICAL: Must sync to client immediately after load
            syncPersonalityData();
        }
    }
}
