package com.tiedup.remake.v2.bondage;

import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.EntityKidnapper;
import com.tiedup.remake.entities.ai.kidnapper.KidnapperState;
import com.tiedup.remake.state.CollarRegistry;
import com.tiedup.remake.v2.bondage.component.ChokingComponent;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GpsComponent;
import com.tiedup.remake.v2.bondage.component.OwnershipComponent;
import com.tiedup.remake.v2.bondage.component.ShockComponent;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.AABB;
import org.jetbrains.annotations.Nullable;

/**
 * Static utility for collar operations using V2 data-driven items
 * with OwnershipComponent.
 */
public final class CollarHelper {

    private CollarHelper() {}

    // Thread-local flag to suppress collar removal alerts during programmatic unequip
    private static final ThreadLocal<Boolean> SUPPRESS_REMOVAL_ALERT =
        ThreadLocal.withInitial(() -> false);

    // ===== DETECTION =====

    // True if the stack is a collar (V2 data-driven item with OwnershipComponent)
    public static boolean isCollar(ItemStack stack) {
        if (stack.isEmpty()) return false;
        return DataDrivenBondageItem.getComponent(stack, ComponentType.OWNERSHIP, OwnershipComponent.class) != null;
    }

    // ===== OWNERSHIP (NBT: "owners") =====

    // Returns all owner UUIDs stored in the collar's "owners" ListTag
    public static List<UUID> getOwners(ItemStack stack) {
        return getListUUIDs(stack, "owners");
    }

    // True if the given UUID is in the owners list
    public static boolean isOwner(ItemStack stack, UUID uuid) {
        return hasUUIDInList(stack, "owners", uuid);
    }

    // True if the given player is an owner
    public static boolean isOwner(ItemStack stack, Player player) {
        return isOwner(stack, player.getUUID());
    }

    // True if the collar has at least one owner
    public static boolean hasOwner(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        if (tag == null || !tag.contains("owners", Tag.TAG_LIST)) return false;
        return !tag.getList("owners", Tag.TAG_COMPOUND).isEmpty();
    }

    // Adds an owner entry {uuid, name} to the "owners" ListTag
    public static void addOwner(ItemStack stack, UUID uuid, String name) {
        addToList(stack, "owners", uuid, name);
    }

    // Convenience: add a player as owner
    public static void addOwner(ItemStack stack, Player player) {
        addOwner(stack, player.getUUID(), player.getGameProfile().getName());
    }

    // Removes an owner by UUID
    public static void removeOwner(ItemStack stack, UUID uuid) {
        removeFromList(stack, "owners", uuid);
    }

    // ===== BLACKLIST (NBT: "blacklist") =====

    public static List<UUID> getBlacklist(ItemStack stack) {
        return getListUUIDs(stack, "blacklist");
    }

    public static boolean isBlacklisted(ItemStack stack, UUID uuid) {
        return hasUUIDInList(stack, "blacklist", uuid);
    }

    public static void addToBlacklist(ItemStack stack, UUID uuid, String name) {
        addToList(stack, "blacklist", uuid, name);
    }

    public static void removeFromBlacklist(ItemStack stack, UUID uuid) {
        removeFromList(stack, "blacklist", uuid);
    }

    // ===== WHITELIST (NBT: "whitelist") =====

    public static List<UUID> getWhitelist(ItemStack stack) {
        return getListUUIDs(stack, "whitelist");
    }

    public static boolean isWhitelisted(ItemStack stack, UUID uuid) {
        return hasUUIDInList(stack, "whitelist", uuid);
    }

    public static void addToWhitelist(ItemStack stack, UUID uuid, String name) {
        addToList(stack, "whitelist", uuid, name);
    }

    public static void removeFromWhitelist(ItemStack stack, UUID uuid) {
        removeFromList(stack, "whitelist", uuid);
    }

    // ===== LIST INTERNALS =====

    private static List<UUID> getListUUIDs(ItemStack stack, String listKey) {
        List<UUID> result = new ArrayList<>();
        CompoundTag tag = stack.getTag();
        if (tag == null || !tag.contains(listKey, Tag.TAG_LIST)) return result;
        ListTag list = tag.getList(listKey, Tag.TAG_COMPOUND);
        for (int i = 0; i < list.size(); i++) {
            CompoundTag entry = list.getCompound(i);
            if (entry.contains("uuid")) {
                try {
                    result.add(UUID.fromString(entry.getString("uuid")));
                } catch (IllegalArgumentException ignored) {
                    // Malformed UUID in NBT, skip
                }
            }
        }
        return result;
    }

    private static boolean hasUUIDInList(ItemStack stack, String listKey, UUID uuid) {
        CompoundTag tag = stack.getTag();
        if (tag == null || !tag.contains(listKey, Tag.TAG_LIST)) return false;
        String uuidStr = uuid.toString();
        ListTag list = tag.getList(listKey, Tag.TAG_COMPOUND);
        for (int i = 0; i < list.size(); i++) {
            if (uuidStr.equals(list.getCompound(i).getString("uuid"))) {
                return true;
            }
        }
        return false;
    }

    private static void addToList(ItemStack stack, String listKey, UUID uuid, String name) {
        CompoundTag tag = stack.getOrCreateTag();
        ListTag list = tag.contains(listKey, Tag.TAG_LIST)
            ? tag.getList(listKey, Tag.TAG_COMPOUND)
            : new ListTag();
        // Prevent duplicates
        String uuidStr = uuid.toString();
        for (int i = 0; i < list.size(); i++) {
            if (uuidStr.equals(list.getCompound(i).getString("uuid"))) return;
        }
        CompoundTag entry = new CompoundTag();
        entry.putString("uuid", uuidStr);
        entry.putString("name", name);
        list.add(entry);
        tag.put(listKey, list);
    }

    private static void removeFromList(ItemStack stack, String listKey, UUID uuid) {
        CompoundTag tag = stack.getTag();
        if (tag == null || !tag.contains(listKey, Tag.TAG_LIST)) return;
        String uuidStr = uuid.toString();
        ListTag list = tag.getList(listKey, Tag.TAG_COMPOUND);
        list.removeIf(element ->
            uuidStr.equals(((CompoundTag) element).getString("uuid"))
        );
    }

    // ===== FEATURES =====

    @Nullable
    public static String getNickname(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        if (tag == null || !tag.contains("nickname")) return null;
        return tag.getString("nickname");
    }

    public static void setNickname(ItemStack stack, String nickname) {
        stack.getOrCreateTag().putString("nickname", nickname);
    }

    public static boolean hasNickname(ItemStack stack) {
        return stack.hasTag() && stack.getTag().contains("nickname");
    }

    @Nullable
    public static UUID getCellId(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        if (tag == null || !tag.contains("cellId")) return null;
        try {
            return UUID.fromString(tag.getString("cellId"));
        } catch (IllegalArgumentException e) {
            return null;
        }
    }

    public static void setCellId(ItemStack stack, UUID cellId) {
        stack.getOrCreateTag().putString("cellId", cellId.toString());
    }

    public static boolean hasCellAssigned(ItemStack stack) {
        return getCellId(stack) != null;
    }

    public static boolean isKidnappingModeEnabled(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        return tag != null && tag.getBoolean("kidnappingMode");
    }

    public static void setKidnappingModeEnabled(ItemStack stack, boolean enabled) {
        stack.getOrCreateTag().putBoolean("kidnappingMode", enabled);
    }

    // Kidnapping mode is ready when enabled AND a cell is assigned
    public static boolean isKidnappingModeReady(ItemStack stack) {
        return isKidnappingModeEnabled(stack) && hasCellAssigned(stack);
    }

    public static boolean shouldTieToPole(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        return tag != null && tag.getBoolean("tieToPole");
    }

    public static void setShouldTieToPole(ItemStack stack, boolean value) {
        stack.getOrCreateTag().putBoolean("tieToPole", value);
    }

    // Default true when tag is absent or key is missing
    public static boolean shouldWarnMasters(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        if (tag == null || !tag.contains("warnMasters")) return true;
        return tag.getBoolean("warnMasters");
    }

    public static void setShouldWarnMasters(ItemStack stack, boolean value) {
        stack.getOrCreateTag().putBoolean("warnMasters", value);
    }

    public static boolean isBondageServiceEnabled(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        return tag != null && tag.getBoolean("bondageservice");
    }

    public static void setBondageServiceEnabled(ItemStack stack, boolean enabled) {
        stack.getOrCreateTag().putBoolean("bondageservice", enabled);
    }

    @Nullable
    public static String getServiceSentence(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        if (tag == null || !tag.contains("servicesentence")) return null;
        return tag.getString("servicesentence");
    }

    public static void setServiceSentence(ItemStack stack, String sentence) {
        stack.getOrCreateTag().putString("servicesentence", sentence);
    }

    // ===== SHOCK =====

    // True if the collar can shock (V2 ShockComponent)
    public static boolean canShock(ItemStack stack) {
        if (stack.isEmpty()) return false;
        return DataDrivenBondageItem.getComponent(stack, ComponentType.SHOCK, ShockComponent.class) != null;
    }

    public static boolean isPublicShock(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        return tag != null && tag.getBoolean("public_mode");
    }

    public static void setPublicShock(ItemStack stack, boolean publicMode) {
        stack.getOrCreateTag().putBoolean("public_mode", publicMode);
    }

    // V2: from ShockComponent auto interval, else 0
    public static int getShockInterval(ItemStack stack) {
        ShockComponent comp = DataDrivenBondageItem.getComponent(
            stack, ComponentType.SHOCK, ShockComponent.class
        );
        if (comp != null) return comp.getAutoInterval();
        return 0;
    }

    // ===== GPS =====

    // True if the collar has GPS capabilities (V2 GpsComponent)
    public static boolean hasGPS(ItemStack stack) {
        if (stack.isEmpty()) return false;
        return DataDrivenBondageItem.getComponent(stack, ComponentType.GPS, GpsComponent.class) != null;
    }

    public static boolean hasPublicTracking(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        return tag != null && tag.getBoolean("publicTracking");
    }

    public static void setPublicTracking(ItemStack stack, boolean publicTracking) {
        stack.getOrCreateTag().putBoolean("publicTracking", publicTracking);
    }

    // GPS active defaults to true when absent
    public static boolean isActive(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        if (tag == null || !tag.contains("gpsActive")) return true;
        return tag.getBoolean("gpsActive");
    }

    public static void setActive(ItemStack stack, boolean active) {
        stack.getOrCreateTag().putBoolean("gpsActive", active);
    }

    // ===== CHOKE =====

    // True if the collar is a choke collar (V2 ChokingComponent)
    public static boolean isChokeCollar(ItemStack stack) {
        if (stack.isEmpty()) return false;
        return DataDrivenBondageItem.getComponent(stack, ComponentType.CHOKING, ChokingComponent.class) != null;
    }

    public static boolean isChoking(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        return tag != null && tag.getBoolean("choking");
    }

    public static void setChoking(ItemStack stack, boolean choking) {
        stack.getOrCreateTag().putBoolean("choking", choking);
    }

    public static boolean isPetPlayMode(ItemStack stack) {
        CompoundTag tag = stack.getTag();
        return tag != null && tag.getBoolean("petPlayMode");
    }

    public static void setPetPlayMode(ItemStack stack, boolean petPlay) {
        stack.getOrCreateTag().putBoolean("petPlayMode", petPlay);
    }

    // ===== ALERT SUPPRESSION =====

    // Executes the action with collar removal alerts suppressed
    public static void runWithSuppressedAlert(Runnable action) {
        SUPPRESS_REMOVAL_ALERT.set(true);
        try {
            action.run();
        } finally {
            SUPPRESS_REMOVAL_ALERT.set(false);
        }
    }

    // True if removal alerts are currently suppressed (ThreadLocal state)
    public static boolean isRemovalAlertSuppressed() {
        return SUPPRESS_REMOVAL_ALERT.get();
    }

    // ===== COLLAR REMOVAL =====

    /**
     * Alert nearby kidnappers when a collar is forcibly removed.
     * Handles CollarRegistry unregistration and kidnapper alerting.
     *
     * @param wearer The entity whose collar was removed
     * @param forced Whether the removal was forced (struggle, lockpick, etc.)
     */
    public static void onCollarRemoved(LivingEntity wearer, boolean forced) {
        if (wearer == null || wearer.level().isClientSide()) {
            return;
        }

        if (!forced) {
            return; // Only alert on forced removals
        }

        if (!(wearer.level() instanceof ServerLevel serverLevel)) {
            return;
        }

        TiedUpMod.LOGGER.info(
            "[CollarHelper] {} collar was forcibly removed - alerting kidnappers",
            wearer.getName().getString()
        );

        // Unregister from CollarRegistry
        CollarRegistry registry = CollarRegistry.get(serverLevel);
        if (registry != null) {
            registry.unregisterWearer(wearer.getUUID());
        }

        // Find and alert nearby kidnappers
        AABB searchBox = wearer.getBoundingBox().inflate(50, 20, 50);
        List<EntityKidnapper> kidnappers = serverLevel.getEntitiesOfClass(
            EntityKidnapper.class, searchBox
        );

        for (EntityKidnapper kidnapper : kidnappers) {
            if (!kidnapper.hasCaptives() && !kidnapper.isTiedUp()) {
                kidnapper.setAlertTarget(wearer);
                kidnapper.setCurrentState(KidnapperState.ALERT);
                kidnapper.broadcastAlert(wearer);

                TiedUpMod.LOGGER.debug(
                    "[CollarHelper] Alerted kidnapper {} about collar removal",
                    kidnapper.getNpcName()
                );
            }
        }
    }
}
