package com.tiedup.remake.personality;

import com.tiedup.remake.cells.CellDataV2;
import com.tiedup.remake.cells.CellRegistryV2;
import com.tiedup.remake.entities.EntityDamsel;
import java.util.List;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import org.jetbrains.annotations.Nullable;

/**
 * Main personality state manager for an NPC.
 * Contains personality type, needs, mood, command state, and job experience.
 *
 * NPCs always obey commands when captured — no refusal, no relationship checks.
 */
public class PersonalityState {

    // --- Core personality (immutable after generation) ---
    private final PersonalityType personality;

    // --- Needs ---
    private final NpcNeeds needs = new NpcNeeds();

    // --- Current command state ---
    private NpcCommand activeCommand = NpcCommand.NONE;

    /** Current follow distance (for FOLLOW command) */
    private NpcCommand.FollowDistance followDistance =
        NpcCommand.FollowDistance.FAR;

    @Nullable
    private UUID commandingPlayer = null;

    @Nullable
    private BlockPos commandTarget = null;

    /** Secondary command target (for TRANSFER: destination chest) */
    @Nullable
    private BlockPos commandTarget2 = null;

    private int commandProgress = 0;

    // --- Struggle state ---
    private int struggleTimer = 0;
    private int consecutiveStruggleFails = 0;

    // --- Mood (calculated from needs) ---
    private float mood = 50.0f; // 0-100, 50 is neutral

    // --- Name state ---
    private boolean hasBeenNamed = false;

    // --- Home State ---
    @Nullable
    private BlockPos homePos = null;

    private HomeType homeType = HomeType.NONE;

    @Nullable
    private UUID cellId = null;

    // --- Cell Quality ---
    private CellQuality cellQuality = CellQuality.STANDARD;

    // --- Cell Navigation ---
    @Nullable
    private BlockPos cellDeliveryPoint = null;

    // --- Auto-Rest Setting ---
    /** Whether the NPC will automatically go rest when tired */
    private boolean autoRestEnabled = true;

    // --- Job Experience ---
    /** Persistent job experience tracking */
    private JobExperience jobExperience = new JobExperience();

    // --- Constructor ---

    public PersonalityState(PersonalityType personality) {
        this.personality = personality;
        resetStruggleTimer();
    }

    // --- Factory methods ---

    /**
     * Generate a random personality state for a Damsel.
     *
     * @param entityUUID Entity UUID for seeding
     * @return New PersonalityState
     */
    public static PersonalityState generateForDamsel(UUID entityUUID) {
        PersonalityType type = PersonalityType.randomForDamsel();
        return new PersonalityState(type);
    }

    /**
     * Generate a random personality state for a Kidnapper.
     *
     * @param entityUUID Entity UUID for seeding
     * @return New PersonalityState
     */
    public static PersonalityState generateForKidnapper(UUID entityUUID) {
        PersonalityType type = PersonalityType.randomForKidnapper();
        return new PersonalityState(type);
    }

    // --- Getters ---

    public PersonalityType getPersonality() {
        return personality;
    }

    public NpcNeeds getNeeds() {
        return needs;
    }

    public NpcCommand getActiveCommand() {
        return activeCommand;
    }

    public NpcCommand.FollowDistance getFollowDistance() {
        return followDistance;
    }

    public void setFollowDistance(NpcCommand.FollowDistance distance) {
        this.followDistance = distance;
    }

    @Nullable
    public UUID getCommandingPlayer() {
        return commandingPlayer;
    }

    @Nullable
    public BlockPos getCommandTarget() {
        return commandTarget;
    }

    /**
     * Get secondary command target (for TRANSFER: destination chest B).
     */
    @Nullable
    public BlockPos getCommandTarget2() {
        return commandTarget2;
    }

    /**
     * Set secondary command target.
     */
    public void setCommandTarget2(@Nullable BlockPos pos) {
        this.commandTarget2 = pos;
    }

    public int getCommandProgress() {
        return commandProgress;
    }

    public float getMood() {
        return mood;
    }

    public boolean hasBeenNamed() {
        return hasBeenNamed;
    }

    /**
     * Modify mood directly.
     *
     * @param amount Amount to add (can be negative)
     */
    public void modifyMood(float amount) {
        this.mood = Math.max(0, Math.min(100, this.mood + amount));
    }

    // --- Home Management ---

    @Nullable
    public BlockPos getHomePos() {
        return homePos;
    }

    public HomeType getHomeType() {
        return homeType;
    }

    @Nullable
    public BlockPos getCellDeliveryPoint() {
        return cellDeliveryPoint;
    }

    @Nullable
    public UUID getCellId() {
        return cellId;
    }

    public boolean hasHome() {
        return homePos != null && homeType != HomeType.NONE;
    }

    public void clearHome() {
        this.homePos = null;
        this.homeType = HomeType.NONE;
        this.cellId = null;
        this.cellDeliveryPoint = null;
    }

    /**
     * Assign a cell to this NPC. Derives homePos/homeType from cell content.
     * Uses NPC's index in the prisoner list to distribute beds among multiple NPCs.
     */
    public void assignCell(UUID cellId, CellDataV2 cell, UUID npcId) {
        this.cellId = cellId;
        deriveHomeFromCell(cell, npcId);
    }

    /**
     * Derive homePos and homeType from cell content.
     * Priority: PetBed > Bed > SpawnPoint.
     */
    private void deriveHomeFromCell(CellDataV2 cell, @Nullable UUID npcId) {
        int npcIndex = 0;
        if (npcId != null) {
            List<UUID> prisoners = cell.getPrisonerIds();
            int idx = prisoners.indexOf(npcId);
            if (idx >= 0) npcIndex = idx;
        }

        if (!cell.getPetBeds().isEmpty()) {
            int bedIdx = npcIndex % cell.getPetBeds().size();
            this.homePos = cell.getPetBeds().get(bedIdx);
            this.homeType = HomeType.PET_BED;
        } else if (!cell.getBeds().isEmpty()) {
            int bedIdx = npcIndex % cell.getBeds().size();
            this.homePos = cell.getBeds().get(bedIdx);
            this.homeType = HomeType.BED;
        } else {
            this.homePos = cell.getSpawnPoint();
            this.homeType = HomeType.CELL;
        }
        this.cellDeliveryPoint =
            cell.getDeliveryPoint() != null
                ? cell.getDeliveryPoint()
                : cell.getSpawnPoint();
    }

    public void unassignCell() {
        clearHome();
    }

    public boolean isNearHome(BlockPos currentPos, int radius) {
        if (homePos == null) return false;
        return homePos.closerThan(currentPos, radius);
    }

    /**
     * Derive the rest type based on home type and proximity.
     */
    public NpcNeeds.RestType deriveRestType(EntityDamsel entity) {
        if (!isNearHome(entity.blockPosition(), 3)) {
            return NpcNeeds.RestType.IDLE;
        }
        return switch (homeType) {
            case BED -> NpcNeeds.RestType.SLEEPING;
            case PET_BED -> NpcNeeds.RestType.SITTING;
            default -> NpcNeeds.RestType.IDLE;
        };
    }

    /**
     * Called when home block is destroyed.
     */
    public void onHomeDestroyed() {
        if (homePos != null) {
            modifyMood(-5);
            clearHome();
        }
    }

    // --- Cell Quality ---

    public CellQuality getCellQuality() {
        return cellQuality;
    }

    // --- Auto-Rest Setting ---

    public boolean isAutoRestEnabled() {
        return autoRestEnabled;
    }

    public void setAutoRestEnabled(boolean enabled) {
        this.autoRestEnabled = enabled;
    }

    public boolean toggleAutoRest() {
        this.autoRestEnabled = !this.autoRestEnabled;
        return this.autoRestEnabled;
    }

    // --- Job Experience ---

    public JobExperience getJobExperience() {
        return jobExperience;
    }

    // --- Discipline System ---

    /**
     * Apply discipline to the NPC. Only affects mood.
     *
     * @param type Discipline type
     * @param worldTime Current world time (unused, kept for API compat)
     * @return false (no brutality tracking)
     */
    public boolean applyDiscipline(DisciplineType type, long worldTime) {
        // MASOCHIST: Inverted mood for punishment
        float moodMult = 1.0f;
        if (personality == PersonalityType.MASOCHIST && type.isPunishment()) {
            moodMult = -1.0f;
        }

        modifyMood(type.moodChange * moodMult);
        return false;
    }

    // --- Command System ---

    /**
     * Check if the NPC will obey a command from a player.
     * Always returns true — NPCs obey when captured.
     */
    public boolean willObeyCommand(
        net.minecraft.world.entity.player.Player commander,
        NpcCommand command
    ) {
        return true;
    }

    /**
     * Set the active command.
     */
    public void setActiveCommand(
        NpcCommand command,
        UUID playerUUID,
        @Nullable BlockPos target
    ) {
        this.activeCommand = command;
        this.commandingPlayer = playerUUID;
        this.commandTarget = target;
        this.commandProgress = 0;
    }

    /**
     * Clear the active command.
     */
    public void clearCommand() {
        this.activeCommand = NpcCommand.NONE;
        this.commandingPlayer = null;
        this.commandTarget = null;
        this.commandTarget2 = null;
        this.commandProgress = 0;
    }

    /**
     * Increment command progress.
     */
    public void addCommandProgress(int amount) {
        this.commandProgress += amount;
    }

    // --- Struggle System ---

    public void resetStruggleTimer() {
        resetStruggleTimer(6000);
    }

    public void resetStruggleTimer(int baseInterval) {
        float personalityMod = personality.getStruggleTimerMultiplier();
        float randomFactor = 0.8f + (float) Math.random() * 0.4f;
        this.struggleTimer = Math.round(
            baseInterval * personalityMod * randomFactor
        );
    }

    public int getStruggleTimer() {
        return struggleTimer;
    }

    public boolean tickStruggleTimer() {
        if (struggleTimer > 0) {
            struggleTimer--;
            return false;
        }
        return true;
    }

    /**
     * Calculate struggle success chance.
     *
     * @param bindResistance Resistance of current bind item
     * @param captorNearby Whether the captor is nearby
     * @param allyNearby Whether an ally NPC is nearby
     * @return Success chance (0.0 to 1.0)
     */
    public float calculateStruggleChance(
        float bindResistance,
        boolean captorNearby,
        boolean allyNearby
    ) {
        float baseChance = 0.15f;

        // Personality modifier
        baseChance *= personality.struggleModifier;

        // Bind resistance
        baseChance *= (1.0f / Math.max(1.0f, bindResistance));

        // Mood affects struggle (miserable = more likely)
        if (mood < 30) baseChance *= 1.5f;
        else if (mood > 70) baseChance *= 0.7f;

        // Environmental factors
        if (captorNearby) baseChance *= 0.7f;
        if (allyNearby) baseChance *= 1.3f;

        return Math.min(0.8f, Math.max(0.01f, baseChance));
    }

    /**
     * Record struggle result.
     */
    public void recordStruggleResult(boolean success, int baseInterval) {
        if (success) {
            consecutiveStruggleFails = 0;
            modifyMood(5);
        } else {
            consecutiveStruggleFails++;
            modifyMood(-2);
        }
        resetStruggleTimer(baseInterval);
    }

    // --- Mood ---

    /**
     * Get mood modifier for command compliance (kept for dialogue variety).
     *
     * @return Modifier (0.7 to 1.3)
     */
    public float getMoodModifier() {
        if (mood < 20) return 0.7f;
        if (mood < 40) return 0.85f;
        if (mood > 80) return 1.2f;
        if (mood > 60) return 1.1f;
        return 1.0f;
    }

    /**
     * Recalculate mood based on needs and cell quality.
     */
    public void recalculateMood() {
        float newMood = 50.0f;

        // Needs impact
        newMood += needs.getMoodImpact();

        // Job personality preference impact
        if (activeCommand != null && activeCommand.isActiveJob()) {
            float jobMoodMod = JobPersonalityModifiers.getJobMoodModifier(
                personality,
                activeCommand
            );
            newMood += jobMoodMod * 10;
        }

        // Cell quality impact
        newMood += cellQuality.moodModifier;

        // Clamp
        this.mood = Math.max(0, Math.min(100, newMood));
    }

    // --- Naming ---

    public void markAsNamed() {
        this.hasBeenNamed = true;
    }

    // --- Tick ---

    /**
     * Tick the personality state. Called every game tick.
     *
     * @param entity The entity
     * @param isNight Whether it's night (unused, kept for API compat)
     * @param masterUUID Current collar owner UUID (unused, kept for API compat)
     * @param isLeashed Whether the entity is currently leashed (unused)
     * @return NeedTransitions containing any threshold crossings this tick
     */
    public NpcNeeds.NeedTransitions tick(
        EntityDamsel entity,
        boolean isNight,
        @Nullable UUID masterUUID,
        boolean isLeashed
    ) {
        // Determine if NPC is doing active work (jobs that consume rest)
        boolean isWorking =
            activeCommand != null && activeCommand.isActiveJob();

        // Derive rest type based on home proximity and type
        NpcNeeds.RestType restType = deriveRestType(entity);

        // Update needs and get transitions
        NpcNeeds.NeedTransitions transitions = needs.tick(
            entity,
            personality,
            isWorking,
            restType
        );

        // Cell quality evaluation (every 60 seconds = 1200 ticks)
        if (entity.tickCount % 1200 == 0 && hasHome()) {
            // Validate cell link
            CellDataV2 cell = null;
            if (
                cellId != null &&
                entity.level() instanceof ServerLevel serverLevel
            ) {
                CellRegistryV2 registry = CellRegistryV2.get(serverLevel);
                cell = registry.getCell(cellId);
                if (cell == null) {
                    // Cell destroyed
                    onHomeDestroyed();
                    return transitions;
                }
                // Re-derive homePos in case cell content changed
                deriveHomeFromCell(cell, entity.getUUID());
            }

            cellQuality = CellQuality.evaluate(homePos, entity.level(), cell);
        }

        // Recalculate mood every 100 ticks (5 seconds)
        if (entity.tickCount % 100 == 0) {
            recalculateMood();
        }

        return transitions;
    }

    // --- NBT Persistence ---

    public CompoundTag save() {
        CompoundTag tag = new CompoundTag();

        // Core
        tag.putString("Personality", personality.name());

        // Needs
        tag.put("Needs", needs.save());

        // Command state
        tag.putString("ActiveCommand", activeCommand.name());
        tag.putString("FollowDistance", followDistance.name());
        if (commandingPlayer != null) {
            tag.putUUID("CommandingPlayer", commandingPlayer);
        }
        if (commandTarget != null) {
            tag.putLong("CommandTarget", commandTarget.asLong());
        }
        if (commandTarget2 != null) {
            tag.putLong("CommandTarget2", commandTarget2.asLong());
        }
        tag.putInt("CommandProgress", commandProgress);

        // Struggle state
        tag.putInt("StruggleTimer", struggleTimer);
        tag.putInt("ConsecutiveFails", consecutiveStruggleFails);

        // Other
        tag.putFloat("Mood", mood);
        tag.putBoolean("HasBeenNamed", hasBeenNamed);

        // Home state
        if (homePos != null) {
            tag.putLong("HomePos", homePos.asLong());
            tag.putString("HomeType", homeType.name());
        }
        if (cellId != null) {
            tag.putUUID("HomeCellId", cellId);
        }
        if (cellDeliveryPoint != null) {
            tag.putLong("CellDeliveryPoint", cellDeliveryPoint.asLong());
        }

        // Cell quality
        tag.putString("CellQuality", cellQuality.name());

        // Auto-rest setting
        tag.putBoolean("AutoRestEnabled", autoRestEnabled);

        // Job experience
        tag.put("JobExperience", jobExperience.save());

        return tag;
    }

    public static PersonalityState load(CompoundTag tag) {
        // Core
        PersonalityType type;
        try {
            type = PersonalityType.valueOf(tag.getString("Personality"));
        } catch (IllegalArgumentException e) {
            type = PersonalityType.CALM;
        }

        PersonalityState state = new PersonalityState(type);

        // Needs
        if (tag.contains("Needs")) {
            NpcNeeds loadedNeeds = NpcNeeds.load(tag.getCompound("Needs"));
            state.needs.setHunger(loadedNeeds.getHunger());
            state.needs.setRest(loadedNeeds.getRest());
        }

        // Command state (gracefully handles removed commands)
        try {
            state.activeCommand = NpcCommand.valueOf(
                tag.getString("ActiveCommand")
            );
        } catch (IllegalArgumentException e) {
            state.activeCommand = NpcCommand.NONE;
        }
        try {
            state.followDistance = NpcCommand.FollowDistance.valueOf(
                tag.getString("FollowDistance")
            );
        } catch (IllegalArgumentException e) {
            state.followDistance = NpcCommand.FollowDistance.FAR;
        }
        if (tag.contains("CommandingPlayer")) {
            state.commandingPlayer = tag.getUUID("CommandingPlayer");
        }
        if (tag.contains("CommandTarget")) {
            state.commandTarget = BlockPos.of(tag.getLong("CommandTarget"));
        }
        if (tag.contains("CommandTarget2")) {
            state.commandTarget2 = BlockPos.of(tag.getLong("CommandTarget2"));
        }
        state.commandProgress = tag.getInt("CommandProgress");

        // Struggle state
        state.struggleTimer = tag.getInt("StruggleTimer");
        state.consecutiveStruggleFails = tag.getInt("ConsecutiveFails");

        // Other
        state.mood = tag.getFloat("Mood");
        state.hasBeenNamed = tag.getBoolean("HasBeenNamed");

        // Home state
        if (tag.contains("HomePos")) {
            state.homePos = BlockPos.of(tag.getLong("HomePos"));
            try {
                String homeTypeName = tag.getString("HomeType");
                if ("BASKET".equals(homeTypeName)) homeTypeName = "PET_BED"; // migration
                state.homeType = HomeType.valueOf(homeTypeName);
            } catch (IllegalArgumentException e) {
                state.homeType = HomeType.NONE;
            }
        }
        if (tag.hasUUID("HomeCellId")) {
            state.cellId = tag.getUUID("HomeCellId");
        }
        if (tag.contains("CellDeliveryPoint")) {
            state.cellDeliveryPoint = BlockPos.of(
                tag.getLong("CellDeliveryPoint")
            );
        }

        // Cell quality
        if (tag.contains("CellQuality")) {
            try {
                state.cellQuality = CellQuality.valueOf(
                    tag.getString("CellQuality")
                );
            } catch (IllegalArgumentException e) {
                state.cellQuality = CellQuality.STANDARD;
            }
        }

        // Auto-rest setting (default true if not present)
        if (tag.contains("AutoRestEnabled")) {
            state.autoRestEnabled = tag.getBoolean("AutoRestEnabled");
        }

        // Job experience
        if (tag.contains("JobExperience")) {
            state.jobExperience = JobExperience.load(
                tag.getCompound("JobExperience")
            );
        }

        return state;
    }
}
