package com.tiedup.remake.network.sync;

import com.mojang.logging.LogUtils;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.state.IBondageState;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.slf4j.Logger;

/**
 * Client-side sync queue handler.
 *
 * This class is ONLY loaded on the client side to avoid NoClassDefFoundError
 * on dedicated servers. It handles the pending sync queue for players that
 * are not yet loaded when packets arrive.
 *
 * Separated from SyncManager to ensure server-side code doesn't trigger
 * class loading of Minecraft client classes.
 */
@OnlyIn(Dist.CLIENT)
@Mod.EventBusSubscriber(modid = TiedUpMod.MOD_ID, value = Dist.CLIENT)
public class ClientSyncHandler {

    private static final Logger LOGGER = LogUtils.getLogger();

    // ==================== Configuration ====================

    /** Maximum ticks before a pending sync entry expires (5 seconds) */
    private static final int MAX_PENDING_TICKS = 100;

    /** Maximum retry attempts before giving up */
    private static final int MAX_RETRIES = 20;

    /** Ticks between retry attempts */
    private static final int RETRY_INTERVAL = 5;

    // ==================== Client-side pending queue ====================

    /**
     * Queue of pending sync entries waiting to be applied.
     * Thread-safe for access from network thread and client tick.
     */
    private static final Queue<PendingSyncEntry> pendingQueue =
        new ConcurrentLinkedQueue<>();

    /**
     * Track last known tick for expiration checks.
     * Volatile to ensure visibility across network and tick threads.
     */
    private static volatile long currentClientTick = 0;

    // ==================== Queue methods ====================

    /**
     * Queue a bind state sync for later processing.
     * Called from PacketSyncBindState.handle() when target player is null.
     *
     * @param targetUUID The UUID of the player to sync
     * @param stateFlags The state flags to apply
     */
    public static void queueBindStateSync(UUID targetUUID, byte stateFlags) {
        PendingSyncEntry entry = PendingSyncEntry.forBindState(
            targetUUID,
            stateFlags,
            currentClientTick
        );
        pendingQueue.add(entry);
        LOGGER.debug(
            "[ClientSyncHandler] Queued bind state sync for {} (queue size: {})",
            targetUUID,
            pendingQueue.size()
        );
    }

    /**
     * Queue an enslavement sync for later processing.
     * Called from PacketSyncEnslavement.handle() when target player is null.
     *
     * @param targetUUID The UUID of the player to sync
     * @param masterUUID The master's UUID (null if free)
     * @param transportUUID The transport entity UUID (null if no transport)
     * @param isEnslaved Whether the player is enslaved
     */
    public static void queueEnslavementSync(
        UUID targetUUID,
        UUID masterUUID,
        UUID transportUUID,
        boolean isEnslaved
    ) {
        PendingSyncEntry entry = PendingSyncEntry.forEnslavement(
            targetUUID,
            masterUUID,
            transportUUID,
            isEnslaved,
            currentClientTick
        );
        pendingQueue.add(entry);
        LOGGER.debug(
            "[ClientSyncHandler] Queued enslavement sync for {} (queue size: {})",
            targetUUID,
            pendingQueue.size()
        );
    }

    /**
     * Process the pending sync queue. Called every few ticks on client.
     * Attempts to apply queued syncs and removes expired entries.
     */
    private static void processPendingQueue() {
        if (pendingQueue.isEmpty()) return;

        Minecraft mc = Minecraft.getInstance();
        if (mc.level == null) return;

        int processed = 0;
        int expired = 0;

        // Process queue
        Iterator<PendingSyncEntry> iterator = pendingQueue.iterator();
        while (iterator.hasNext()) {
            PendingSyncEntry entry = iterator.next();

            // Check expiration
            if (
                entry.isExpired(
                    currentClientTick,
                    MAX_PENDING_TICKS,
                    MAX_RETRIES
                )
            ) {
                iterator.remove();
                expired++;
                continue;
            }

            // Check if retry interval has passed
            if (entry.getRetryCount() > 0) {
                long ticksSinceCreation =
                    currentClientTick - entry.getCreationTick();
                if (ticksSinceCreation % RETRY_INTERVAL != 0) {
                    continue; // Wait for retry interval
                }
            }

            // Try to find the player
            Player player = mc.level.getPlayerByUUID(
                entry.getTargetPlayerUUID()
            );
            if (player == null) {
                // Player still not loaded, increment retry count
                entry.incrementRetryCount();
                continue;
            }

            // Player found! Apply the sync
            boolean success = applyPendingSync(entry, player);
            if (success) {
                iterator.remove();
                processed++;
            }
        }

        if (processed > 0 || expired > 0) {
            LOGGER.debug(
                "[ClientSyncHandler] Processed {} pending, {} expired",
                processed,
                expired
            );
        }
    }

    /**
     * Apply a pending sync entry to a player.
     *
     * @param entry The sync entry
     * @param player The target player
     * @return true if successful
     */
    private static boolean applyPendingSync(
        PendingSyncEntry entry,
        Player player
    ) {
        switch (entry.getType()) {
            case BIND_STATE -> {
                // Bind state is derived from inventory, so just trigger a state check
                // The actual flags are informational
                return true;
            }
            case ENSLAVEMENT -> {
                // Enslavement state is primarily server-side, but we store it for UI awareness
                // The actual leash rendering uses Minecraft's entity system
                IBondageState kidnappedState =
                    com.tiedup.remake.util.KidnappedHelper.getKidnappedState(
                        player
                    );
                if (kidnappedState != null) {
                    // Client now knows about this player's enslavement state
                    // This enables UI indicators and rendering awareness
                    LOGGER.debug(
                        "[ClientSyncHandler] Applied enslavement sync for {} (enslaved: {})",
                        player.getName().getString(),
                        entry.isEnslaved()
                    );
                }
                return true;
            }
            default -> {
                return false;
            }
        }
    }

    /**
     * Clear all pending syncs for a player (e.g., when they disconnect).
     *
     * @param playerUUID The player's UUID
     */
    public static void clearPendingForPlayer(UUID playerUUID) {
        pendingQueue.removeIf(entry ->
            entry.getTargetPlayerUUID().equals(playerUUID)
        );
    }

    /**
     * Clear the entire pending queue (e.g., when disconnecting from server).
     */
    public static void clearAllPending() {
        pendingQueue.clear();
        LOGGER.debug("[ClientSyncHandler] Cleared all pending syncs");
    }

    // ==================== Tick handler ====================

    /**
     * Client tick handler to process pending syncs.
     */
    @SubscribeEvent
    public static void onClientTick(TickEvent.ClientTickEvent event) {
        if (event.phase != TickEvent.Phase.END) return;

        currentClientTick++;

        // Process queue every RETRY_INTERVAL ticks
        if (currentClientTick % RETRY_INTERVAL == 0) {
            processPendingQueue();
        }
    }
}
