package com.tiedup.remake.state.components;

import com.tiedup.remake.core.ModSounds;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.core.SystemMessageManager.MessageCategory;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.state.hosts.IPlayerBindStateHost;
import com.tiedup.remake.util.GameConstants;
import com.tiedup.remake.util.time.Timer;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.GpsComponent;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import java.util.List;
import java.util.UUID;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;

/**
 * Component responsible for shock collar mechanics and GPS tracking.
 *
 * Single Responsibility: Collar automation and GPS monitoring
 * Complexity: HIGH (synchronized timer, GPS zone checks, network messages)
 * Risk: HIGH (timer thread management)
 *
 * Threading: Uses synchronized blocks for timer access (volatile Timer field)
 */
public class PlayerShockCollar {

    private final IPlayerBindStateHost host;

    // volatile: accessed from synchronized blocks across multiple threads
    private volatile Timer timerAutoShockCollar;
    private final Object lockTimerAutoShock = new Object();

    public PlayerShockCollar(IPlayerBindStateHost host) {
        this.host = host;
    }

    // ========== Shock Functionality ==========

    /**
     * Triggers a shock with default damage.
     */
    public void shockKidnapped() {
        this.shockKidnapped(null, GameConstants.DEFAULT_SHOCK_DAMAGE);
    }

    /**
     * Triggers a visual and auditory shock effect.
     * Damage is applied (shock can kill).
     *
     * @param messageAddon Optional message addon for HUD (e.g., GPS violation).
     *        For translatable messages, prefer calling shockKidnapped() then sending the message separately.
     * @param damage Shock damage amount
     */
    public void shockKidnapped(@Nullable String messageAddon, float damage) {
        Player player = host.getPlayer();
        if (player == null || !player.isAlive()) return;

        // Sound effects
        player
            .level()
            .playSound(
                null,
                player.blockPosition(),
                ModSounds.ELECTRIC_SHOCK.get(),
                net.minecraft.sounds.SoundSource.PLAYERS,
                GameConstants.SHOCK_SOUND_VOLUME,
                GameConstants.SHOCK_SOUND_PITCH
            );

        // Particle effects
        if (
            player.level() instanceof
                net.minecraft.server.level.ServerLevel serverLevel
        ) {
            serverLevel.sendParticles(
                net.minecraft.core.particles.ParticleTypes.ELECTRIC_SPARK,
                player.getX(),
                player.getY() + GameConstants.SHOCK_PARTICLE_Y_OFFSET,
                player.getZ(),
                GameConstants.SHOCK_PARTICLE_COUNT,
                GameConstants.SHOCK_PARTICLE_X_SPREAD,
                GameConstants.SHOCK_PARTICLE_Y_SPREAD,
                GameConstants.SHOCK_PARTICLE_Z_SPREAD,
                GameConstants.SHOCK_PARTICLE_SPEED
            );
        }

        // Damage logic - shock can kill
        if (damage > 0) {
            player.hurt(player.damageSources().magic(), damage);
        }

        // HUD Message via SystemMessageManager
        if (messageAddon != null) {
            // Legacy addon path: append literal text to SLAVE_SHOCK message
            MutableComponent msg = Component.translatable(
                SystemMessageManager.getTranslationKey(MessageCategory.SLAVE_SHOCK)
            ).append(Component.literal(messageAddon));
            msg.withStyle(style -> style.withColor(ChatFormatting.RED));
            player.displayClientMessage(msg, true);
        } else {
            SystemMessageManager.sendToPlayer(
                player,
                MessageCategory.SLAVE_SHOCK
            );
        }
    }

    // ========== Auto-Shock & GPS Monitoring ==========

    /**
     * Periodic check for Auto-Shock intervals and GPS Safe Zones.
     * Called from RestraintTaskTickHandler every player tick.
     *
     * Thread Safety: Refactored to avoid alien method calls under lock.
     * We compute shock decisions inside synchronized block, then execute
     * outside the lock to prevent deadlock risk.
     */
    public void checkAutoShockCollar() {
        Player player = host.getPlayer();
        if (player == null || !player.isAlive()) return;

        // Flags set inside lock, actions performed outside
        boolean shouldShockAuto = false;
        boolean shouldShockGPS = false;
        ItemStack gpsStackCopy = null;

        synchronized (lockTimerAutoShock) {
            ItemStack collarStack = getCurrentCollar();
            if (collarStack.isEmpty()) return;

            // Auto-Shock Collar handling: collar can shock AND has auto interval > 0
            if (CollarHelper.canShock(collarStack) && CollarHelper.getShockInterval(collarStack) > 0) {
                int interval = CollarHelper.getShockInterval(collarStack);

                if (
                    timerAutoShockCollar != null &&
                    timerAutoShockCollar.isExpired()
                ) {
                    shouldShockAuto = true;
                }

                if (
                    timerAutoShockCollar == null ||
                    timerAutoShockCollar.isExpired()
                ) {
                    timerAutoShockCollar = new Timer(
                        interval / GameConstants.TICKS_PER_SECOND,
                        player.level()
                    );
                }
            }
            // GPS Collar handling
            else if (CollarHelper.hasGPS(collarStack)) {
                if (
                    CollarHelper.isActive(collarStack) &&
                    (timerAutoShockCollar == null ||
                        timerAutoShockCollar.isExpired())
                ) {
                    GpsComponent gpsComp = DataDrivenBondageItem.getComponent(
                        collarStack, ComponentType.GPS, GpsComponent.class
                    );
                    int safeZoneRadius = gpsComp != null ? gpsComp.getSafeZoneRadius() : 50;
                    // Shock interval from ShockComponent (GPS collars also have shock capability)
                    int shockInterval = CollarHelper.getShockInterval(collarStack);
                    if (shockInterval <= 0) shockInterval = 100; // Fallback: 5 seconds

                    // Check safe spots from NBT (CollarHelper reads "safeSpots" NBT)
                    // For now, use safeZoneRadius from GpsComponent
                    // GPS safe zone check: if the collar has safe spots in NBT, check them
                    boolean isSafe = isInSafeZone(collarStack, player, safeZoneRadius);

                    if (!isSafe) {
                        timerAutoShockCollar = new Timer(
                            shockInterval / GameConstants.TICKS_PER_SECOND,
                            player.level()
                        );
                        shouldShockGPS = true;
                        gpsStackCopy = collarStack.copy();
                    }
                }
            }
        }

        // Execute shock actions OUTSIDE the lock (avoid alien method call under lock)
        if (shouldShockAuto) {
            this.shockKidnapped();
        }

        if (shouldShockGPS && gpsStackCopy != null) {
            this.shockKidnapped(
                null,
                GameConstants.DEFAULT_SHOCK_DAMAGE
            );
            // GPS-specific message (replaces hardcoded " Return back to your allowed area!")
            SystemMessageManager.sendToPlayer(host.getPlayer(), MessageCategory.GPS_ZONE_VIOLATION);
            warnOwnersGPSViolation(gpsStackCopy);
        }
    }

    /**
     * Check if the player is inside any safe zone defined on the collar.
     * Reads safe spots from NBT "safeSpots" ListTag.
     */
    private boolean isInSafeZone(ItemStack collarStack, Player player, int defaultRadius) {
        net.minecraft.nbt.CompoundTag tag = collarStack.getTag();
        if (tag == null || !tag.contains("safeSpots", net.minecraft.nbt.Tag.TAG_LIST)) {
            return true; // No safe spots defined = always safe
        }
        net.minecraft.nbt.ListTag safeSpots = tag.getList("safeSpots", net.minecraft.nbt.Tag.TAG_COMPOUND);
        if (safeSpots.isEmpty()) return true;

        for (int i = 0; i < safeSpots.size(); i++) {
            net.minecraft.nbt.CompoundTag spot = safeSpots.getCompound(i);
            double x = spot.getDouble("x");
            double y = spot.getDouble("y");
            double z = spot.getDouble("z");
            int radius = spot.contains("radius") ? spot.getInt("radius") : defaultRadius;
            double dist = player.distanceToSqr(x, y, z);
            if (dist <= (double) radius * radius) {
                return true;
            }
        }
        return false;
    }

    /**
     * Sends a global alert to masters when a slave violates their GPS zone.
     * Private helper method.
     */
    private void warnOwnersGPSViolation(ItemStack stack) {
        Player player = host.getPlayer();
        if (player.getServer() == null) return;

        if (!CollarHelper.shouldWarnMasters(stack)) return;

        // Translatable: "ALERT: <player name> is outside the safe zone!"
        String playerName = player.getName().getString();

        for (UUID ownerId : CollarHelper.getOwners(stack)) {
            ServerPlayer owner = player
                .getServer()
                .getPlayerList()
                .getPlayer(ownerId);
            if (owner != null) {
                SystemMessageManager.sendChatToPlayer(
                    owner,
                    MessageCategory.GPS_OWNER_ALERT,
                    player
                );
            }
        }
    }

    /**
     * Force-stops and clears any active shock timers.
     * Threading: Synchronized block protects timer access
     */
    public void resetAutoShockTimer() {
        synchronized (lockTimerAutoShock) {
            this.timerAutoShockCollar = null;
        }
    }

    // ========== Helper Methods ==========

    /**
     * Get the current collar ItemStack.
     * Epic 5F: Migrated to V2EquipmentHelper.
     */
    private ItemStack getCurrentCollar() {
        return com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.getInRegion(
            host.getPlayer(),
            com.tiedup.remake.v2.BodyRegionV2.NECK
        );
    }
}
