package com.crunii.micro.core.aspect;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import cn.hutool.extra.servlet.ServletUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.google.common.collect.Sets;

import cn.hutool.json.JSONUtil;
import io.swagger.v3.oas.annotations.Operation;

@Aspect
@Component
public class ApiLogAspect {
    
    private final static Logger logger = LoggerFactory.getLogger(ApiLogAspect.class);

    private Set<String> block_url = Sets.newHashSet("/v3/api-docs/swagger-config","/v3/api-docs","/swagger-ui.html");
 
    /** 以 controller 包下定义的所有请求为切入点 */
    @Pointcut("@annotation(org.springframework.web.bind.annotation.ResponseBody)")
    public void api() {}
 
    /**
     * 在切点之前织入
     * @param joinPoint
     * @throws Throwable
     */
    @Before("api()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if(null != attributes){
            HttpServletRequest request = attributes.getRequest();
            if(block_url.contains(request.getRequestURI())){
                return;
            }
        }
        //从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 打印调用 controller 的全路径以及执行方法
        logger.info("Class Method   : {}.{}", signature.getDeclaringTypeName(), signature.getName());

        //获取切入点所在的方法
        Method method = signature.getMethod();


        //获取操作
        Operation operation = method.getAnnotation(Operation.class);
        if (operation != null) {
            logger.info("接口中文说明    :{}", operation.summary());
        }
       
        // 打印请求入参
        try {
           // Object requestParam = joinPoint.getArgs();
            // 获取参数名称和值
            Map<String, Object> nameAndArgsMap = AopUtils.getFieldsName(joinPoint);
            logger.info("参数            : {}", JSONUtil.toJsonStr(nameAndArgsMap) );
        }catch (Exception e){
            logger.info("参数打印失败，异常: {}", e.getMessage() );
        }
    }
 
    /**
     * 在切点之后织入
     * @throws Throwable
     */
    @After("api()")
    public void doAfter() throws Throwable {
       
    }
 
    /**
     * 环绕
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("api()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String reqId = "";
        if (null != attributes) {
            reqId = attributes.getSessionId();
        }
        // 打印请求相关参数
        logger.info("======================= Start(请求id:{}) ======================", reqId);
        commonLog(attributes);
        long startTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        commonLog(attributes);

        // 打印出参
        logger.info("返回值 : {}", JSONUtil.toJsonStr(result));
        // 执行耗时
        logger.info("耗时   : {} ms", System.currentTimeMillis() - startTime);

        logger.info("=======================  End(请求id:{})  ======================", reqId);
        return result;
    }

    @AfterThrowing(pointcut = "api()", throwing = "e")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String reqId = "";
        if (null != attributes) {
            reqId = attributes.getSessionId();
        }
        logger.error("Exception in (请求id:{}) {}.{}() with cause :", reqId,
                joinPoint.getSignature().getDeclaringTypeName(),
                joinPoint.getSignature().getName(), e.getCause() != null ? e.getCause() : "NULL");
        logger.info("=======================  End(请求id:{})  ======================", reqId);
    }

    private void commonLog(ServletRequestAttributes attributes){
        if(null != attributes){
            HttpServletRequest request = attributes.getRequest();
            if(!block_url.contains(request.getRequestURI())){
                // 打印请求 url
                logger.info("URL            : {}", request.getRequestURL().toString());
                // 打印 Http method
                logger.info("HTTP Method    : {}", request.getMethod());
                // 打印请求的 IP
                logger.info("IP             : {}", ServletUtil.getClientIP(request));
                logger.info("session        : {}", request.getSession());
            }
        }
    }
}


