package com.heyu.api.data.utils;

import com.heyu.api.data.constants.ApiConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ClassUtils;

import org.springframework.beans.BeanUtils;

import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.*;

@Slf4j
public class SanUtils {

    public static <T, R, V> Map<R, V> groupByFiled2Map(List<V> values, LBiFunction0<T, R> function) {
        try {
            if (CollectionUtils.isEmpty(values)) {
                return new HashMap<>();
            }

            Map<R, V> map = new HashMap<>();
            Method method = getMethod(function);
            for (V value : values) {
                map.put((R) method.invoke(value), value);
            }
            return map;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new HashMap<>();
    }


    public static <T, P, K, V> Map<K, V> groupByFiledFiled2Map(List<P> values, LBiFunction0<T, K> keyFunction, LBiFunction0<T, V> valueFunction) {
        try {
            if (CollectionUtils.isEmpty(values)) {
                return new HashMap<>();
            }
            Map<K, V> map = new HashMap<>();

            Method keyMethod = getMethod(keyFunction);
            Method valueMethod = getMethod(valueFunction);
            for (P value : values) {
                map.put((K) keyMethod.invoke(value), (V) valueMethod.invoke(value));
            }
            return map;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new HashMap<>();
    }


    public static <T, R, V> List<R> list2listFilterNull(List<V> values, LBiFunction0<T, R> function) {

        List<R> list = list2FieldList(values, function);

        List<R> result = new ArrayList<>();

        if (CollectionUtils.isNotEmpty(list)) {
            for (R r : list) {
                if (r != null) {
                    result.add(r);
                }
            }
        }

        return result;
    }


    public static <T, R, V> List<R> setNull(List<V> values, LBiFunction0<T, R>... functions) {
        List<R> list = new ArrayList<>();
        try {
            if (CollectionUtils.isEmpty(values)) {
                return list;
            }
            for (LBiFunction0<T, R> function : functions) {
                Method method = getSetMethod(function);
                for (V value : values) {
                    Object[] objects = new Object[]{null};
                    method.invoke(value, objects);
                }
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }


    public static <T, V, R> List<R> copy2List(List<V> values, Class clazz) {
        List<R> list = new ArrayList<>();
        if (CollectionUtils.isEmpty(values)) {
            return new ArrayList<>();
        }
        try {
            for (V value : values) {
                Object o = clazz.newInstance();
                BeanUtils.copyProperties(value, o);
                list.add((R) o);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return list;
    }

    public static <T, V, R> List<R> baseTypeConvert(List<V> values, Class clazz) {
        List<R> list = new ArrayList<>();
        if (CollectionUtils.isEmpty(values)) {
            return new ArrayList<>();
        }
        try {
            for (V value : values) {
                list.add((R) parseBasicTypeWrapper(clazz, value));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return list;
    }




    public static <T, R, V> List<R> list2FieldList(List<V> values, LBiFunction0<T, R> function) {
        List<R> list = new ArrayList<>();
        try {
            if (CollectionUtils.isEmpty(values)) {
                return list;

            }
            Method method = getMethod(function);
            for (V value : values) {
                list.add((R) method.invoke(value));
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    public static <T, R, V> List<R> getDistinctFiledList(List<V> values, LBiFunction0<T, R> function) {
        List<R> list = new ArrayList<>();
        List<R> set = new ArrayList<>();
        try {
            if (CollectionUtils.isEmpty(values)) {
                return list;
            }
            Method method = getMethod(function);
            for (V value : values) {
                set.add((R) method.invoke(value));
            }


            for (R r : set) {
                list.add(r);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    public static void getClassDeclaredMethods(Class entityType, List<Method> methods) {
        if (entityType == Object.class) {
            return;
        }
        Method method[] = entityType.getDeclaredMethods();
        for (Method method1 : method) {
            methods.add(method1);
        }
        getClassDeclaredMethods(entityType.getSuperclass(), methods);
    }

    public static <T, R, V> Map<R, List<V>> groupByFiled2MapList(List<V> values, LBiFunction0<T, R> function) {
        if (CollectionUtils.isEmpty(values)) {
            return new LinkedHashMap<>();
        }
        Map<R, List<V>> listmap = new LinkedHashMap<>();
        Method method = getMethod(function);
        try {
            for (int i = 0; i < values.size(); i++) {
                V v = values.get(i);
                R r = (R) method.invoke(v);
                List<V> list = listmap.get(r);
                if (list == null) {
                    list = new ArrayList<>();
                }
                list.add(v);
                listmap.put(r, list);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return listmap;
    }


    public static <T, R, V> R findAnyNotNull(List<V> values, LBiFunction0<T, R> function) {
        try {
            if (CollectionUtils.isEmpty(values)) {
                return null;

            }

            Method method = getMethod(function);
            for (V value : values) {
                R r = (R) method.invoke(value);
                if (r != null) {
                    return r;
                }
            }

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

    public static <T, R, V> R sum(List<V> values, LBiFunction0<T, R> function) {
        BigDecimal sum = BigDecimal.ZERO;
        Class<?> clazz = null;
        try {
            if (CollectionUtils.isEmpty(values)) {
                return null;
            }
            Method method = getMethod(function);
            clazz = method.getReturnType();
            for (V value : values) {
                R r = (R) method.invoke(value);
                if (r != null) {
                    sum = NumberUtils.objToBigDecimalDefault(r, BigDecimal.ZERO).add(sum);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return (R) parseBasicTypeWrapper(clazz, sum);
    }


    public static <T, P, V, R> Map<P, R> groupAndSum(LBiFunction0<T, P> groupKeyFunction, List<V> values, LBiFunction0<T, R> function) {
        Map<P, MethodSumDTO> map = new LinkedHashMap<>();
        try {
            if (CollectionUtils.isEmpty(values)) {
                return new LinkedHashMap<>();
            }
            for (V value : values) {
                Method groupKeyMethod = getMethod(groupKeyFunction);
                P p = (P) groupKeyMethod.invoke(value);

                Method method = getMethod(function);
                R r = (R) method.invoke(value);

                if (r != null) {
                    BigDecimal bigDecimalR = NumberUtils.objToBigDecimalDefault(r, BigDecimal.ZERO);
                    MethodSumDTO methodSumDTO = map.get(p);
                    if (methodSumDTO == null) {
                        methodSumDTO = new MethodSumDTO();
                    }
                    methodSumDTO.getItemList().add(bigDecimalR);
                    methodSumDTO.setMethod(method);
                    map.put(p, methodSumDTO);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        Map<P, R> result = new LinkedHashMap<>();
        for (Map.Entry<P, MethodSumDTO> entry : map.entrySet()) {

            MethodSumDTO methodSumDTO = entry.getValue();
            if (methodSumDTO.getItemList().size() > 0) {
                BigDecimal sum = BigDecimal.ZERO;
                for (BigDecimal bigDecimal : methodSumDTO.getItemList()) {
                    sum = sum.add(bigDecimal);
                }
                Class clazz = methodSumDTO.getMethod().getReturnType();
                result.put(entry.getKey(), (R) parseBasicTypeWrapper(clazz, sum));
            }
        }
        return result;
    }


    public static <T, V, R> List<R> getFieldList(List<V> values, LBiFunction0<T, R> tlBiFunction0) {
        List<R> list = new ArrayList<>();
        if (CollectionUtils.isEmpty(values)) {

            return list;
        }
        Method method = getMethod(tlBiFunction0);

        for (V value : values) {
            try {
                Object r = method.invoke(value);
                if (r != null) {
                    list.add((R) r);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        return list;
    }

    public static <T, V, R> List<V> groupMerge2List(LBiFunction0<T, R> groupByFunction, List<V> values, LBiFunction0<T, ? extends Object>... functionArray) {
        List<V> vList = new ArrayList<>();
        Map<R, V> map = groupMerge2Map(groupByFunction, values, functionArray);
        if (map != null) {
            for (Map.Entry<R, V> entry : map.entrySet()) {
                vList.add(entry.getValue());
            }
        }
        return vList;
    }

    public static <T, V, R> Map<R, V> groupMerge2Map(LBiFunction0<T, R> groupByFunction, List<V> values, LBiFunction0<T, ? extends Object>... functionArray) {
        Map<R, List<V>> mapData = new LinkedHashMap<>();
        if (CollectionUtils.isEmpty(values)) {
            return new LinkedHashMap<>();
        }
        for (V value : values) {
            try {
                Method method = getMethod(groupByFunction);
                Object object = method.invoke(value);
                R r = null;
                if (object != null) {
                    r = (R) object;
                }
                List<V> list = mapData.get(r);
                if (list == null) {
                    list = new ArrayList<>();
                }
                list.add(value);
                mapData.put(r, list);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        Map<R, V> result = new LinkedHashMap<>();
        if (!mapData.isEmpty()) {
            for (Map.Entry<R, List<V>> map : mapData.entrySet()) {
                List<V> list = map.getValue();
                if (com.heyu.api.data.utils.CollectionUtils.isNotEmpty(list)) {
                    V v = merge(list, functionArray);
                    result.put(map.getKey(), v);
                }
            }
        }
        return result;
    }


    public static <T, V> V merge(List<V> values, LBiFunction0<T, ? extends Object>... functionArray) {
        try {
            if (CollectionUtils.isEmpty(values)) {
                return null;
            }
            V v = values.get(0);
            for (LBiFunction0<T, ? extends Object> tlBiFunction0 : functionArray) {
                Method method = getMethod(tlBiFunction0);
                Class<?> returnClazz = method.getReturnType();
                BigDecimal sum = new BigDecimal(0);
                StringBuffer sb = new StringBuffer();
                List<Object> list = new ArrayList<>();
                if (returnClazz == String.class) {
                    for (V value : values) {
                        Object r = method.invoke(value);
                        sb.append(r).append("_");
                    }
                } else if (isNumberTypes(returnClazz)) {
                    for (V value : values) {
                        Object r = method.invoke(value);
                        if (r != null) {
                            sum = NumberUtils.objToBigDecimalDefault(r, BigDecimal.ZERO).add(sum);
                        }
                    }
                }
                for (V value : values) {
                    Object r = method.invoke(value);
                    list.add(r);
                }
                String getMethodName = method.getName();
                String setMethodName = ApiConstants.set + getMethodName.substring(3);

                String setMethodNameList = ApiConstants.set + ApiConstants.Merge + getMethodName.substring(3) + ApiConstants.List;

                Method setMehtod = null;

                List<Method> methods = new ArrayList<>();
                getAllDeclaredMethods(methods, v.getClass());
                for (Method m : methods) {
                    if (m.getName().equals(setMethodNameList)) {
                        setMehtod = m;
                        m.invoke(v, new Object[]{list});
                    }
                }

                if (setMehtod == null) {
                    for (Method m : methods) {
                        if (m.getName().equals(setMethodName)) {
                            if (returnClazz == String.class) {
                                m.invoke(v, new Object[]{sb});
                            } else if (isNumberTypes(returnClazz)) {
                                Object obj = parseBasicTypeWrapper(returnClazz, sum);
                                m.invoke(v, new Object[]{obj});
                            }
                        }
                    }
                }
            }
            return v;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void getAllDeclaredMethods(List<Method> list, Class<?> clazz) {
        if (clazz == Object.class) {
            return;
        }
        Method methods[] = clazz.getDeclaredMethods();
        for (Method m : methods) {
            list.add(m);
        }
        getAllDeclaredMethods(list, clazz.getSuperclass());
    }


    private static <T, R> Method getSetMethod(LBiFunction0<T, R> function) {
        try {
            Method method = function.getClass().getDeclaredMethods()[1];
            method.setAccessible(true);
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function);
            String methodName = serializedLambda.getImplMethodName();
            String className = serializedLambda.getImplClass().replace("/", ".");
            Class<?> clazz = Class.forName(className);
            methodName = "set" + methodName.substring(3);
            List<Method> methodList = new ArrayList<>();
            getAllDeclaredMethods(methodList, clazz);
            for (Method m : methodList) {
                if (m.getName().equals(methodName)) {
                    return m;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static <T, R> Method getMethod(LBiFunction0<T, R> function) {
        try {
            Method method = function.getClass().getDeclaredMethods()[1];
            method.setAccessible(true);
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function);
            String methodName = serializedLambda.getImplMethodName();
            String className = serializedLambda.getImplClass().replace("/", ".");
            Class<?> clazz = Class.forName(className);
            List<Method> methodList = new ArrayList<>();
            getAllDeclaredMethods(methodList, clazz);
            for (Method m : methodList) {
                if (m.getName().equals(methodName)) {
                    return m;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    private static <T, R> List<Method> getMethods(LBiFunction0<T, R> function) {
        try {
            Method method = function.getClass().getDeclaredMethods()[1];
            method.setAccessible(true);
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function);
            String methodName = serializedLambda.getImplMethodName();
            String className = serializedLambda.getImplClass().replace("/", ".");
            Class<?> clazz = Class.forName(className);

            List<Method> list = new ArrayList<>();
            Method methods[] = clazz.getDeclaredMethods();
            for (Method m : methods) {
                if (m.getName().equals(methodName)) {
                    list.add(m);
                }
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    private static Method getRealMethod(List<Method> methods, Class paramClazz) {
        for (Method method : methods) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes != null
                    && parameterTypes.length == 1
                    && ClassUtils.isAssignable(paramClazz, parameterTypes[0])) {
                return method;
            }
        }
        return null;
    }


    public static <T, R, V> List<R> convert(List<V> list, LBiFunction0<T, R> function) {
        List<Method> methods = getMethods(function);
        List result = new ArrayList<>();
        for (V v : list) {
            try {
                Class clazz = v.getClass();
                Method method = getRealMethod(methods, clazz);
                int modifiers = method.getModifiers();
                Object object = null;
                if (Modifier.isStatic(modifiers)) {
                    object = method.invoke(null, v);
                } else {
                    object = method.invoke(v);
                }
                result.add(object);
            } catch (Exception e) {
                log.error("convert exception", e);
            }
        }
        return result;
    }


    public static <R> List<R> asList(R r) {
        List<R> result = new ArrayList<>();
        if (r != null) {
            result.add(r);
        }
        return result;
    }

    /**
     * 基本类型包装类解析
     */
    public static Object parseBasicTypeWrapper(Class<?> parameterType, Object value) {
        if (Number.class.isAssignableFrom(parameterType)) {
            if (parameterType == Integer.class || parameterType == int.class) {
                return NumberUtils.objToIntDefault(value, null);
            } else if (parameterType == Short.class || parameterType == short.class) {
                return NumberUtils.objToShortDefault(value, null);
            } else if (parameterType == Long.class || parameterType == long.class) {
                return NumberUtils.objToLongDefault(value, null);
            } else if (parameterType == Float.class || parameterType == float.class) {
                return NumberUtils.objToFloatDefault(value, null);
            } else if (parameterType == Double.class || parameterType == double.class) {
                return NumberUtils.objToDoubleDefault(value, null);
            } else if (parameterType == Byte.class || parameterType == byte.class) {
                return NumberUtils.objToByteDefault(value, null);
            } else if (parameterType == BigDecimal.class) {
                return NumberUtils.objToBigDecimalDefault(value, null);
            }
        } else if (parameterType == Boolean.class || parameterType == boolean.class) {
            return NumberUtils.objToBooleanDefault(value, null);
        } else if (parameterType == Character.class || parameterType == char.class) {
            return NumberUtils.objToCharacterDefault(value, null);
        } else if (parameterType == String.class) {
            return value != null ? value.toString() : null;
        } else if (parameterType == Date.class && value instanceof Date) {
            return value;
        } else if (parameterType == Date.class && value instanceof String) {
            Object target = DateUtils.parseStr2Date((String) value, YYYY_MM_DD_HH_MM_SS);
            if (target == null) {
                target = DateUtils.parseStr2Date((String) value, YYYY_MM_DD);
                if (target == null) {
                    target = DateUtils.parseStr2Date((String) value, YYYY);
                    if (target == null) {
                        target = DateUtils.parseStr2Date((String) value, YYYY_MM);
                        if (target == null) {
                            target = DateUtils.parseStr2Date((String) value, YYYY_MM_DD_HH);
                            if (target == null) {
                                target = DateUtils.parseStr2Date((String) value, YYYY_MM_DD_HH_MM);
                            }
                        }
                    }
                }
            }
            return target;
        }
        return null;
    }

    public static String YYYY = "yyyy";
    public static String YYYY_MM = "yyyy-MM";
    public static String YYYY_MM_DD = "yyyy-MM-dd";
    public static String YYYY_MM_DD_HH = "yyyy-MM-dd HH";
    public static String YYYY_MM_DD_HH_MM = "yyyy-MM-dd HH:mm";
    public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";

    private static final List<Class<?>> primitiveTypes = new ArrayList<>(32);
    private static final List<Class<?>> isNumberTypes = new ArrayList<>(32);

    static {
        primitiveTypes.add(Boolean.class);
        primitiveTypes.add(Byte.class);
        primitiveTypes.add(Character.class);
        primitiveTypes.add(Double.class);
        primitiveTypes.add(Float.class);
        primitiveTypes.add(Integer.class);
        primitiveTypes.add(Long.class);
        primitiveTypes.add(Short.class);
        primitiveTypes.add(BigDecimal.class);

        primitiveTypes.add(boolean.class);
        primitiveTypes.add(byte.class);
        primitiveTypes.add(char.class);
        primitiveTypes.add(double.class);
        primitiveTypes.add(float.class);
        primitiveTypes.add(int.class);
        primitiveTypes.add(long.class);
        primitiveTypes.add(short.class);
        primitiveTypes.add(String.class);
        primitiveTypes.add(Date.class);


        isNumberTypes.add(Byte.class);
        isNumberTypes.add(Double.class);
        isNumberTypes.add(Float.class);
        isNumberTypes.add(Integer.class);
        isNumberTypes.add(Long.class);
        isNumberTypes.add(Short.class);
        isNumberTypes.add(BigDecimal.class);


        isNumberTypes.add(byte.class);
        isNumberTypes.add(char.class);
        isNumberTypes.add(double.class);
        isNumberTypes.add(float.class);
        isNumberTypes.add(int.class);
        isNumberTypes.add(long.class);
        isNumberTypes.add(short.class);

    }

    public static boolean isBasicDataTypes(Class clazz) {
        return primitiveTypes.contains(clazz) ? true : false;
    }

    public static boolean isNumberTypes(Class clazz) {
        return isNumberTypes.contains(clazz) ? true : false;
    }


    public static <T> T get(Future<T> t) {

        return get(t, 3000L);

    }


    public static <T> T get(Future<T> t, long timeout) {
        if (t != null) {
            try {
                return t.get(timeout, TimeUnit.MILLISECONDS);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
        return null;
    }


    private static ExecutorService threadPool;


    static {
        threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }


}
