package com.ruoyi.framework.interceptor;


import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.common.annotation.DataChange;
import com.ruoyi.framework.web.domain.server.CompareResult;
import com.ruoyi.system.domain.LogInfo;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class DataChangeAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(DataChangeAspect.class);

    public Object work(Object target, Class<?> targetClass, Method method, Object[] args,
                       final InvocationCallback invocation) throws Throwable {
        LogInfo logInfo = this.getLog(target, targetClass, method, args);
        long startTime = System.currentTimeMillis();
        Object result = null;
        try {
            result = invocation.proceedWithLog();
        } catch (Throwable t) {
            if (logInfo != null) {
                logInfo.setException(ExceptionUtils.getRootCause(t).getMessage());
            }
        } finally {
            long timeout = System.currentTimeMillis() - startTime;
            logInfo.setTimeout(timeout);
        }
        LOGGER.info("[easy-log]{}:\n{}", logInfo.getMethod(), JSON.toJSONString(logInfo));
        return result;
    }

    private LogInfo getLog(Object target, Class<?> targetClass, Method method, Object[] args) {
        LogInfo logInfo = new LogInfo();
        String methodName = ((Class) targetClass.getGenericInterfaces()[0]).getSimpleName() + "." + method.getName();
        logInfo.setMethod(methodName);
        if (methodName.contains("update") || methodName.contains("delete") || methodName.contains("insert")) {
            Serializable id = this.getPrimaryKey(args[0]);
            //数据更新逻辑
            if (methodName.contains("update")) {
                try {
                    BaseMapper baseMapper = (BaseMapper) target;
                    Object result = baseMapper.selectById(id.toString());
                    List<CompareResult> compareResultList = this.getDiff(result, args[0]);
                    logInfo.setDataSnapshot(JSON.toJSONString(compareResultList));
                    LOGGER.debug("数据变化:" + JSON.toJSONString(compareResultList));
                    for (CompareResult compareResult : compareResultList) {
                        String report = compareResult.getFieldName() + "【" + compareResult.getFieldComment() + "】值:" + compareResult.getOldValue() + " => " + compareResult.getNewValue();
                        LOGGER.debug(report);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                //数据插入逻辑
            } else if (methodName.contains("insert")) {
                LOGGER.debug("数据新增:" + JSON.toJSONString(args[0]));
            }else if (methodName.contains("delete")) {
                LOGGER.debug("数据删除:" + JSON.toJSONString(args[0]));
            }
        }
        return logInfo;
    }


    /**
     * 对比两个对象
     *
     * @param oldObj 旧对象
     * @param newObj 新对象
     */
    protected List<CompareResult> getDiff(Object oldObj, Object newObj) throws IllegalAccessException {
        List<CompareResult> list = new ArrayList<>();
        //获取对象的class
        Class<?> clazz1 = oldObj.getClass();
        Class<?> clazz2 = newObj.getClass();
        //获取对象的属性列表
        Field[] field1 = clazz1.getDeclaredFields();
        Field[] field2 = clazz2.getDeclaredFields();
        //遍历属性列表field1
        for (int i = 0; i < field1.length; i++) {
            //没有注解不比较数据
            DataChange dataChange = field1[i].getAnnotation(DataChange.class);
            if (dataChange == null) {
                continue;
            }
            //遍历属性列表field2
            for (int j = 0; j < field2.length; j++) {
                //如果field1[i]属性名与field2[j]属性名内容相同
                if (field1[i].getName().equals(field2[j].getName())) {
                    field1[i].setAccessible(true);
                    field2[j].setAccessible(true);
                    if (field2[j].get(newObj) == null) {
                        continue;
                    }
                    //如果field1[i]属性值与field2[j]属性值内容不相同
                    if (!compareTwo(field1[i].get(oldObj), field2[j].get(newObj))) {
                        CompareResult r = new CompareResult();
                        r.setFieldName(field1[i].getName());
                        r.setOldValue(field1[i].get(oldObj));
                        r.setNewValue(field2[j].get(newObj));
                        if (dataChange != null) {
                            r.setFieldComment(dataChange.name());
                        }
                        list.add(r);
                    }
                    break;
                }
            }
        }
        return list;
    }

    @FunctionalInterface
    protected interface InvocationCallback {
        @Nullable
        Object proceedWithLog() throws Throwable;
    }

    private Serializable getPrimaryKey(Object et) {
        // 反射获取实体类
        Class<?> clazz = et.getClass();
        String pkValue = null;
        // 获取实体所有字段
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // 设置些属性是可以访问的
            field.setAccessible(true);
            if (field.isAnnotationPresent(TableId.class)) {
                try {
                    // 获取主键值
                    pkValue = field.get(et).toString();
                } catch (Exception e) {
                    pkValue = null;
                }
                break;
            }
        }
        return pkValue;
    }

    /**
     * 对比两个数据是否内容相同
     *
     * @param object1,object2
     * @return boolean类型
     */
    private boolean compareTwo(Object object1, Object object2) {

        if (object1 == null && object2 == null) {
            return true;
        }
        if (object1 == null && object2 != null) {
            return false;
        }
        if (object1.equals(object2)) {
            return true;
        }
        return false;
    }
}
