package cn.qaiu.vx.core.util;

import javassist.*;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.reflections.Reflections;
import org.reflections.scanners.MemberUsageScanner;
import org.reflections.scanners.MethodParameterNamesScanner;
import org.reflections.scanners.Scanners;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.text.ParseException;
import java.util.*;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static cn.qaiu.vx.core.util.ConfigConstant.BASE_LOCATIONS;

/**
 * 基于org.reflection和javassist的反射工具包
 * 通过包扫描实现路由地址的注解映射
 * <br>Create date 2021-05-07 10:26:54
 *
 * @author <a href="https://qaiu.top">QAIU</a>
 */
public final class ReflectionUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionUtil.class);

    // 缓存Reflections实例，避免重复扫描（每次扫描约35K+值，耗时1-3秒，占用大量内存）
    private static final Map<String, Reflections> REFLECTIONS_CACHE = new java.util.concurrent.ConcurrentHashMap<>();

    // 预编译的类型匹配正则，避免每次请求重新编译
    private static final Pattern BASIC_TYPE_PATTERN = Pattern.compile(
            "^java\\.lang\\.((Boolean)|(Character)|(Byte)|(Short)|(Integer)|(Long)|(Float)|(Double)|(String))$");
    private static final Pattern BASIC_TYPE_ARRAY_PATTERN = Pattern.compile(
            "^(boolean|char|byte|short|int|long|float|double|String)\\[]$");

    /**
     * 以默认配置的基础包路径获取反射器
     *
     * @return Reflections object
     */
    public static Reflections getReflections() {
        return getReflections(SharedDataUtil.getStringForCustomConfig(BASE_LOCATIONS));
    }

    /**
     * 获取反射器（带缓存）
     *
     * @param packageAddress Package address String
     * @return Reflections object
     */
    public static Reflections getReflections(String packageAddress) {
        return REFLECTIONS_CACHE.computeIfAbsent(packageAddress, key -> {
            List<String> packageAddressList;
            if (key.contains(",")) {
                packageAddressList = Arrays.asList(key.split(","));
            } else if (key.contains(";")) {
                packageAddressList = Arrays.asList(key.split(";"));
            } else {
                packageAddressList = Collections.singletonList(key);
            }
            return createReflections(packageAddressList);
        });
    }

    /**
     * 获取反射器（带缓存）
     *
     * @param packageAddresses Package address List
     * @return Reflections object
     */
    public static Reflections getReflections(List<String> packageAddresses) {
        String cacheKey = String.join(",", packageAddresses);
        return REFLECTIONS_CACHE.computeIfAbsent(cacheKey, key -> createReflections(packageAddresses));
    }

    private static Reflections createReflections(List<String> packageAddresses) {
        ConfigurationBuilder configurationBuilder = new ConfigurationBuilder()
                .addClassLoaders(Thread.currentThread().getContextClassLoader())
                .forPackages(packageAddresses.toArray(new String[0]))
                .setScanners(
                        Scanners.SubTypes.filterResultsBy(s -> true), //允许getAllTypes获取所有Object的子类, 不设置为false则 getAllTypes
                        // 会报错.默认为true.
                        new MethodParameterNamesScanner(), //设置方法参数名称 扫描器,否则调用getConstructorParamNames 会报错
                        Scanners.MethodsAnnotated, //设置方法注解 扫描器, 否则getConstructorsAnnotatedWith,getMethodsAnnotatedWith 会报错
                        new MemberUsageScanner(),  //设置 member 扫描器,否则 getMethodUsage 会报错
                        Scanners.TypesAnnotated //设置类注解 扫描器 ,否则 getTypesAnnotatedWith 会报错
                );
        return new Reflections(configurationBuilder);
    }

    /**
     * 获取指定类指定方法的参数名和类型列表(忽略重载方法)
     *
     * @param method 方法名(不考虑重载)
     * @return 参数名类型map
     * @apiNote ..
     */
    public static Map<String, Pair<Annotation[], CtClass>> getMethodParameter(Method method) {
        Map<String, Pair<Annotation[], CtClass>> paramMap = new LinkedHashMap<>();
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.get(method.getDeclaringClass().getName());
            CtMethod cm = ctClass.getDeclaredMethod(method.getName());
            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            MethodInfo methodInfo = cm.getMethodInfo();
            CtClass[] parameterTypes = cm.getParameterTypes();
            CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
            LocalVariableAttribute attr =
                    (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);

            boolean flag = true;
            boolean flag2 = cm.getModifiers() - 1 != AccessFlag.STATIC;
            for (int j = 0, k = 0; j < parameterTypes.length + k; j++) {
                // 注意这里 只能从tableLength处获取 目前还没发现问题
                String name = attr.variableName(j);
                if (!"this".equals(name) && flag && flag2) {
                    k++;
                    continue;
                }
                flag = false;
                paramMap.put(attr.variableName(j + (flag2 ? 1 : 0)), Pair.of(parameterAnnotations[j - k],
                        parameterTypes[j - k]));
            }
        } catch (NotFoundException e) {
            LOGGER.error("获取方法参数失败", e);
        }
        return paramMap;
    }

    /**
     * 类型转换: 字符串转对应类型
     *
     * @param ctClass 目标类: javassist的ctClass类对象
     * @param value   字符串值
     * @param fmt     日期格式(如果value是日期的话这个字段将有用,否则置为null或空字符串)
     * @return 基本类型或目标对象
     */
    public static Object conversion(CtClass ctClass, String value, String fmt) {
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        if (StringUtils.isEmpty(fmt)) {
            fmt = "yyyy-MM-dd";
        }
        String name = ctClass.getName();
        if (ctClass.isArray()) {
            name = ctClass.getName().substring(0, ctClass.getName().length() - 2);
        }
        switch (name) {
            case "java.lang.Boolean":
            case "boolean":
                return Boolean.valueOf(value);
            case "java.lang.Character":
            case "char":
                return value.charAt(0);
            case "java.lang.Byte":
            case "byte":
                return Byte.valueOf(value);
            case "java.lang.Short":
            case "short":
                return Short.valueOf(value);
            case "java.lang.Integer":
            case "int":
                return Integer.valueOf(value);
            case "java.lang.Long":
            case "long":
                return Long.valueOf(value);
            case "java.lang.Float":
            case "float":
                return Float.valueOf(value);
            case "java.lang.Double":
            case "double":
                return Double.valueOf(value);
            case "java.lang.String":
                return value;
            case "java.util.Date":
                try {
                    return DateUtils.parseDate(value, fmt);
                } catch (ParseException e) {
                    LOGGER.error("日期解析失败: {}", value, e);
                    throw new RuntimeException("无法将格式化日期");
                }
            default:
                throw new RuntimeException("无法将String类型" + value + "转为[" + name + "]");
        }
    }

    /**
     * 数组类型的转换
     *
     * @param ctClass 目标类: javassist的ctClass类对象
     * @param value   字符串表示的数组(逗号分隔符)
     * @return Array
     */
    public static Object conversionArray(CtClass ctClass, String value) {
        if (!isBasicTypeArray(ctClass)) throw new RuntimeException("无法解析数组");
        String[] strArr = value.split(",");
        List<Object> obj = new ArrayList<>();
        Arrays.stream(strArr).forEach(v -> obj.add(conversion(ctClass, v, null)));

        try {
            // 暂时这么处理
            String name = "[" + ((CtPrimitiveType) ctClass.getComponentType()).getDescriptor();
            Class<?> cls = Class.forName(name).getComponentType();
            Object arr = Array.newInstance(cls, obj.size());//初始化对应类型的数组

            for (int i = 0; i < obj.size(); i++) {
                Array.set(arr, i, obj.get(i));
            }
            return arr;
        } catch (Exception e) {
            LOGGER.error("数组类型转换失败: {}", value, e);
        }
        return null;
    }

    /**
     * 判断是否是基本类型 8种原始类型和包装类以及String类型 返回true
     *
     * @return bool
     */
    public static boolean isBasicType(CtClass ctClass) {
        if (ctClass.isPrimitive() || "java.util.Date".equals(ctClass.getName())) {
            return true;
        }
        return BASIC_TYPE_PATTERN.matcher(ctClass.getName()).matches();
    }

    /**
     * 判断是否是基本类型数组 8种原始数组类型和String数组 返回true
     *
     * @return bool
     */
    public static boolean isBasicTypeArray(CtClass ctClass) {
        if (!ctClass.isArray()) {
            return false;
        } else return BASIC_TYPE_ARRAY_PATTERN.matcher(ctClass.getName()).matches();
    }

    /**
     * 反射通过无参构造创建对象
     *
     * @param handler 类对象
     * @return 目标对象
     * @throws NoSuchMethodException     NoSuchMethodException
     * @throws InvocationTargetException InvocationTargetException
     * @throws InstantiationException    InstantiationException
     * @throws IllegalAccessException    IllegalAccessException
     */
    public static <T> T newWithNoParam(Class<T> handler) throws NoSuchMethodException, InvocationTargetException,
            InstantiationException, IllegalAccessException {
        return handler.getConstructor().newInstance();
    }

    /**
     * 反射调用有参方法
     *
     * @param method    方法类对象
     * @param instance  方法所在的对象实例
     * @param arguments 方法参数
     * @return 方法返回值
     * @throws Throwable Throwable
     */
    public static Object invokeWithArguments(Method method, Object instance, Object... arguments) throws Throwable {
        return MethodHandles.lookup().unreflect(method).bindTo(instance).invokeWithArguments(arguments);
    }

    /**
     * 反射调用无参方法
     *
     * @param method   方法类对象
     * @param instance 方法所在的对象实例
     * @return 方法返回值
     * @throws Throwable Throwable
     */
    public static Object invoke(Method method, Object instance) throws Throwable {
        return MethodHandles.lookup().unreflect(method).bindTo(instance).invoke();
    }

}
