package org.jeecg.common.aspect;

import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.PropertyFilter;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.jeecg.common.api.dto.LogDto;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.enums.ModuleType;
import org.jeecg.common.constant.enums.OperateTypeEnum;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.IpUtils;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.oConvertUtils;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * 系统日志，切面处理类
 */
@Aspect
@Component
public class AutoLogAspect {

    @Pointcut("@annotation(org.jeecg.common.aspect.annotation.AutoLog)")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        try {
            Object result = point.proceed();
            long time = System.currentTimeMillis() - beginTime;
            LogDto logDto = getLogDto(point, time, result, null);
            SpringUtil.getApplicationContext().publishEvent(logDto);
            return result;
        } catch (Exception e) {
            long time = System.currentTimeMillis() - beginTime;
            LogDto logDto = getLogDto(point, time, null, e.getMessage());
            SpringUtil.getApplicationContext().publishEvent(logDto);
            throw e;
        }
    }

    private LogDto getLogDto(JoinPoint joinPoint, Long time, Object obj, String exceptionMsg) {
        LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String methodName = signature.getName();
        String className = joinPoint.getTarget().getClass().getName();

        AutoLog syslog = method.getAnnotation(AutoLog.class);
        String content = syslog.value();
        ModuleType module = syslog.module();
        int logType = syslog.logType();
        int operateType = syslog.operateType();
        if (module == ModuleType.ONLINE && obj != null) {
            content = getOnlineLogContent(obj, content);
        }

        LogDto dto = new LogDto();
        dto.setLogType(logType);
        dto.setLogContent(content);
        dto.setCostTime(time);
        dto.setCreateTime(new Date());
        dto.setExceptions(exceptionMsg);
        dto.setMethod(className + "." + methodName);
        dto.setIp(IpUtils.getIpAddr(request));
        dto.setRequestType(request.getMethod());
        dto.setRequestUrl(request.getRequestURI());
        if (module != ModuleType.EMAIL) // 不保存邮件的参数
            dto.setRequestParam(getReqestParams(request, joinPoint));
        if (CommonConstant.LOG_TYPE_2 == dto.getLogType()) // 是操作类的日志
            dto.setOperateType(getOperateType(methodName, operateType));
        if (loginUser != null) {
            dto.setLoginUser(loginUser);
            dto.setUserid(loginUser.getUsername());
            dto.setUsername(loginUser.getRealname());
            dto.setCreateBy(loginUser.getRealname());
        }
        return dto;
    }

    /**
     * 获取操作类型
     */
    private int getOperateType(String methodName, int operateType) {
        if (operateType > 0) {
            return operateType;
        }
        return OperateTypeEnum.getTypeByMethodName(methodName);
    }

    /**
     * 获取请求参数
     */
    private String getReqestParams(HttpServletRequest request, JoinPoint joinPoint) {
        String httpMethod = request.getMethod();
        String params = "";
        if (CommonConstant.HTTP_POST.equals(httpMethod) || CommonConstant.HTTP_PUT.equals(httpMethod) || CommonConstant.HTTP_PATCH.equals(httpMethod)) {
            Object[] paramsArray = joinPoint.getArgs();
            Object[] arguments = new Object[paramsArray.length];
            for (int i = 0; i < paramsArray.length; i++) {
                if (paramsArray[i] instanceof BindingResult || paramsArray[i] instanceof ServletRequest || paramsArray[i] instanceof ServletResponse || paramsArray[i] instanceof MultipartFile) {
                    continue;
                }
                arguments[i] = paramsArray[i];
            }
            PropertyFilter profilter = (o, name, value) -> true;
            params = JSONObject.toJSONString(arguments, profilter);
        } else {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Object[] args = joinPoint.getArgs();
            LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
            String[] paramNames = u.getParameterNames(method);
            if (args != null && paramNames != null) {
                for (int i = 0; i < args.length; i++) {
                    params += "  " + paramNames[i] + ": " + args[i];
                }
            }
        }
        return params;
    }

    /**
     * online日志内容拼接
     */
    private String getOnlineLogContent(Object obj, String content) {
        if (obj instanceof Result) {
            Result res = (Result) obj;
            String msg = res.getMessage();
            String tableName = res.getOnlTable();
            if (oConvertUtils.isNotEmpty(tableName)) {
                content += ",表名:" + tableName;
            }
            if (res.isSuccess()) {
                content += "," + (oConvertUtils.isEmpty(msg) ? "操作成功" : msg);
            } else {
                content += "," + (oConvertUtils.isEmpty(msg) ? "操作失败" : msg);
            }
        }
        return content;
    }
}
