package org.allaymc.server.utils;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.reflect.ClassPath;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * @author daoge_cmd
 */
@UtilityClass
public class ReflectionUtils {
    @SneakyThrows
    public static List<String> getAllClasses(String packageName) {
        var classPath = ClassPath.from(ClassLoader.getSystemClassLoader());
        return classPath.getTopLevelClassesRecursive(packageName).stream()
                .map(ClassPath.ClassInfo::getName)
                .toList();
    }

    public static List<Field> getAllFields(Class<?> clazz) {
        List<Field> fields = new ArrayList<>();

        var current = clazz;
        while (current != null) {
            Collections.addAll(fields, current.getDeclaredFields());
            current = current.getSuperclass();
        }

        return fields;
    }

    public static List<Method> getAllMethods(Class<?> clazz) {
        Map<String, Method> methods = new HashMap<>();
        collectAllMethods(clazz, methods, new HashSet<>());

        return new ArrayList<>(methods.values());
    }

    private static void collectAllMethods(Class<?> type, Map<String, Method> methods, Set<Class<?>> visited) {
        if (type == null || !visited.add(type)) {
            return;
        }

        for (var method : type.getDeclaredMethods()) {
            var signature = buildMethodSignature(method);
            methods.putIfAbsent(signature, method);
        }

        for (var iface : type.getInterfaces()) {
            collectAllMethods(iface, methods, visited);
        }

        collectAllMethods(type.getSuperclass(), methods, visited);
    }

    public static String buildMethodSignature(Method method) {
        var signature = method.getName() + Arrays.toString(method.getParameterTypes());

        if (method.getDeclaringClass().isInterface() && Modifier.isPrivate(method.getModifiers())) {
            return method.getDeclaringClass().getName() + "#" + signature;
        }

        return signature;
    }

    public static List<Method> getAllStaticVoidParameterlessMethods(Class<?> clazz) {
        return Arrays.stream(clazz.getMethods()).filter(method -> {
            // return if not a static method
            if (!Modifier.isStatic(method.getModifiers())) return false;
            // return if not a void method
            if (method.getReturnType() != void.class) return false;
            return method.getParameterCount() == 0;
        }).toList();
    }

    public static <T, U> BiMap<T, U> mapStaticFields(Class<?> classA, Class<?> classB) {
        Map<String, T> staticInstancesA = new java.util.HashMap<>();

        for (var field : classA.getDeclaredFields()) {
            if (Modifier.isStatic(field.getModifiers()) &&
                Modifier.isPublic(field.getModifiers())) {
                field.setAccessible(true);
                try {
                    @SuppressWarnings("unchecked")
                    T instance = (T) field.get(null);
                    if (instance != null) {
                        staticInstancesA.put(field.getName(), instance);
                    }
                } catch (IllegalAccessException ignored) {
                }
            }
        }

        BiMap<T, U> result = HashBiMap.create();

        for (var field : classB.getDeclaredFields()) {
            if (Modifier.isStatic(field.getModifiers()) &&
                Modifier.isPublic(field.getModifiers())) {
                field.setAccessible(true);
                try {
                    @SuppressWarnings("unchecked")
                    U instance = (U) field.get(null);
                    if (instance != null) {
                        T match = staticInstancesA.get(field.getName());
                        if (match != null) {
                            result.put(match, instance);
                        }
                    }
                } catch (IllegalAccessException ignored) {
                }
            }
        }

        return result;
    }
}
