package coldfusion.hytaleserver;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Guards against inventory sharing between players (Issue #45).
 * <p>
 * This class is called by the patched LivingEntity.setInventory() method
 * to validate that an Inventory isn't being shared between multiple players.
 * <p>
 * If a shared reference is detected, the inventory is deep-cloned to break
 * the shared reference before assignment.
 */
public class InventoryOwnershipGuard {

    private static final Logger LOGGER = Logger.getLogger("HyFixes");

    // Track inventory -> owner UUID
    private static final Map<Integer, UUID> inventoryOwners = new ConcurrentHashMap<>();
    private static final Map<Integer, UUID> containerOwners = new ConcurrentHashMap<>();

    // Statistics
    private static volatile int sharedReferencesDetected = 0;
    private static volatile int inventoriesCloned = 0;
    private static volatile int validationCalls = 0;

    // Cached reflection objects
    private static Method getPlayerRefMethod;
    private static Method getUuidMethod;
    private static Class<?> playerClass;
    private static boolean reflectionInitialized = false;
    private static boolean reflectionFailed = false;

    /**
     * Called by patched LivingEntity.setInventory() BEFORE inventory assignment.
     */
    public static Object validateAndClone(Object livingEntity, Object inventory) {
        if (inventory == null || livingEntity == null) {
            return inventory;
        }

        validationCalls++;

        try {
            if (!reflectionInitialized && !reflectionFailed) {
                initializeReflection();
            }

            if (reflectionFailed) {
                return inventory;
            }

            // Only validate for Player entities
            if (!playerClass.isInstance(livingEntity)) {
                return inventory;
            }

            UUID newOwnerUuid = getEntityUuid(livingEntity);
            if (newOwnerUuid == null) {
                return inventory;
            }

            int inventoryId = System.identityHashCode(inventory);
            UUID currentOwner = inventoryOwners.get(inventoryId);

            if (currentOwner != null && !currentOwner.equals(newOwnerUuid)) {
                // SHARED REFERENCE DETECTED!
                sharedReferencesDetected++;

                LOGGER.log(Level.WARNING,
                        "[HyFix #45] INVENTORY SHARING DETECTED!\n" +
                                "  Inventory @{0} owned by {1} being assigned to {2}\n" +
                                "  Forcing deep clone to prevent inventory sync.",
                        new Object[]{
                                Integer.toHexString(inventoryId),
                                currentOwner.toString().substring(0, 8) + "...",
                                newOwnerUuid.toString().substring(0, 8) + "..."
                        }
                );

                Object clonedInventory = cloneInventory(inventory);
                if (clonedInventory != null) {
                    inventoriesCloned++;
                    int clonedId = System.identityHashCode(clonedInventory);
                    inventoryOwners.put(clonedId, newOwnerUuid);
                    registerContainers(clonedInventory, newOwnerUuid);
                    return clonedInventory;
                } else {
                    LOGGER.log(Level.SEVERE,
                            "[HyFix #45] CRITICAL: Failed to clone inventory!");
                }
            } else {
                inventoryOwners.put(inventoryId, newOwnerUuid);
                registerContainers(inventory, newOwnerUuid);
            }

        } catch (Exception e) {
            if (validationCalls < 5) {
                LOGGER.log(Level.WARNING,
                        "[HyFix] Error in inventory validation: " + e.getMessage());
            }
        }

        return inventory;
    }

    private static synchronized void initializeReflection() {
        if (reflectionInitialized || reflectionFailed) {
            return;
        }

        try {
            playerClass = Class.forName("com.hypixel.hytale.server.core.entity.entities.Player");
            getPlayerRefMethod = playerClass.getMethod("getPlayerRef");

            Class<?> playerRefClass = Class.forName("com.hypixel.hytale.server.core.universe.PlayerRef");
            getUuidMethod = playerRefClass.getMethod("getUuid");

            reflectionInitialized = true;
            LOGGER.log(Level.INFO, "[HyFix] InventoryOwnershipGuard initialized");

        } catch (Exception e) {
            reflectionFailed = true;
            LOGGER.log(Level.WARNING,
                    "[HyFix] Failed to initialize InventoryOwnershipGuard: " + e.getMessage());
        }
    }

    private static UUID getEntityUuid(Object livingEntity) {
        try {
            Object playerRef = getPlayerRefMethod.invoke(livingEntity);
            if (playerRef == null) {
                return null;
            }
            return (UUID) getUuidMethod.invoke(playerRef);
        } catch (Exception e) {
            return null;
        }
    }

    private static void registerContainers(Object inventory, UUID ownerUuid) {
        try {
            String[] containerGetters = {"getStorage", "getArmor", "getHotbar", "getUtility", "getTools", "getBackpack"};

            for (String getter : containerGetters) {
                try {
                    Method method = inventory.getClass().getMethod(getter);
                    Object container = method.invoke(inventory);
                    if (container != null) {
                        containerOwners.put(System.identityHashCode(container), ownerUuid);
                    }
                } catch (NoSuchMethodException ignored) {
                }
            }
        } catch (Exception e) {
        }
    }

    private static Object cloneInventory(Object inventory) {
        Object cloned = cloneViaCodec(inventory);
        if (cloned != null) {
            return cloned;
        }

        cloned = cloneViaContainers(inventory);
        if (cloned != null) {
            return cloned;
        }

        LOGGER.log(Level.SEVERE, "[HyFix] All inventory clone methods failed!");
        return null;
    }

    private static Object cloneViaCodec(Object inventory) {
        try {
            Class<?> inventoryClass = inventory.getClass();
            Field codecField = inventoryClass.getField("CODEC");
            Object codec = codecField.get(null);

            Class<?> extraInfoClass = Class.forName("com.hypixel.hytale.codec.ExtraInfo");
            Field threadLocalField = extraInfoClass.getField("THREAD_LOCAL");
            ThreadLocal<?> threadLocal = (ThreadLocal<?>) threadLocalField.get(null);
            Object extraInfo = threadLocal.get();

            if (extraInfo == null) {
                return null;
            }

            Method encodeMethod = findMethod(codec.getClass(), "encode", Object.class, extraInfoClass);
            if (encodeMethod == null) {
                return null;
            }
            Object bsonValue = encodeMethod.invoke(codec, inventory, extraInfo);

            Constructor<?> defaultConstructor = inventoryClass.getDeclaredConstructor();
            defaultConstructor.setAccessible(true);
            Object newInventory = defaultConstructor.newInstance();

            Class<?> bsonValueClass = Class.forName("org.bson.BsonValue");
            Method decodeMethod = findMethod(codec.getClass(), "decode", bsonValueClass, Object.class, extraInfoClass);
            if (decodeMethod == null) {
                return null;
            }
            decodeMethod.invoke(codec, bsonValue, newInventory, extraInfo);

            return newInventory;

        } catch (Exception e) {
            return null;
        }
    }

    private static Object cloneViaContainers(Object inventory) {
        try {
            Class<?> inventoryClass = inventory.getClass();

            Object storage = invokeGetter(inventory, "getStorage");
            Object armor = invokeGetter(inventory, "getArmor");
            Object hotbar = invokeGetter(inventory, "getHotbar");
            Object utility = invokeGetter(inventory, "getUtility");
            Object tools = invokeGetter(inventory, "getTools");
            Object backpack = invokeGetter(inventory, "getBackpack");

            Object clonedStorage = cloneContainer(storage);
            Object clonedArmor = cloneContainer(armor);
            Object clonedHotbar = cloneContainer(hotbar);
            Object clonedUtility = cloneContainer(utility);
            Object clonedTools = cloneContainer(tools);
            Object clonedBackpack = cloneContainer(backpack);

            Class<?> containerClass = Class.forName("com.hypixel.hytale.server.core.inventory.container.ItemContainer");
            Constructor<?> constructor = inventoryClass.getConstructor(
                    containerClass, containerClass, containerClass,
                    containerClass, containerClass, containerClass
            );

            return constructor.newInstance(
                    clonedStorage, clonedArmor, clonedHotbar,
                    clonedUtility, clonedTools, clonedBackpack
            );

        } catch (Exception e) {
            return null;
        }
    }

    private static Object cloneContainer(Object container) {
        if (container == null) {
            return null;
        }
        try {
            Method cloneMethod = container.getClass().getMethod("clone");
            return cloneMethod.invoke(container);
        } catch (Exception e) {
            return container;
        }
    }

    private static Object invokeGetter(Object obj, String methodName) {
        try {
            Method method = obj.getClass().getMethod(methodName);
            return method.invoke(obj);
        } catch (Exception e) {
            return null;
        }
    }

    private static Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
        try {
            return clazz.getMethod(name, paramTypes);
        } catch (NoSuchMethodException e) {
            for (Method m : clazz.getMethods()) {
                if (m.getName().equals(name) && m.getParameterCount() == paramTypes.length) {
                    return m;
                }
            }
            return null;
        }
    }

    public static void onPlayerDisconnect(UUID playerUuid) {
        if (playerUuid == null) {
            return;
        }
        inventoryOwners.entrySet().removeIf(entry -> playerUuid.equals(entry.getValue()));
        containerOwners.entrySet().removeIf(entry -> playerUuid.equals(entry.getValue()));
    }
}
