package com.tiedup.remake.events.lifecycle;

import com.tiedup.remake.cells.CampOwnership;
import com.tiedup.remake.cells.CellRegistryV2;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.events.restriction.BondageItemRestrictionHandler;
import com.tiedup.remake.labor.LaborTask;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.labor.PacketSyncLaborProgress;
import com.tiedup.remake.network.sync.PacketSyncBindState;
import com.tiedup.remake.prison.LaborRecord;
import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.prison.PrisonerRecord;
import com.tiedup.remake.prison.PrisonerState;
import com.tiedup.remake.state.IPlayerLeashAccess;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2BondageEquipmentProvider;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.decoration.LeashFenceKnotEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

/**
 * Event handler for PlayerBindState lifecycle management.
 * Handles player connection, disconnection, death, and respawn.
 */
@Mod.EventBusSubscriber(
    modid = TiedUpMod.MOD_ID,
    bus = Mod.EventBusSubscriber.Bus.FORGE
)
public class PlayerStateEventHandler {

    /** UUIDs of players who died while imprisoned — message sent on respawn */
    private static final Set<UUID> pendingDeathEscapeMessage =
        ConcurrentHashMap.newKeySet();

    /**
     * Called when a player logs in to the server.
     * Initialize or reset their PlayerBindState, restore pole leash, and sync to client.
     */
    @SubscribeEvent
    public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
        if (!(event.getEntity() instanceof ServerPlayer player)) {
            return;
        }

        PlayerBindState state = PlayerBindState.getInstance(player);
        if (state != null) {
            state.resetNewConnection(player);
        }

        // Restore pole leash if player was leashed to one when they disconnected
        restorePoleLeashIfNeeded(player);

        // MEDIUM FIX: Restore captor if player was captive when they disconnected
        restoreCaptorIfNeeded(player);

        // Sync V2 equipment + bind state to the logging-in player
        com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.sync(player);

        PacketSyncBindState statePacket = PacketSyncBindState.fromPlayer(
            player
        );
        if (statePacket != null) {
            ModNetwork.sendToPlayer(statePacket, player);
        }

        // Sync Labor HUD on login
        syncLaborState(player);
    }

    /**
     * Restore leash to pole if player was leashed when they disconnected.
     */
    private static void restorePoleLeashIfNeeded(ServerPlayer player) {
        player
            .getCapability(V2BondageEquipmentProvider.V2_BONDAGE_EQUIPMENT)
            .ifPresent(cap -> {
                if (!cap.wasLeashedToPole()) {
                    return;
                }

                BlockPos polePos = cap.getSavedPolePosition();
                ResourceKey<Level> poleDimension = cap.getSavedPoleDimension();

                if (polePos == null || poleDimension == null) {
                    cap.clearSavedPoleLeash();
                    return;
                }

                // Check if player is in the same dimension
                if (!player.level().dimension().equals(poleDimension)) {
                    TiedUpMod.LOGGER.info(
                        "[Lifecycle] {} reconnected but is in different dimension. Clearing saved pole leash.",
                        player.getName().getString()
                    );
                    cap.clearSavedPoleLeash();
                    return;
                }

                ServerLevel level = player.serverLevel();

                // Try to find or create the LeashFenceKnotEntity at that position
                LeashFenceKnotEntity fenceKnot =
                    LeashFenceKnotEntity.getOrCreateKnot(level, polePos);

                if (fenceKnot == null) {
                    TiedUpMod.LOGGER.info(
                        "[Lifecycle] {} reconnected but pole at {} no longer exists.",
                        player.getName().getString(),
                        polePos
                    );
                    cap.clearSavedPoleLeash();
                    return;
                }

                // Attach leash to the pole
                if (player instanceof IPlayerLeashAccess access) {
                    access.tiedup$attachLeash(fenceKnot);
                    TiedUpMod.LOGGER.info(
                        "[Lifecycle] {} reconnected. Restored leash to pole at {}.",
                        player.getName().getString(),
                        polePos
                    );
                }

                // Clear saved data (no longer needed)
                cap.clearSavedPoleLeash();
            });
    }

    /**
     * MEDIUM FIX: Restore captor if player was captive when they disconnected.
     */
    private static void restoreCaptorIfNeeded(ServerPlayer player) {
        player
            .getCapability(V2BondageEquipmentProvider.V2_BONDAGE_EQUIPMENT)
            .ifPresent(cap -> {
                if (!cap.hasSavedCaptor()) {
                    return;
                }

                java.util.UUID captorUUID = cap.getSavedCaptorUUID();
                if (captorUUID == null) {
                    cap.clearSavedCaptor();
                    return;
                }

                ServerLevel level = player.serverLevel();

                // Try to find the captor entity
                net.minecraft.world.entity.Entity captorEntity =
                    level.getEntity(captorUUID);

                if (captorEntity == null) {
                    TiedUpMod.LOGGER.info(
                        "[Lifecycle] {} reconnected but captor {} no longer exists. Clearing captivity.",
                        player.getName().getString(),
                        captorUUID.toString().substring(0, 8)
                    );
                    cap.clearSavedCaptor();
                    return;
                }

                // Check if captor is still a valid ICaptor
                if (
                    !(captorEntity instanceof
                            net.minecraft.world.entity.LivingEntity livingCaptor)
                ) {
                    TiedUpMod.LOGGER.info(
                        "[Lifecycle] {} reconnected but captor is not a LivingEntity. Clearing captivity.",
                        player.getName().getString()
                    );
                    cap.clearSavedCaptor();
                    return;
                }

                // Get the ICaptor interface
                com.tiedup.remake.state.ICaptor captor = null;
                if (
                    livingCaptor instanceof
                        com.tiedup.remake.state.ICaptor kidnapper
                ) {
                    captor = kidnapper;
                } else {
                    TiedUpMod.LOGGER.info(
                        "[Lifecycle] {} reconnected but captor {} does not implement ICaptor. Clearing captivity.",
                        player.getName().getString(),
                        captorUUID.toString().substring(0, 8)
                    );
                    cap.clearSavedCaptor();
                    return;
                }

                // Check if player is still enslavable (still tied up)
                PlayerBindState state = PlayerBindState.getInstance(player);
                if (state == null || !state.isTiedUp()) {
                    TiedUpMod.LOGGER.info(
                        "[Lifecycle] {} reconnected but is no longer tied up. Clearing saved captor.",
                        player.getName().getString()
                    );
                    cap.clearSavedCaptor();
                    return;
                }

                // Restore captivity using the PlayerCaptivity component
                boolean success = state.getCapturedBy(captor);

                if (success) {
                    TiedUpMod.LOGGER.info(
                        "[Lifecycle] {} reconnected. Restored captivity to {}.",
                        player.getName().getString(),
                        captorEntity.getName().getString()
                    );
                } else {
                    TiedUpMod.LOGGER.warn(
                        "[Lifecycle] {} reconnected but failed to restore captivity to {}.",
                        player.getName().getString(),
                        captorEntity.getName().getString()
                    );
                }

                // Clear saved data (no longer needed)
                cap.clearSavedCaptor();
            });
    }

    /**
     * Called when a player logs out of the server.
     * Clean up their PlayerBindState instance.
     */
    @SubscribeEvent
    public static void onPlayerLoggedOut(
        PlayerEvent.PlayerLoggedOutEvent event
    ) {
        if (!(event.getEntity() instanceof ServerPlayer player)) {
            return;
        }

        // NOTE: PlayerBindState.removeInstance() is NOT called here.
        // The canonical removal point is PlayerLifecycleHandler.onPlayerLoggedOut (EventPriority.HIGH).
        // Calling it here at NORMAL priority would be a redundant double-removal.

        // Clean up message cooldowns to prevent memory leak
        BondageItemRestrictionHandler.clearCooldowns(player.getUUID());

        // Clean up pending death escape message flag (player disconnected between death and respawn)
        pendingDeathEscapeMessage.remove(player.getUUID());
    }

    /**
     * Called when a player dies.
     * Handle bondage state cleanup based on game rules.
     */
    @SubscribeEvent
    public static void onPlayerDeath(LivingDeathEvent event) {
        if (!(event.getEntity() instanceof ServerPlayer player)) {
            return;
        }

        PlayerBindState state = PlayerBindState.getInstance(player);
        if (state != null) {
            state.onDeathKidnapped(player.level());
        }

        // Clean up captivity state - prisoner can't continue task after death
        PrisonerManager manager = PrisonerManager.get(player.serverLevel());
        PrisonerRecord record = manager.getRecord(player.getUUID());
        LaborRecord laborRecord = manager.getLaborRecord(player.getUUID());

        if (record.isImprisoned() || laborRecord.hasTask()) {
            // SECURITY: Remove labor tools before death to prevent item duplication
            // (Tools would otherwise drop on death)
            removeLaborToolsOnDeath(player);

            // Transition to FREE state via escape (clears all data)
            long currentTime = player.serverLevel().getGameTime();
            // Use centralized escape service for complete cleanup
            com.tiedup.remake.prison.service.EscapeMonitorService.get().escape(
                player.serverLevel(),
                player.getUUID(),
                "death"
            );

            // Flag for respawn message
            pendingDeathEscapeMessage.add(player.getUUID());

            TiedUpMod.LOGGER.debug(
                "[PlayerStateEventHandler] Transitioned {} to FREE state on death",
                player.getName().getString()
            );
        } else if (record.isProtected(player.serverLevel().getGameTime())) {
            // Revoke grace period on death - player can be targeted again after respawn
            // Death is considered "payment" for any remaining grace
            record.setProtectionExpiry(0);
        }

        // Release prisoner from any cells to prevent "ghost prisoner" blocking cells
        // Without this, the cell still counts this player as prisoner, making it "full"
        // and preventing new kidnappers from using it
        CellRegistryV2 cellRegistry = CellRegistryV2.get(player.server);
        if (cellRegistry != null) {
            int released = cellRegistry.releasePrisonerFromAllCells(
                player.getUUID()
            );
            if (released > 0) {
                TiedUpMod.LOGGER.debug(
                    "[PlayerStateEventHandler] Released {} from {} cell(s) on death",
                    player.getName().getString(),
                    released
                );
            }
        }
    }

    /**
     * Called when a player respawns after death.
     * Handled by PlayerEvent.Clone in CapabilityEventHandler (capability copying)
     * and PlayerLoggedIn (state reset).
     *
     * The respawn process:
     * 1. PlayerEvent.Clone -> Copy capability data from old player to new player
     * 2. PlayerLoggedIn -> Reset PlayerBindState with new player entity
     * 3. Sync to client
     */
    @SubscribeEvent
    public static void onPlayerRespawn(PlayerEvent.PlayerRespawnEvent event) {
        if (!(event.getEntity() instanceof ServerPlayer player)) {
            return;
        }

        // Get or create state for the respawned player
        PlayerBindState state = PlayerBindState.getInstance(player);
        if (state != null) {
            state.resetNewConnection(player);
        }

        // Sync V2 equipment + bind state to the respawned player
        com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.sync(player);

        PacketSyncBindState statePacket = PacketSyncBindState.fromPlayer(
            player
        );
        if (statePacket != null) {
            ModNetwork.sendToPlayer(statePacket, player);
        }

        // Sync Labor HUD on respawn (restores task OR clears it if executed)
        syncLaborState(player);

        // Send death escape message if applicable
        if (pendingDeathEscapeMessage.remove(player.getUUID())) {
            player.sendSystemMessage(
                net.minecraft.network.chat.Component.translatable(
                    "msg.tiedup.event.death_escape"
                ).withStyle(net.minecraft.ChatFormatting.YELLOW)
            );
        }
    }

    /**
     * Helper to sync labor state to client.
     * Sends current progress if active, or a clear packet if not.
     */
    private static void syncLaborState(ServerPlayer player) {
        PrisonerManager manager = PrisonerManager.get(player.serverLevel());
        PrisonerRecord record = manager.getRecord(player.getUUID());
        LaborRecord laborRecord = manager.getLaborRecord(player.getUUID());

        if (record.isImprisoned() && laborRecord.hasTask()) {
            LaborTask task = laborRecord.getTask();
            ModNetwork.sendToPlayer(
                new PacketSyncLaborProgress(
                    task.getDescription(),
                    task.getProgress(),
                    task.getQuota(),
                    task.getValue()
                ),
                player
            );
            TiedUpMod.LOGGER.debug(
                "[PlayerStateEventHandler] Synced labor HUD for {}: {}/{} {}",
                player.getName().getString(),
                task.getProgress(),
                task.getQuota(),
                task.getDescription()
            );
        } else {
            // No active captivity state - ensure HUD is cleared (important after execution/death)
            ModNetwork.sendToPlayer(new PacketSyncLaborProgress(), player);
            TiedUpMod.LOGGER.debug(
                "[PlayerStateEventHandler] Cleared labor HUD for {}",
                player.getName().getString()
            );
        }
    }

    /**
     * SECURITY: Remove all labor tools from player inventory before death.
     * Prevents tools from dropping and being recoverable.
     */
    private static void removeLaborToolsOnDeath(ServerPlayer player) {
        var inventory = player.getInventory();
        int removedCount = 0;

        for (int i = 0; i < inventory.getContainerSize(); i++) {
            ItemStack stack = inventory.getItem(i);
            if (!stack.isEmpty() && stack.hasTag()) {
                CompoundTag tag = stack.getTag();
                if (tag != null && tag.getBoolean("LaborTool")) {
                    inventory.setItem(i, ItemStack.EMPTY);
                    removedCount++;
                }
            }
        }

        if (removedCount > 0) {
            TiedUpMod.LOGGER.debug(
                "[PlayerStateEventHandler] Removed {} labor tools from {} before death",
                removedCount,
                player.getName().getString()
            );
        }
    }
}
