package org.jeecg.config.mybatis;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.aspect.DictAspect;
import org.jeecg.common.aspect.annotation.Dict;
import org.jeecg.common.system.vo.DictModel;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.system.entity.SysDataLog;
import org.jeecg.modules.system.mapper.SysDataLogMapper;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 记录编辑的时候修改了哪些字段
 *
 * @Author sunwd
 * @date 2024/4/24
 */
@Slf4j
// @Component
// @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class SysDataLogMybatisInterceptor implements Interceptor {

    public static final List<String> commonExcludeFields = Arrays.asList(
            "createBy",
            "createTime",
            "createName",
            "updateTime",
            "updateBy"
    );
    public static final List<String> excludeMappers = Arrays.asList(
            // "sysUserMapper",
            // "sysDepartMapper",
            // "sysPermissionMapper",
            "onlCgformFieldMapper",
            "onlCgformHeadMapper",
            "onlCgformIndexMapper",
            "onlineMapper",
            "jeecgDemoMapper",

            "bisonCustomerSequenceMapper",
            "middleMailInfoSentMapper",
            "middleMailInfoSentGroupMapper",
            "middleSequenceMapper"
    );

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        String sqlId = mappedStatement.getId();
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        Object parameter = invocation.getArgs()[1];

        if (parameter == null) {
            return invocation.proceed();
        }

        if (SqlCommandType.UPDATE == sqlCommandType) {
            Field[] fields = null;
            if (parameter instanceof ParamMap) {
                ParamMap<?> p = (ParamMap<?>) parameter;
                String et = "et";
                if (p.containsKey(et)) {
                    parameter = p.get(et);
                } else {
                    parameter = p.get("param1");
                }
                if (parameter == null) {
                    return invocation.proceed();
                }
                fields = oConvertUtils.getAllFields(parameter);
            } else {
                fields = oConvertUtils.getAllFields(parameter);
            }
            this.saveSysDataLog(sqlId, fields, parameter);
        }

        return invocation.proceed();
    }

    private void saveSysDataLog(String sqlId, Field[] fields, Object parameter) throws IllegalAccessException {
        String mapperName = convertMapperName(sqlId);
        if (excludeMappers.contains(mapperName)) return;

        BaseMapper baseMapper = (BaseMapper) SpringContextUtils.getBean(mapperName);
        String id = "";
        for (Field field : fields) {
            if ("id".equals(field.getName())) {
                field.setAccessible(true);
                Object o = field.get(parameter);
                id = String.valueOf(o);
            }
        }
        if (StrUtil.isBlank(id)) {
            // 如果数据表没有ID，就不保存数据
            return;
        }

        Object originData = baseMapper.selectOne(Wrappers.query().eq("id", id));
        Field[] originDataFields = oConvertUtils.getAllFields(originData);

        String dataChangeContent = getDataChangeContent(originData, originDataFields, parameter, fields);
        if (StrUtil.isBlank(dataChangeContent)) {
            // log.debug("没有任何改动，不进行记录");
            return;
        }

        LoginUser sysUser = this.getLoginUser();
        SysDataLog sysDataLog = new SysDataLog();
        sysDataLog.setCreateBy(sysUser == null ? "admin" : sysUser.getUsername());
        sysDataLog.setCreateName(sysUser == null ? "admin" : sysUser.getRealname());
        sysDataLog.setCreateTime(new Date());
        sysDataLog.setDataId(id);
        sysDataLog.setDataTable(StrUtil.toUnderlineCase(mapperName).replace("_mapper", ""));
        sysDataLog.setDataContent(dataChangeContent);

        SysDataLogMapper sysDataLogMapper = SpringContextUtils.getBean(SysDataLogMapper.class);
        sysDataLogMapper.insert(sysDataLog);
    }

    private LoginUser getLoginUser() {
        LoginUser sysUser = null;
        try {
            sysUser = SecurityUtils.getSubject().getPrincipal() != null ? (LoginUser) SecurityUtils.getSubject().getPrincipal() : null;
        } catch (Exception e) {
            sysUser = null;
        }
        return sysUser;
    }

    private String getDataChangeContent(Object originData, Field[] originDataFields, Object newData, Field[] newDataFields) throws IllegalAccessException {
        StringBuilder sb = new StringBuilder();
        ArrayList<Field> arrayList = new ArrayList<>(Arrays.asList(originDataFields));

        DictAspect dictAspect = SpringContextUtils.getBean(DictAspect.class);
        Map<String, List<String>> dataListMap = getDataListMap(originData, originDataFields, newData, newDataFields);
        Map<String, List<DictModel>> translText = null;
        if (!dataListMap.isEmpty()) {
            translText = dictAspect.translateAllDict(dataListMap);
        }
        for (Field newDataField : newDataFields) {
            if (newDataField.getAnnotation(TableField.class) != null && !newDataField.getAnnotation(TableField.class).exist()) {
                // @TableField(exist = false) 不进行记录
                continue;
            }
            if (newDataField.getAnnotation(SysDataLogIgnore.class) != null) {
                // @SysDataLogIgnore 不进行记录
                continue;
            }
            Iterator<Field> iterator = arrayList.iterator();
            while (iterator.hasNext()) {
                Field originDataField = iterator.next();
                if (commonExcludeFields.contains(originDataField.getName())) {
                    iterator.remove();
                    continue;
                }
                if (originDataField.getName().equals(newDataField.getName())) {
                    newDataField.setAccessible(true);
                    Object newValue = newDataField.get(newData);
                    originDataField.setAccessible(true);
                    Object originValue = originDataField.get(originData);
                    if (ObjectUtil.notEqual(originValue, newValue)) {
                        Dict dict = originDataField.getDeclaredAnnotation(Dict.class);
                        if (dict != null) {
                            String code = dict.dicCode();
                            String table = dict.dictTable();
                            String fieldDictCode = code;
                            if (StrUtil.isNotBlank(table)) {
                                String text = dict.dicText(), dataSource = dict.ds();
                                fieldDictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
                            }
                            try {
                                if (translText != null) {
                                    List<DictModel> dictModels = translText.get(fieldDictCode);
                                    originValue = dictAspect.translDictText(dictModels, String.valueOf(originValue));
                                    newValue = dictAspect.translDictText(dictModels, String.valueOf(newValue));
                                }
                            } catch (Exception e) {
                                log.debug("映射字典值出错", e);
                            }
                        }

                        ApiModelProperty apiModelProperty = originDataField.getDeclaredAnnotation(ApiModelProperty.class);
                        String tableField = apiModelProperty != null ? apiModelProperty.value() : originDataField.getName();
                        sb.append("【").append(tableField).append("】被更新 从 ").append(ObjectUtil.isEmpty(originValue) ? "null" : originValue).append(" 到 ").append(newValue).append("\n");
                    }
                    iterator.remove();
                }
            }
        }
        return sb.toString();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }

    private String convertMapperName(String str) {
        if (StrUtil.isBlank(str)) return str;
        String[] split = str.split("\\.");
        return StrUtil.lowerFirst(split[split.length - 2]);
    }

    private Map<String, List<String>> getDataListMap(Object originData, Field[] originDataFields, Object newData, Field[] newDataFields) throws IllegalAccessException {
        // List<Field> dictFieldList = new ArrayList<>();
        Map<String, List<String>> dataListMap = new HashMap<>(5);

        for (Field field : originDataFields) {
            field.setAccessible(true);
            String value = String.valueOf(field.get(originData));
            if (oConvertUtils.isEmpty(value)) {
                continue;
            }
            if (field.getAnnotation(Dict.class) != null) {
                // if (!dictFieldList.contains(field)) {
                //     dictFieldList.add(field);
                // }
                String code = field.getAnnotation(Dict.class).dicCode();
                String text = field.getAnnotation(Dict.class).dicText();
                String table = field.getAnnotation(Dict.class).dictTable();
                String dataSource = field.getAnnotation(Dict.class).ds();
                List<String> dataList;
                String dictCode = code;
                if (StrUtil.isNotBlank(table)) {
                    dictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
                }
                dataList = dataListMap.computeIfAbsent(dictCode, k -> new ArrayList<>());
                this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
            }
        }

        for (Field field : newDataFields) {
            field.setAccessible(true);
            String value = String.valueOf(field.get(newData));
            if (oConvertUtils.isEmpty(value)) {
                continue;
            }
            if (field.getAnnotation(Dict.class) != null) {
                // if (!dictFieldList.contains(field)) {
                //     dictFieldList.add(field);
                // }
                String code = field.getAnnotation(Dict.class).dicCode();
                String text = field.getAnnotation(Dict.class).dicText();
                String table = field.getAnnotation(Dict.class).dictTable();
                String dataSource = field.getAnnotation(Dict.class).ds();
                List<String> dataList;
                String dictCode = code;
                if (StrUtil.isNotBlank(table)) {
                    dictCode = String.format("%s,%s,%s,%s", table, text, code, dataSource);
                }
                dataList = dataListMap.computeIfAbsent(dictCode, k -> new ArrayList<>());
                this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
            }
        }

        return dataListMap;
    }

    private void listAddAllDeduplicate(List<String> dataList, List<String> addList) {
        // 筛选出dataList中没有的数据
        List<String> filterList = addList.stream().filter(i -> !dataList.contains(i)).collect(Collectors.toList());
        dataList.addAll(filterList);
    }
}
