package com.sunyard.ssp.modules.monitor.log.aspect;

import cn.hutool.core.thread.threadlocal.NamedThreadLocal;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sunyard.chsm.model.R;
import com.sunyard.chsm.utils.IpUtils;
import com.sunyard.ssp.common.Result;
import com.sunyard.ssp.common.annotation.AuditControllerLog;
import com.sunyard.ssp.modules.monitor.log.entity.AuditLog;
import com.sunyard.ssp.modules.monitor.log.service.IAuditLogService;
import com.sunyard.ssp.modules.user.entity.ScUser;
import com.sunyard.ssp.utils.SecurityUtil;
import com.sunyard.ssp.utils.ThreadPoolUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author fyc
 * @description 切点实现类
 */
@Slf4j
@Aspect
@Component
@SuppressWarnings("all")
public class AuditLogAspect {

    private final ObjectMapper objectMapper;

    @Autowired
    private IAuditLogService auditLogService;

    @Autowired
    private SecurityUtil securityUtil;

    @Autowired
    private HttpServletRequest request;

    private static final ThreadLocal<Date> beginTimeThreadLocal = new NamedThreadLocal<Date>("ThreadLocal beginTime");

    public AuditLogAspect(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    /**
     * 控制器切入层
     */
    @Pointcut("@annotation(com.sunyard.ssp.common.annotation.AuditControllerLog)")
    public void controllerAspect(){

    }
    private static ConcurrentHashMap<String, String> parameters = new ConcurrentHashMap<>();
    /**
     * 前置切入
     * @param joinPoint
     */
    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint) throws ServletException, IOException {
        Date beginTime = new Date();
        beginTimeThreadLocal.set(beginTime);
    }
    /**
     * 后置切入
     * @param joinPoint
     * @param res
     */
    @AfterReturning(pointcut = "controllerAspect()",returning = "res")
    public void doAfterReturning(JoinPoint joinPoint, Object res){
        try {
            AuditLog auditLog = new AuditLog();
            auditLog.setCreateTime(LocalDateTime.now());

            //操作用户信息
            ScUser scUser = securityUtil.getCurrUser();
            if(scUser != null){
                auditLog.setOperatorName(scUser.getRealname());
                auditLog.setOperatorId(scUser.getId());
            }

            //操作描述、操作类型和操作结果
            Map map = getAuditLogDescription(joinPoint);
            auditLog.setOperateContent((String) map.get("description"));
            auditLog.setOperateType((String)map.get("operateType"));

            if (res instanceof R) {
                auditLog.setOperateResult("成功");
            } else if (res instanceof Result) {
                Result<Object> result = (Result<Object>) res;
                if(result.isSuccess()){
                    auditLog.setOperateResult("成功");
                }else {
                    auditLog.setOperateResult("失败");
                }
            } else {
                log.warn("unrecognized return object: {}", res.getClass());
            }

            //Ip信息
            auditLog.setIpAddress(IpUtils.getIpAddress(request));
            auditLog.setIpInfo("未知");

            //请求相关
            auditLog.setRequestMothed(request.getMethod());
            auditLog.setRequestPath(request.getRequestURI());
            Map params = request.getParameterMap();
//            auditLog.setRequestParam(requestParamToString(request.getParameterMap()));
            String parameters = getParameters(joinPoint);
            auditLog.setRequestParam(parameters);
            //计算耗时
            Date logStartTime = beginTimeThreadLocal.get();
            long beginTime = beginTimeThreadLocal.get().getTime();
            long endTime = System.currentTimeMillis();
            Long logElapsedTime = endTime - beginTime;
            auditLog.setTimeConsuming(logElapsedTime.intValue());

            //存储日志
            ThreadPoolUtil.getPool().execute(new SaveAuditLogThread(auditLog, auditLogService));

        }catch (Exception e){
            log.warn("记录操作日志异常", e);
        }
    }

    /**
     * 调用线程存储审计日志
     */
    private static class SaveAuditLogThread implements Runnable{

        private AuditLog auditLog;
        private  IAuditLogService iAuditLogService;

        public SaveAuditLogThread(AuditLog auditLog, IAuditLogService iAuditLogService) {
            this.auditLog = auditLog;
            this.iAuditLogService = iAuditLogService;
        }
        @Override
        public void run() {
            iAuditLogService.save(auditLog);
        }
    }


    /**
     * 从注解中获取描述和类型
     * @param joinPoint
     * @return
     * @throws Exception
     */
    public static Map getAuditLogDescription(JoinPoint joinPoint) throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();//目标方法名
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        Map map = new HashMap();
        for (Method method:methods) {
            if (method.getName().equals(methodName)){
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length==arguments.length){
                    String description = method.getAnnotation(AuditControllerLog.class).description();
                    String operateType =  method.getAnnotation(AuditControllerLog.class).operateType();
                    map.put("description",description);
                    map.put("operateType",operateType);
                    break;
                }
            }
        }
        return map;
    }

    /**
     * 请求参数map 转化成 string
     * @param params
     * @return
     */
    public static String requestParamToString(Map<String,String[]> params){
        Map map = new HashMap<String,Object>(16);
        Set<String> keySet = params.keySet();

        for(String key : keySet){
            String values[] = params.get(key);
            map.put(key, Arrays.asList(values).toString());
        }
        JSONObject jsonObject = new JSONObject(map);
        return jsonObject.toJSONString();
    }

    /**
     * @Description: 获取参数列表
     * @param joinPoint 切入点
     * @Return: java.lang.String 参数:值，多个之间使用空格分割
     * @Author: joan.wu
     * @Date: 2024/5/21 14:28
     */
    private String getParameters(JoinPoint joinPoint) throws JsonProcessingException {
        ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
        Object[] args = joinPoint.getArgs();
        Method method = getMethod(joinPoint);
        // 获取方法参数名
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);

        // 构造参数名和参数值的对应关系
        StringBuilder argsInfo = new StringBuilder();
        for (int i = 0; i < args.length; i++) {
            if (parameterNames == null || parameterNames[i] == null) {
                continue;
            }
            argsInfo.append(parameterNames[i])
                    .append(":")
                    .append(objectMapper.writeValueAsString(args[i]))
                    .append(" ");
        }
        return argsInfo.toString();

    }
    /**
     * @Description: 通过烦设获取类的方法
     * @param joinPoint 切入点
     * @Return: java.lang.reflect.Method
     * @Author: joan.wu
     * @Date: 2024/5/21 14:30
     */
    private Method getMethod(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        if (!(signature instanceof MethodSignature)) {
            throw new IllegalArgumentException("This annotation is only supported on method level");
        }
        MethodSignature methodSignature = (MethodSignature) signature;
        return methodSignature.getMethod();
    }
}
