package com.izouma.jmrh.aspect;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import com.alibaba.fastjson.JSON;
import com.izouma.jmrh.annotations.OperLog;
import com.izouma.jmrh.domain.ExceptionLog;
import com.izouma.jmrh.domain.OperationLog;
import com.izouma.jmrh.domain.User;
import com.izouma.jmrh.repo.ExceptionLogRepo;
import com.izouma.jmrh.repo.OperationLogRepo;
import com.izouma.jmrh.utils.IPUtils;
import com.izouma.jmrh.utils.SecurityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
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.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

@Aspect
@Component
public class OperLogAspect {

    @Autowired
    private OperationLogRepo operationLogRepo;

    @Autowired
    private ExceptionLogRepo exceptionLogRepo;

    /**
     * 设置操作日志切入点 记录操作日志 在注解的位置切入代码
     */
    @Pointcut("@annotation(com.izouma.jmrh.annotations.OperLog)")
    public void operLogPointCut() {
    }

    /**
     * 设置操作异常切入点记录异常日志 扫描所有controller包下操作
     */
    @Pointcut("execution(* com.izouma.jmrh.web..*.*(..))")
    public void operExceptionLogPointCut() {
    }

    /**
     * 正常返回通知，拦截用户操作日志，连接点正常执行完成后执行， 如果连接点抛出异常，则不会执行
     *
     * @param joinPoint 切入点
     * @param keys      返回结果
     */
    @AfterReturning(value = "operLogPointCut()", returning = "keys")
    public void saveOperLog(JoinPoint joinPoint, Object keys) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes
                .resolveReference(RequestAttributes.REFERENCE_REQUEST);

        OperationLog operationLog = new OperationLog();
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();
            // 获取操作
            OperLog operLog = method.getAnnotation(OperLog.class);
            if (operLog != null) {
                operationLog.setName(operLog.value()); // 操作模块
                operationLog.setType(operLog.type()); // 操作类型
                operationLog.setDesc(operLog.desc()); // 操作描述
            }
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName;

            operationLog.setReqMethod(methodName);

            // 请求的参数
            Map<String, String> rtnMap = null;
            String params = null;
            if (request != null) {
                rtnMap = convertMap(request.getParameterMap());
                params = JSON.toJSONString(rtnMap);
            }

            operationLog.setReqParams(params);
            operationLog.setResp(JSON.toJSONString(keys));

            User user = SecurityUtils.getAuthenticatedUser();
            if (user != null) {
                operationLog.setUserId(String.valueOf(user.getId()));
                operationLog.setUsername(user.getUsername());
            }
            operationLog.setReqIp(IPUtils.getIpAddr(request));
            operationLog.setReqUrl(request != null ? request.getRequestURI() : null);
            operationLog.setTime(LocalDateTime.now());
            operationLogRepo.save(operationLog);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 异常返回通知，用于拦截异常日志信息 连接点抛出异常后执行
     *
     * @param joinPoint 切入点
     * @param e         异常信息
     */
    @AfterThrowing(pointcut = "operExceptionLogPointCut()", throwing = "e")
    public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes
                .resolveReference(RequestAttributes.REFERENCE_REQUEST);

        ExceptionLog exceptionLog = new ExceptionLog();
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = method.getName();
            methodName = className + "." + methodName;
            exceptionLog.setReqMethod(methodName);

            Map<String, String> rtnMap = convertMap(request.getParameterMap());
            String params = JSON.toJSONString(rtnMap);
            exceptionLog.setReqParams(params);

            exceptionLog.setName(e.getClass().getName());
            exceptionLog.setMessage(stackTraceToString(e.getClass().getName(), e.getMessage(), e
                    .getStackTrace()));
            User user = SecurityUtils.getAuthenticatedUser();
            if (user != null) {
                exceptionLog.setUserId(String.valueOf(user.getId()));
                exceptionLog.setUsername(user.getUsername());
            }

            exceptionLog.setReqUrl(request.getRequestURI());
            exceptionLog.setReqIp(IPUtils.getIpAddr(request));
            exceptionLog.setTime(LocalDateTime.now());

            exceptionLogRepo.save(exceptionLog);

        } catch (Exception e2) {
            e2.printStackTrace();
        }

    }

    /**
     * 转换request 请求参数
     *
     * @param paramMap request获取的参数数组
     */
    public Map<String, String> convertMap(Map<String, String[]> paramMap) {
        Map<String, String> rtnMap = new HashMap<>();
        for (String key : paramMap.keySet()) {
            rtnMap.put(key, paramMap.get(key)[0]);
        }
        return rtnMap;
    }

    /**
     * 转换异常信息为字符串
     *
     * @param exceptionName    异常名称
     * @param exceptionMessage 异常信息
     * @param elements         堆栈信息
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuilder strBuff = new StringBuilder();
        for (StackTraceElement stet : elements) {
            strBuff.append(stet).append("\n");
        }
        return exceptionName + ":" + exceptionMessage + "\n\t" + strBuff;
    }
}