package cn.freemud.aop;

import com.alibaba.fastjson.JSON;
import com.freemud.application.sdk.api.base.SDKCommonBaseContextWare;
import com.freemud.application.sdk.api.exception.IgnoreErrorAnnotation;
import com.freemud.application.sdk.api.log.*;
import com.freemud.application.sdk.api.service.EmailAlertService;
import com.google.common.collect.Lists;
import org.apache.commons.beanutils.BeanUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

/**
 * @author freemud_whh
 */
@Aspect
@Component
public class ControllerLogAop implements Ordered {

    @Autowired
    private EmailAlertService emailAlertService;

    /**
     * 是否打印响应报文日志，默认是false,若为true会覆盖注解里面的{@link LogIgnore}配置，输出响应报文里面所有的信息
     */
    @Value("${print-response-body-log-shop-cart:false}")
    private volatile boolean printResponseBodyLogForShopCart = false;

    /**
     * 即使printResponseBodyLog设置为true, 该参数中包含的url也会被过滤且不打印日志
     */
    @Value("${exclude-print-body-log-methods:findNearPickUpStores,getMenuCategory}")
    private volatile List<String> excludePrintBodyLogMethods = Lists.newArrayList();

    @Pointcut("@annotation(cn.freemud.aop.LogIgnore)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object doAroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        StringBuilder logMethod = new StringBuilder(joinPoint.getSignature().getDeclaringTypeName());
        logMethod.append(".");
        logMethod.append(joinPoint.getSignature().getName());
        Signature sig = joinPoint.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("非法参数使用 ");
        } else {
            msig = (MethodSignature) sig;
            Object target = joinPoint.getTarget();
            Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            if (ApiLog.isDebugEnabled()) {
                ApiLog.debug("获取tracking的值====>{}", new Object[]{request.getHeader("x-transaction-id")});
            }
            LogThreadLocal.setTrackingNo(StringUtils.isEmpty(request.getHeader("x-transaction-id")) ? UUID.randomUUID().toString().replaceAll("-", "") : request.getHeader("x-transaction-id"));
            List logArgs = this.getLogArgs(currentMethod, joinPoint);
            String requestData = JSON.toJSONString(logArgs);
            LogIgnore logIgnore = currentMethod.getAnnotation(LogIgnore.class);
            Object object = joinPoint.proceed();
            Object logObj = object;
            if (logIgnore != null && logIgnore.printLog()) {
                if (!this.printResponseBodyLogForShopCart || excludePrintBodyLogMethods.contains(logIgnore.logMessage())) {
                    String statusCodeValue = BeanUtils.getProperty(object, logIgnore.statusCodeFieldName());
                    String messageValue = BeanUtils.getProperty(object, logIgnore.messageFieldName());
                    String[] excludeStatusCodes = logIgnore.excludeStatusCodes();
                    //todo 当排除了这个状态码不打印响应的具体内容只会打印状态码和状态描述信息
                    //当返回code在不打印的范围，则将打印信息变更为只打印code和message
                    if (this.containStatusCode(excludeStatusCodes, statusCodeValue)) {
                        logObj = object.getClass().newInstance();
                        BeanUtils.setProperty(logObj, logIgnore.statusCodeFieldName(), statusCodeValue);
                        BeanUtils.setProperty(logObj, logIgnore.messageFieldName(), messageValue);
                    }
                }
            }
            ApiLog.infoConvertJson(logMethod.toString(), logIgnore.logMessage(), request, startTime, System.currentTimeMillis(), requestData, logObj);
            LogThreadLocal.removeTrackingNo();
            return object;
        }
    }

    /**
     * 过滤返参code是否在excludeStatusCodes存在
     *
     * @param excludeStatusCodes
     * @param statusCodeValue
     * @return
     */
    private boolean containStatusCode(String[] excludeStatusCodes, String statusCodeValue) {
        if (excludeStatusCodes == null || excludeStatusCodes.length == 0) {
            return false;
        }
        for (int i = 0; i < excludeStatusCodes.length; i++) {
            if (excludeStatusCodes[i].equals(statusCodeValue)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 系统异常时，AfterThrowing在ApiAnnotation注解中已经处理。
     * 此处先注释，暂不删除，代码保留
     *
     * @param joinPoint
     * @return
     */
    @AfterThrowing(
            pointcut = "pointcut()",
            throwing = "e"
    )
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        long startTime = System.currentTimeMillis();
        Signature sig = joinPoint.getSignature();
        Method currentMethod = null;
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("非法参数使用");
        } else {
            msig = (MethodSignature) sig;
            Object target = joinPoint.getTarget();
            try {
                currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
            } catch (NoSuchMethodException var19) {
                var19.printStackTrace();
            }
            StringBuilder logMethod = new StringBuilder(joinPoint.getSignature().getDeclaringTypeName());
            logMethod.append(".");
            logMethod.append(joinPoint.getSignature().getName());
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            try {
                Class<?> clz = e.getClass();
                boolean clzHasAnno = clz.isAnnotationPresent(IgnoreErrorAnnotation.class);
                if (!clzHasAnno) {
                    this.emailAlertService.sendEmailAlert("系统全局异常", e);
                }
            } catch (Exception var17) {
                ErrorLog.errorConvertJson(SDKCommonBaseContextWare.getAppName(), logMethod.toString(), "出错:" + e.getMessage(), request, startTime, System.currentTimeMillis(), this.getLogArgs(currentMethod, joinPoint), var17);
            } finally {
                ErrorLog.errorConvertJson(SDKCommonBaseContextWare.getAppName(), logMethod.toString(), "出错:" + e.getMessage(), request, startTime, System.currentTimeMillis(), this.getLogArgs(currentMethod, joinPoint), e);
                LogThreadLocal.removeTrackingNo();
            }
        }
    }

    private List getLogArgs(Method method, JoinPoint joinPoint) {
        if (method == null) {
            return null;
        } else {
            Object[] args = joinPoint.getArgs();
            if (args != null && args.length != 0) {
                List logArgs = new ArrayList();
                Parameter[] parameters = method.getParameters();
                for (int j = 0; j < parameters.length; ++j) {
                    Parameter parameter = parameters[j];
                    LogParams logParams = (LogParams) parameter.getAnnotation(LogParams.class);
                    if (logParams != null) {
                        logArgs.add(args[j]);
                    }
                }
                return logArgs;
            } else {
                return null;
            }
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
