package com.tiedup.remake.cells;

import com.tiedup.remake.core.TiedUpMod;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.saveddata.SavedData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * Global registry for camp ownership linking camps to their SlaveTrader.
 *
 * This registry tracks:
 * - Camp UUID -> CampData (trader, maid, alive status)
 * - When a trader dies, the camp becomes inactive
 *
 * Persists across server restarts using Minecraft's SavedData system.
 */
public class CampOwnership extends SavedData {

    private static final String DATA_NAME = "tiedup_camp_ownership";

    // Camp UUID -> CampData
    private final Map<UUID, CampData> camps = new ConcurrentHashMap<>();

    // Prisoners that have been processed (to avoid re-processing)
    private final Set<UUID> processedPrisoners = ConcurrentHashMap.newKeySet();

    // ==================== CAMP DATA CLASS ====================

    /**
     * Data structure representing a camp and its owner.
     * Uses thread-safe collections for concurrent access.
     */
    /** Maid respawn delay in ticks (5 minutes = 6000 ticks) */
    public static final long MAID_RESPAWN_DELAY = 6000;

    public static class CampData {

        private final UUID campId;
        private volatile UUID traderUUID;
        private volatile UUID maidUUID;
        private volatile boolean isAlive = true;
        private volatile BlockPos center;
        private final Set<UUID> linkedKidnapperUUIDs =
            ConcurrentHashMap.newKeySet();

        /** Time when maid died (for respawn timer), -1 if alive */
        private volatile long maidDeathTime = -1;

        /** Cached positions of LOOT chests (below LOOT markers) */
        private final List<BlockPos> lootChestPositions = new ArrayList<>();

        public CampData(UUID campId) {
            this.campId = campId;
        }

        public CampData(
            UUID campId,
            UUID traderUUID,
            @Nullable UUID maidUUID,
            BlockPos center
        ) {
            this.campId = campId;
            this.traderUUID = traderUUID;
            this.maidUUID = maidUUID;
            this.center = center;
            this.isAlive = true;
        }

        // Getters
        public UUID getCampId() {
            return campId;
        }

        public UUID getTraderUUID() {
            return traderUUID;
        }

        public UUID getMaidUUID() {
            return maidUUID;
        }

        public boolean isAlive() {
            return isAlive;
        }

        public BlockPos getCenter() {
            return center;
        }

        // Setters
        public void setTraderUUID(UUID traderUUID) {
            this.traderUUID = traderUUID;
        }

        public void setMaidUUID(UUID maidUUID) {
            this.maidUUID = maidUUID;
        }

        public void setAlive(boolean alive) {
            this.isAlive = alive;
        }

        public void setCenter(BlockPos center) {
            this.center = center;
        }

        // Maid death/respawn
        public long getMaidDeathTime() {
            return maidDeathTime;
        }

        public void setMaidDeathTime(long time) {
            this.maidDeathTime = time;
        }

        public boolean isMaidDead() {
            return maidDeathTime >= 0;
        }

        public boolean canRespawnMaid(long currentTime) {
            return (
                isMaidDead() &&
                (currentTime - maidDeathTime) >= MAID_RESPAWN_DELAY
            );
        }

        // Loot chest management
        public List<BlockPos> getLootChestPositions() {
            return lootChestPositions;
        }

        public void addLootChestPosition(BlockPos pos) {
            if (!lootChestPositions.contains(pos)) {
                lootChestPositions.add(pos);
            }
        }

        // Kidnapper management
        public void addKidnapper(UUID kidnapperUUID) {
            linkedKidnapperUUIDs.add(kidnapperUUID);
        }

        public void removeKidnapper(UUID kidnapperUUID) {
            linkedKidnapperUUIDs.remove(kidnapperUUID);
        }

        public Set<UUID> getLinkedKidnappers() {
            return Collections.unmodifiableSet(linkedKidnapperUUIDs);
        }

        public boolean hasKidnapper(UUID kidnapperUUID) {
            return linkedKidnapperUUIDs.contains(kidnapperUUID);
        }

        public int getKidnapperCount() {
            return linkedKidnapperUUIDs.size();
        }

        // NBT Serialization
        public CompoundTag save() {
            CompoundTag tag = new CompoundTag();
            tag.putUUID("campId", campId);
            if (traderUUID != null) tag.putUUID("traderUUID", traderUUID);
            if (maidUUID != null) tag.putUUID("maidUUID", maidUUID);
            tag.putBoolean("isAlive", isAlive);
            if (center != null) {
                tag.putInt("centerX", center.getX());
                tag.putInt("centerY", center.getY());
                tag.putInt("centerZ", center.getZ());
            }
            // Save linked kidnappers
            if (!linkedKidnapperUUIDs.isEmpty()) {
                ListTag kidnapperList = new ListTag();
                for (UUID uuid : linkedKidnapperUUIDs) {
                    CompoundTag uuidTag = new CompoundTag();
                    uuidTag.putUUID("uuid", uuid);
                    kidnapperList.add(uuidTag);
                }
                tag.put("linkedKidnappers", kidnapperList);
            }
            // Save maid death time
            tag.putLong("maidDeathTime", maidDeathTime);
            // Save loot chest positions
            if (!lootChestPositions.isEmpty()) {
                ListTag lootList = new ListTag();
                for (BlockPos pos : lootChestPositions) {
                    CompoundTag posTag = new CompoundTag();
                    posTag.putInt("x", pos.getX());
                    posTag.putInt("y", pos.getY());
                    posTag.putInt("z", pos.getZ());
                    lootList.add(posTag);
                }
                tag.put("lootChestPositions", lootList);
            }
            return tag;
        }

        public static CampData load(CompoundTag tag) {
            UUID campId = tag.getUUID("campId");
            CampData data = new CampData(campId);
            if (tag.contains("traderUUID")) data.traderUUID = tag.getUUID(
                "traderUUID"
            );
            if (tag.contains("maidUUID")) data.maidUUID = tag.getUUID(
                "maidUUID"
            );
            data.isAlive = tag.getBoolean("isAlive");
            if (tag.contains("centerX")) {
                data.center = new BlockPos(
                    tag.getInt("centerX"),
                    tag.getInt("centerY"),
                    tag.getInt("centerZ")
                );
            }
            // Load linked kidnappers
            if (tag.contains("linkedKidnappers")) {
                ListTag kidnapperList = tag.getList(
                    "linkedKidnappers",
                    Tag.TAG_COMPOUND
                );
                for (int i = 0; i < kidnapperList.size(); i++) {
                    CompoundTag uuidTag = kidnapperList.getCompound(i);
                    if (uuidTag.contains("uuid")) {
                        data.linkedKidnapperUUIDs.add(uuidTag.getUUID("uuid"));
                    }
                }
            }
            // Load maid death time
            if (tag.contains("maidDeathTime")) {
                data.maidDeathTime = tag.getLong("maidDeathTime");
            }
            // Load loot chest positions
            if (tag.contains("lootChestPositions")) {
                ListTag lootList = tag.getList(
                    "lootChestPositions",
                    Tag.TAG_COMPOUND
                );
                for (int i = 0; i < lootList.size(); i++) {
                    CompoundTag posTag = lootList.getCompound(i);
                    data.lootChestPositions.add(
                        new BlockPos(
                            posTag.getInt("x"),
                            posTag.getInt("y"),
                            posTag.getInt("z")
                        )
                    );
                }
            }
            return data;
        }
    }

    // ==================== STATIC ACCESS ====================

    /**
     * Get the CampOwnership registry for a server level.
     */
    public static CampOwnership get(ServerLevel level) {
        return level
            .getDataStorage()
            .computeIfAbsent(
                CampOwnership::load,
                CampOwnership::new,
                DATA_NAME
            );
    }

    /**
     * Get the CampOwnership from a MinecraftServer.
     */
    public static CampOwnership get(MinecraftServer server) {
        ServerLevel overworld = server.overworld();
        return get(overworld);
    }

    // ==================== CAMP MANAGEMENT ====================

    /**
     * Register a new camp with its trader and maid.
     *
     * @param campId      The camp/structure UUID
     * @param traderUUID  The SlaveTrader entity UUID
     * @param maidUUID    The Maid entity UUID (can be null)
     * @param center      The center position of the camp
     */
    public void registerCamp(
        UUID campId,
        UUID traderUUID,
        @Nullable UUID maidUUID,
        BlockPos center
    ) {
        CampData data = new CampData(campId, traderUUID, maidUUID, center);
        camps.put(campId, data);
        setDirty();
    }

    /**
     * Check if a camp is alive (has living trader).
     *
     * @param campId The camp UUID
     * @return true if camp exists and is alive
     */
    public boolean isCampAlive(UUID campId) {
        CampData data = camps.get(campId);
        return data != null && data.isAlive();
    }

    /**
     * Get camp data by camp UUID.
     */
    @Nullable
    public CampData getCamp(UUID campId) {
        return camps.get(campId);
    }

    /**
     * Get camp data by trader UUID.
     */
    @Nullable
    public CampData getCampByTrader(UUID traderUUID) {
        for (CampData camp : camps.values()) {
            if (traderUUID.equals(camp.getTraderUUID())) {
                return camp;
            }
        }
        return null;
    }

    /**
     * Get camp data by maid UUID.
     */
    @Nullable
    public CampData getCampByMaid(UUID maidUUID) {
        for (CampData camp : camps.values()) {
            if (maidUUID.equals(camp.getMaidUUID())) {
                return camp;
            }
        }
        return null;
    }

    /**
     * Find camps near a position.
     *
     * @param center The center position
     * @param radius The search radius
     * @return List of camps within radius
     */
    public List<CampData> findCampsNear(BlockPos center, double radius) {
        List<CampData> nearby = new ArrayList<>();
        double radiusSq = radius * radius;

        for (CampData camp : camps.values()) {
            if (
                camp.getCenter() != null &&
                camp.getCenter().distSqr(center) <= radiusSq
            ) {
                nearby.add(camp);
            }
        }
        return nearby;
    }

    /**
     * Find the nearest alive camp to a position.
     *
     * @param pos    The position to search from
     * @param radius Maximum search radius
     * @return The nearest alive camp, or null
     */
    @Nullable
    public CampData findNearestAliveCamp(BlockPos pos, double radius) {
        CampData nearest = null;
        double nearestDistSq = radius * radius;

        for (CampData camp : camps.values()) {
            if (!camp.isAlive() || camp.getCenter() == null) continue;

            double distSq = camp.getCenter().distSqr(pos);
            if (distSq < nearestDistSq) {
                nearestDistSq = distSq;
                nearest = camp;
            }
        }
        return nearest;
    }

    /**
     * Remove a camp from the registry.
     */
    @Nullable
    public CampData removeCamp(UUID campId) {
        CampData removed = camps.remove(campId);
        if (removed != null) {
            setDirty();
        }
        return removed;
    }

    /**
     * Get all registered camps.
     */
    public Collection<CampData> getAllCamps() {
        return Collections.unmodifiableCollection(camps.values());
    }

    /**
     * Get all alive camps.
     */
    public List<CampData> getAliveCamps() {
        List<CampData> alive = new ArrayList<>();
        for (CampData camp : camps.values()) {
            if (camp.isAlive()) {
                alive.add(camp);
            }
        }
        return alive;
    }

    /**
     * Link a kidnapper to a camp.
     *
     * @param campId       The camp UUID
     * @param kidnapperUUID The kidnapper UUID
     */
    public void linkKidnapperToCamp(UUID campId, UUID kidnapperUUID) {
        CampData data = camps.get(campId);
        if (data != null) {
            data.addKidnapper(kidnapperUUID);
            setDirty();
        }
    }

    /**
     * Unlink a kidnapper from a camp.
     *
     * @param campId       The camp UUID
     * @param kidnapperUUID The kidnapper UUID
     */
    public void unlinkKidnapperFromCamp(UUID campId, UUID kidnapperUUID) {
        CampData data = camps.get(campId);
        if (data != null) {
            data.removeKidnapper(kidnapperUUID);
            setDirty();
        }
    }

    /**
     * Check if a kidnapper is linked to a camp.
     *
     * @param campId       The camp UUID
     * @param kidnapperUUID The kidnapper UUID
     * @return true if the kidnapper is linked to this camp
     */
    public boolean isKidnapperLinked(UUID campId, UUID kidnapperUUID) {
        CampData data = camps.get(campId);
        return data != null && data.hasKidnapper(kidnapperUUID);
    }

    /**
     * Find the camp a kidnapper is linked to.
     *
     * @param kidnapperUUID The kidnapper UUID
     * @return The camp data, or null if not linked
     */
    @Nullable
    public CampData findCampByKidnapper(UUID kidnapperUUID) {
        for (CampData camp : camps.values()) {
            if (camp.hasKidnapper(kidnapperUUID)) {
                return camp;
            }
        }
        return null;
    }

    /**
     * Mark a prisoner as processed (to avoid re-processing).
     *
     * @param prisonerId The prisoner's UUID
     */
    public void markPrisonerProcessed(UUID prisonerId) {
        processedPrisoners.add(prisonerId);
        setDirty();
    }

    /**
     * Check if a prisoner has been processed.
     *
     * @param prisonerId The prisoner's UUID
     * @return true if already processed
     */
    public boolean isPrisonerProcessed(UUID prisonerId) {
        return processedPrisoners.contains(prisonerId);
    }

    /**
     * Remove a prisoner from processed set.
     *
     * @param prisonerId The prisoner's UUID
     */
    public void unmarkPrisonerProcessed(UUID prisonerId) {
        if (processedPrisoners.remove(prisonerId)) {
            setDirty();
        }
    }

    /**
     * Get the set of processed prisoners for a camp (unmodifiable).
     *
     * @return Unmodifiable set of processed prisoner UUIDs
     */
    public Set<UUID> getProcessedPrisoners() {
        return Collections.unmodifiableSet(processedPrisoners);
    }

    // ==================== PERSISTENCE ====================

    @Override
    public @NotNull CompoundTag save(@NotNull CompoundTag tag) {
        // Save camps
        ListTag campList = new ListTag();
        for (CampData camp : camps.values()) {
            campList.add(camp.save());
        }
        tag.put("camps", campList);

        // Save processed prisoners
        ListTag processedList = new ListTag();
        for (UUID uuid : processedPrisoners) {
            CompoundTag uuidTag = new CompoundTag();
            uuidTag.putUUID("uuid", uuid);
            processedList.add(uuidTag);
        }
        tag.put("processedPrisoners", processedList);

        return tag;
    }

    public static CampOwnership load(CompoundTag tag) {
        CampOwnership registry = new CampOwnership();

        // Load camps
        if (tag.contains("camps")) {
            ListTag campList = tag.getList("camps", Tag.TAG_COMPOUND);
            for (int i = 0; i < campList.size(); i++) {
                CampData camp = CampData.load(campList.getCompound(i));
                registry.camps.put(camp.getCampId(), camp);
            }
        }

        // Load processed prisoners
        if (tag.contains("processedPrisoners")) {
            ListTag processedList = tag.getList(
                "processedPrisoners",
                Tag.TAG_COMPOUND
            );
            for (int i = 0; i < processedList.size(); i++) {
                CompoundTag uuidTag = processedList.getCompound(i);
                if (uuidTag.contains("uuid")) {
                    registry.processedPrisoners.add(uuidTag.getUUID("uuid"));
                }
            }
        }

        return registry;
    }
}
