package com.zhongshu.card.server.core.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.microservice.core.helper.ApplicationContextHolder;
import com.zhongshu.card.client.type.CommonEnum;
import com.zhongshu.card.server.core.domain.base.SuperMain;
import io.swagger.v3.oas.annotations.media.Schema;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.FatalBeanException;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.util.ClassUtils;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.*;
import java.util.*;

public class BeanUtils extends org.springframework.beans.BeanUtils {

    private static Logger log = LoggerFactory.getLogger(BeanUtils.class);


    /**
     * 将对象属性转化为map结合
     */
    public static <T> Map<String, Object> beanToMap(T bean) {
        Map<String, Object> map = new HashMap<>();
        if (bean != null) {
            BeanMap beanMap = BeanMap.create(bean);
            for (Object key : beanMap.keySet()) {
                map.put(key + "", beanMap.get(key));
            }
        }
        return map;
    }

    public static <T> Map<String, Object> beanToMapBlockRef(T bean) {
        Map<String, Object> data = new HashMap<>();
        if (ObjectUtils.isNotEmpty(bean)) {
            Map<String, Object> map = BeanMap.create(bean);
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                List<String> _keys = new ArrayList<>();
                data.put(key, loopValue(value, _keys));
            }
        }
        return data;
    }

    protected static Object loopValue(Object value, List<String> _keys) {
        if (value != null && isCustomObj(value)) {
            if (isCglibObj(value)) {
                return null;
            }
            Map<String, Object> _map = BeanMap.create(value);
            HashMap<String, Object> map = new HashMap<>();
            for (Map.Entry<String, Object> entry : _map.entrySet()) {
                if (!_keys.contains(entry.getKey())) {
                    // prevent custom entity endless loop
                    if (entry.getValue() != null && isCustomObj(entry.getValue())) {
                        _keys.add(entry.getKey());
                    }
                    map.put(entry.getKey(), loopValue(entry.getValue(), _keys));
                }
            }
            return map;
        }
        return value;
    }

    private static boolean isCglibObj(Object value) {
        String name = value.getClass().getName();
        return name == null || name.contains(ClassUtils.CGLIB_CLASS_SEPARATOR);
    }

    /**
     * 判断是否是自定义类型
     *
     * @param value
     * @return
     */
    private static boolean isCustomObj(Object value) {
        if (value == null) {
            return true;
        }
        String _name = value.getClass().getName();
        if (_name.startsWith("java.lang") || _name.startsWith("java.util") || _name.startsWith("java.math") || (value instanceof Enum)) {
            return false;
        }
        return true;
    }

    public static void copyPropertiesWithoutNull(Object source, Object target) {
        if (source == null || target == null) {
            return;
        }
        Class<?> actualEditable = target.getClass();
        PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
        for (PropertyDescriptor targetPd : targetPds) {
            if (targetPd.getWriteMethod() == null) {
                continue;
            }

            try {
                Object value = getValueFromSource(source, targetPd);
                if (value == null) {
                    continue;
                }
                if (!isSimpleProperty(targetPd.getPropertyType()) && !targetPd.getPropertyType().isInterface()) {
                    Object targetPdValue = targetPd.getReadMethod().invoke(target);
                    targetPdValue = AopTargetUtil.getTarget(targetPdValue);
                    if (targetPdValue == null) {
                        targetPdValue = targetPd.getPropertyType().getDeclaredConstructor().newInstance();
                    }
                    copyPropertiesWithoutNull(value, targetPdValue);
                    setValue(target, targetPd, targetPdValue);
                } else if (targetPd.getPropertyType().isAssignableFrom(List.class)) {
                    Class paramCls = getFieldActualType(actualEditable.getDeclaredField(targetPd.getName()));
                    if (paramCls != null) {
                        Object targetPdValue = convertListToList((List) value, paramCls);
                        setValue(target, targetPd, targetPdValue);
                    }
                } else {
                    setValue(target, targetPd, value);
                }
            } catch (Exception ex) {
                throw new FatalBeanException("Could not copy properties from source to target", ex);
            }
        }
    }

    private static void setValue(Object target, PropertyDescriptor targetPd, Object value) throws IllegalAccessException, InvocationTargetException {
        if (value.getClass().isEnum() && targetPd.getPropertyType() == String.class) {
            if (CommonEnum.class.isAssignableFrom(value.getClass())) {
                CommonEnum commonEnum = (CommonEnum) value;
                value = commonEnum.getRemark();
            }
        }
        // 这里判断以下value是否为空
        if ("".equals(value)) {
            value = null;
        }
        if (value != null) {
            Method writeMethod = targetPd.getWriteMethod();
            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                writeMethod.setAccessible(true);
            }
            try {
                writeMethod.invoke(target, value);
            } catch (Exception e) {
                log.error("复制属性出错,target object type:{},target property name:{},type:{},set value type:{}", target.getClass(), targetPd.getName(), targetPd.getPropertyType(), value.getClass());
                throw e;
            }
        }
    }

    public static <T> T mapToBean(Map map, T bean) {
        BeanMap beanMap = BeanMap.create(bean);

        beanMap.putAll(map);
        return bean;

    }

    public static <T> List<T> convertListToList(List bList, TypeReference<List<T>> typeRef) {
        if (bList == null) return null;
        ObjectMapper objectMapper = ApplicationContextHolder.getContext().getBean(ObjectMapper.class);
        List<T> list = null;
        try {
            //objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            String json = objectMapper.writeValueAsString(bList);
            list = objectMapper.readValue(json, typeRef);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;

    }

    public static <T> List<T> convertListToList(List bList, Class targetClass) {
        if (bList == null) {
            return null;
        }
        List<T> list = null;
        ObjectMapper objectMapper = ApplicationContextHolder.getContext().getBean(ObjectMapper.class);
        if (targetClass == Map.class) {
            targetClass = HashMap.class;
        }
        JavaType javaType = objectMapper.getTypeFactory().constructParametricType(List.class, targetClass);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        String json = null;
        try {
            json = objectMapper.writeValueAsString(bList);
            list = objectMapper.readValue(json, javaType);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        return list;
    }

/*    public static <T> List<T> fastConvertListToList(List bList, Class targetClass) {
        if (bList == null) {
            return null;
        }

        String json = JSON.toJSONString(bList, JSONWriter.Feature.FieldBased,JSONWriter.Feature.ReferenceDetection,JSONWriter.Feature.LargeObject);
        List<T> list = JSON.parseArray(json,targetClass);
        return list;
    }*/

    public static <T> List<T> beanConvertListToList(List bList, Class<T> targetClass) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        if (bList == null) {
            return null;
        }
        List<T> list = new ArrayList<>(bList.size());

        for (Object source : bList) {
            if (Map.class.equals(targetClass)) {
                Map map = new HashMap();
                copyPropertiesWithoutNull(source, map);
                list.add((T) map);
            } else if (targetClass.isEnum()) {
                list.add((T) source);
            } else {
                Constructor<T> constructor = targetClass.getDeclaredConstructor();
                if (constructor != null) {
                    T target = constructor.newInstance();
                    copyPropertiesWithoutNull(source, target);
                    list.add(target);
                } else {
                    log.warn("复制对象没有默认构造参数，请检查,复制类:{}", targetClass.getName());
                }
            }
        }
        return list;
    }

    public static String getActualType(Object o, int index) {
        Type clazz = o.getClass().getGenericSuperclass();
        ParameterizedType pt = (ParameterizedType) clazz;
        return pt.getActualTypeArguments()[index].toString();
    }

    public static Class getFieldActualType(Field field) {
        Type genType = field.getGenericType();
        if (genType == null) {
            return null;
        } else if (genType instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) genType;
            if (pt.getActualTypeArguments()[0] instanceof ParameterizedType) {
                return (Class) ((ParameterizedType) pt.getActualTypeArguments()[0]).getRawType();
            } else {
                return (Class) pt.getActualTypeArguments()[0];
            }
        } else {
            return null;
        }
    }

    public static Object getValueFromSource(Object source, PropertyDescriptor targetPd) throws InvocationTargetException, IllegalAccessException {
        Class sourceClass = source.getClass();
        PropertyDescriptor sourcePd = getPropertyDescriptor(sourceClass, targetPd.getName());
        if (sourcePd == null || sourcePd.getReadMethod() == null) {
            Method readMethod = targetPd.getReadMethod();
            if (readMethod != null) {
                String readMethodName = readMethod.getName();
                Method sourceReadMethod = findMethod(sourceClass, readMethodName);
                if (sourceReadMethod != null && Modifier.isPublic(sourceReadMethod.getDeclaringClass().getModifiers())) {
                    if (targetPd.getPropertyType().equals(sourceReadMethod.getReturnType())) {
                        Object value = readMethod.invoke(source);
                        return value;
                    }
                }
            }
            return null;
        }

        boolean changeAccess = false;
        Method readMethod = sourcePd.getReadMethod();
        if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
            readMethod.setAccessible(true);
            changeAccess = true;
        }
        Object value = readMethod.invoke(source);
        if (changeAccess) {
            readMethod.setAccessible(false);
        }
        return value;
    }

    /**
     * 比较两个实体属性值，返回一个boolean,true则表时两个对象中的属性值无差异
     *
     * @param oldObject 进行属性比较的对象1
     * @param newObject 进行属性比较的对象2
     * @return 属性差异比较结果boolean
     */
    public static boolean compareObject(Object oldObject, Object newObject) {
        Map<String, Map<String, Object>> resultMap = compareFields(oldObject, newObject);

        if (resultMap.size() > 0) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * 比较两个实体属性值，返回一个map以有差异的属性名为key，value为一个Map分别存oldObject,newObject此属性名的值
     *
     * @param oldObject 进行属性比较的对象1
     * @param newObject 进行属性比较的对象2
     * @return 属性差异比较结果map
     */
    public static Map<String, Map<String, Object>> compareFields(Object oldObject, Object newObject) {
        Map<String, Map<String, Object>> map = new HashMap<>();

        try {
            /**
             * 只有两个对象都是同一类型的才有可比性
             */
            if (oldObject.getClass() == newObject.getClass()) {
                Class clazz = oldObject.getClass();

                //获取object的所有属性
                PropertyDescriptor[] pds = Introspector.getBeanInfo(clazz, Object.class).getPropertyDescriptors();

                for (PropertyDescriptor pd : pds) {
                    //遍历获取属性名
                    String name = pd.getName();
                    String description = name;

                    Field field = null;
                    try {
                        field = clazz.getDeclaredField(name);
                    } catch (NoSuchFieldException nsee) {
                        try {
                            field = clazz.getSuperclass().getDeclaredField(name);
                        } catch (NoSuchFieldException nse) {

                        }
                    }

                    if (field != null) {
                        if (field.isAnnotationPresent(Schema.class)) {
                            Schema apiModelProperty = field.getAnnotation(Schema.class);
                            description = apiModelProperty.description();
                        }
                    }
                    //获取属性的get方法
                    Method readMethod = pd.getReadMethod();

                    // 在oldObject上调用get方法等同于获得oldObject的属性值
                    Object oldValue = readMethod.invoke(oldObject);
                    // 在newObject上调用get方法等同于获得newObject的属性值
                    Object newValue = readMethod.invoke(newObject);

                    if (oldValue == null && newValue == null) {
                        continue;
                    }
                    if (oldValue instanceof List || newValue instanceof List) {
                        continue;
                    }

                    if (oldValue == null && newValue != null) {
                        Map<String, Object> valueMap = new HashMap<>(2);
                        valueMap.put("oldValue", oldValue);
                        valueMap.put("newValue", newValue);
                        map.put(description, valueMap);
                    } else if (oldValue instanceof SuperMain && newValue instanceof SuperMain) {
                        SuperMain oldSp = (SuperMain) oldValue;
                        SuperMain newSp = (SuperMain) newValue;
                        if (!Objects.equals(oldSp.getId(), newSp.getId())) {
                            Map<String, Object> valueMap = new HashMap<>(2);
                            valueMap.put("oldValue", oldValue);
                            valueMap.put("newValue", newValue);
                            map.put(description, valueMap);
                        }
                    } else if (!Objects.equals(oldValue, newValue)) {// 比较这两个值是否相等,不等就可以放入map了
                        Map<String, Object> valueMap = new HashMap<>(2);
                        valueMap.put("oldValue", oldValue);
                        valueMap.put("newValue", newValue);
                        map.put(description, valueMap);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return map;
    }
}
