
package com.jy.log.record;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.jy.framework.model.annotation.RecordAudit;
import com.jy.framework.model.base.BaseIdPo;
import com.jy.framework.model.enums.BusinessType;
import com.jy.framework.model.enums.LogTagExtendedType;
import com.jy.framework.satoken.LoginContext;
import com.jy.framework.utils.AssertUtil;
import com.jy.log.model.entity.LogUserOperation;
import com.jy.log.utils.LogTagUtil;
import com.jy.log.model.wrapper.ClazzWrapper;
import com.jy.log.model.wrapper.FieldWrapper;
import com.jy.log.service.LogUserOperationService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 操作日志记录切面
 */
@Slf4j
@Aspect
@Component
public class RecordAuditAspect {

    public static final ThreadLocal<Boolean> EXIST_FUN = ThreadLocal.withInitial(() -> false);

    @Autowired
    private LogUserOperationService logUserOperationService;

    @Around("@annotation(recordAudit)")
    public Object around(ProceedingJoinPoint point, RecordAudit recordAudit) throws Throwable {
        if (EXIST_FUN.get()) {
            return point.proceed();
        }

        Object result = null;

        Object[] args = point.getArgs();

        // 获取方法签名
        MethodSignature signature = (MethodSignature) point.getSignature();
        // 获取完整方法路径
        String declaringTypeName = signature.getDeclaringTypeName();
        // 获取方法名称
        String methodName = signature.getName();

        // 获取参数类型数组
        Class<?>[] parameterTypes = signature.getParameterTypes();
        String jsonString = JSONObject.toJSONString(parameterTypes);

        LogUserOperation logUserOperation = new LogUserOperation();
        logUserOperation.setModuleFlag(recordAudit.moduleFlag());
        logUserOperation.setModuleName(recordAudit.moduleName());
        logUserOperation.setServiceBeanName(declaringTypeName);
        logUserOperation.setMethodName(methodName);
        logUserOperation.setReqClasses(jsonString);
        logUserOperation.setOperatorId(LoginContext.getUserId());
        logUserOperation.setOperatorName(LoginContext.getNickname());
        logUserOperation.setOperationType(recordAudit.businessType().getKey());
        logUserOperation.setOperationData(JSONObject.toJSONString(args));

        switch (recordAudit.businessType()) {
            case INSERT -> {
                logUserOperation.setAuditStatus(1);
                result = point.proceed();

                if (result instanceof Long id) {
                    logUserOperation.setBusinessId(id);
                }
                if (result instanceof BaseIdPo baseIdPo) {
                    logUserOperation.setBusinessId(baseIdPo.getId());
                }
            }
            case UPDATE -> {
                if (args != null && args.length == 1 && args[0] instanceof BaseIdPo) {
                    logUserOperation.setBusinessId(((BaseIdPo) args[0]).getId());
                    boolean hasPendingAudit = logUserOperationService.hasPendingAuditByBusinessId(logUserOperation.getBusinessId());
                    AssertUtil.eqFalse(hasPendingAudit, "已存在待修改记录，操作失败！");
                }

                logUserOperation.setAuditStatus(0);
                Object oldData = getOldData(point, signature, recordAudit);
                logUserOperation.setSourceData(JSONObject.toJSONString(oldData));

                if (args != null && args.length == 1) {
                    Object arg = args[0];
                    Class<?> type = parameterTypes[0];

                    Object oldObject = JSON.parseObject(logUserOperation.getSourceData(), type);
                    Object newObject = JSON.parseObject(JSON.toJSONString(arg), type);
                    String description = LogTagUtil.fieldComparison(oldObject, newObject);
                    logUserOperation.setDescription(description);
                }
            }
            case DELETE -> {
                logUserOperation.setAuditStatus(0);
                Object oldData = getOldData(point, signature, recordAudit);
                logUserOperation.setSourceData(JSONObject.toJSONString(oldData));
            }
            case OTHER -> {
                logUserOperation.setOperationType(BusinessType.UPDATE.getKey());
                logUserOperation.setAuditStatus(1);
                if (args != null && args.length == 1 && StrUtil.isNotBlank(recordAudit.businessIdField())) {
                    Object arg = args[0];
                    Field field = ReflectUtil.getField(arg.getClass(), recordAudit.businessIdField());
                    field.setAccessible(true);
                    logUserOperation.setBusinessId(Long.valueOf(field.get(arg).toString()));
                }
                point.proceed();
            }
        }

        logUserOperationService.add(logUserOperation);
        return result;
    }


    /**
     * 获取原数据
     */
    @SneakyThrows
    private Object getOldData(ProceedingJoinPoint point, MethodSignature signature, RecordAudit recordAudit) {
        String oldDataBeanName = recordAudit.getOldDataBeanName();
        String oldDataFunName = recordAudit.getOldDataFunName();

        Object bean = StrUtil.isEmpty(oldDataBeanName) ? point.getTarget() : SpringUtil.getBean(oldDataBeanName);
        Method method = bean.getClass().getMethod(oldDataFunName, signature.getParameterTypes());
        return method.invoke(bean, point.getArgs());
    }

}
