package cn.com.ty.lift.business.framework.aspect;

import cn.com.ty.lift.common.utils.ValuePool;
import cn.com.ty.lift.common.verify.ValidateException;
import cn.com.xwy.boot.web.dto.RestResponse;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;

/**
 * @author bieao
 * @date 2019/12/3 10:30 AM
 * @description 日志切面
 */
@Slf4j
@Aspect
@Order(1)
@Component
public class ControllerAspect {

    @Autowired
    private ObjectMapper objectMapper;
    private Instant begin = Instant.now();
    private Instant end   = Instant.now();
    private String  url;
    private String  httpMethod;
    private String  ip;
    private String  classMethod;
    private String  args;
    private String  resp;
    private long    timing;

    @Pointcut("execution(* cn.com.ty.lift.business.*.controller..*(..))")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // Receives the request and get request content
        begin = Instant.now();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Signature signature = joinPoint.getSignature();

        url = request.getRequestURL().toString();
        httpMethod = request.getMethod();
        ip = ValuePool.LOG_IP + request.getRemoteAddr();
        classMethod = ValuePool.LOG_CLASS_METHOD + signature.getDeclaringTypeName() + ValuePool.DOT + signature.getName();
        args = ValuePool.LOG_ARGS + Arrays.toString(joinPoint.getArgs()).replace("=null",ValuePool.EMPTY);
    }

    @AfterReturning(returning = "response", pointcut = "pointcut()")
    public void doAfterReturning(RestResponse response) throws Throwable {
        // Processes the request and returns the content
        end = Instant.now();
        String res = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(response);
        if (StrUtil.isNotEmpty(res) && !ValuePool.LOG_PRINT_ALL) {
            int length = res.length();
            int sub = length - ValuePool.LOG_PRINT_MAX;
            int head = ValuePool.LOG_PRINT_MAX >> 1;
            int tail = length - head;
            res = sub < 0 ? res : res.substring(0, head) + ValuePool.NEWLINE + ValuePool.NEWLINE
                    + "..." + ValuePool.NEWLINE
                    + sub + " omit." + ValuePool.NEWLINE
                    + "..." + ValuePool.NEWLINE + ValuePool.NEWLINE
                    + res.substring(tail, length);
        }
        timing = Duration.between(begin, end).toMillis();
        boolean bigger = timing > ValuePool.LOG_GOOD_TIME;
        resp = ValuePool.LOG_RESPONSE + timing + " ms " + (bigger ? "(>_<)" : "(^_^)") + ValuePool.NEWLINE + res;
        if (bigger) {
            warn();
        } else {
            info();
        }
    }

    @AfterThrowing(throwing = "ex", pointcut = "pointcut()")
    public void doAfterThrowing(Exception ex) throws Throwable {
        // Processes the exception
        end = Instant.now();
        if (null != ex) {
            Class<? extends Exception> clazz = ex.getClass();
            if (clazz.equals(ValidateException.class) || clazz.equals(MethodArgumentNotValidException.class)) {
                String message = ex.getMessage();
                if(clazz.equals(MethodArgumentNotValidException.class)){
                    MethodArgumentNotValidException notValid = (MethodArgumentNotValidException) ex;
                    message = notValid.getBindingResult().getFieldError().getDefaultMessage();
                }
                timing = Duration.between(begin, end).toMillis();
                resp = ValuePool.LOG_RESPONSE + "(" + timing + " ms) " + message;
                error();
            }
        }
    }

    @Around("pointcut()")
    public Object interceptor(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        return proceedingJoinPoint.proceed();
    }

    private void info() {
        log.info(ValuePool.LOG_LINE);
        log.info("{}{} {}", ValuePool.LOG_URL, httpMethod, url);
        log.info(ip);
        log.info(classMethod);
        log.info(args);
        log.info(resp);
        log.info(ValuePool.LOG_LINE);
    }

    private void warn() {
        log.warn(ValuePool.LOG_LINE);
        log.warn("{}{} {}", ValuePool.LOG_URL, httpMethod, url);
        log.warn(ip);
        log.warn(classMethod);
        log.warn(args);
        log.warn(resp);
        log.warn(ValuePool.LOG_LINE);
    }

    private void error() {
        log.error(ValuePool.LOG_LINE);
        log.error("{}{} {}", ValuePool.LOG_URL, httpMethod, url);
        log.error(ip);
        log.error(classMethod);
        log.error(args);
        log.error(resp);
        log.error(ValuePool.LOG_LINE);
    }
}
