package com.reyun.saas.web.aop;

import com.reyun.saas.common.constants.PermitAllUrl;
import com.reyun.saas.common.util.IPUtils;
import com.reyun.saas.common.utils.CoreUtils;
import com.reyun.saas.mob.log.domain.SysLog;
import com.reyun.saas.mob.log.service.SysLogService;
import com.reyun.saas.mob.openapi.domain.CollaborativeProject;
import com.reyun.saas.mob.openapi.service.CollaborativeProjectService;
import com.reyun.saas.mob.user.dto.LoginUserDTO;
import com.reyun.saas.security.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
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.core.LocalVariableTableParameterNameDiscoverer;
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.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Date;


/**
 * 操作日志handler
 */
@Slf4j
@Aspect
@Component
public class LogAspect implements Serializable {

    @Resource
    private SysLogService sysLogService;

    @Autowired
    private CollaborativeProjectService projectService;

    /** 如果Log注解的包名改变,记得这里要更新 */
    @Pointcut("@annotation(com.reyun.saas.web.aop.Log)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Object result = null;
        long beginTime = System.currentTimeMillis();
        try {
            // 执行方法
            result = point.proceed();
        } catch (Throwable e) {
            // e.printStackTrace();
            throw e;
        }
        // 执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        // 保存日志
        saveLog(point, time);
        return result;
    }

    @AfterThrowing(pointcut = "pointcut()", throwing = "e")
    public void doAfterThrowing(JoinPoint point, Throwable e){
        // 保存异常日志
        if(null != e){
            saveLog(point, 0, e);
        }
    }

    /**
     * 存储log
     */
    private void saveLog(JoinPoint joinPoint, long time) {
        saveLog(joinPoint, time, null);
    }

    /**
     * 存储log
     */
    private void saveLog(JoinPoint joinPoint, long time, Throwable e) {
        LoginUserDTO currentUser = SecurityUtils.getCurrentUser();
        // 获取request
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // TODO 是否记录未登录用户的信息
        if(null == currentUser){
            currentUser = LoginUserDTO.builder()
                    .id(0L)
                    .username("未登录用户")
                    .build();
            updateUserInfo(request,currentUser);
        }
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        boolean insertDB = false;
        SysLog sysLog = new SysLog();
        Log logAnnotation = method.getAnnotation(Log.class);
        if (logAnnotation != null) {
            // 注解上的描述
            int operate = logAnnotation.value();
            //String operation = CoreUtils.getOperation(operate);
            sysLog.setOperation(operate);
            // 注解上的模块类型
            int type = logAnnotation.type();
            String module = CoreUtils.getModule(type);
            sysLog.setLogType(type);
            // 注解上的日志描述
            String desc = currentUser.getUsername() + "在 " + module + " 调用了 " + logAnnotation.description() + " 的方法";
            sysLog.setLogDesc(desc);
            sysLog.setUsername(currentUser.getUsername());
            insertDB = logAnnotation.insertDB();
        }
        // 请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setLogMethod(className + "." + methodName + "()");
        // 请求的方法参数值
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        if (args != null && paramNames != null) {
            String params = "";
            for (int i = 0; i < args.length; i++) {
                params += "  " + paramNames[i] + ": " + args[i];
            }
            sysLog.setLogParams(params);
        }

        // 设置IP地址
        sysLog.setLogIp(IPUtils.getIpAddr(request));
        request.getRemoteAddr();
        sysLog.setLogUser(currentUser.getId());
        sysLog.setLogTime(time);
        sysLog.setCreateTime(new Date());

        // 加入异常的信息
        if(null != e){
            sysLog.setExceptionCode(e.getClass().getName());
            sysLog.setExceptionDetail(e.getMessage());
        }
        // TODO 保存系统日志,入库,这里模拟打出信息即可
        log.debug(sysLog.getLog());

        if (insertDB) {
            sysLogService.saveLog(sysLog);
        }
    }

    /**
     * 访问openApi接口时 设置用户信息
     */
    private void updateUserInfo(HttpServletRequest request, LoginUserDTO user){
        String path = request.getServletPath();
        if(PermitAllUrl.OPENAPIURL.contains(path)){
            String token = request.getHeader("token");
            if(!StringUtils.isEmpty(token)){
                try {
                    CollaborativeProject projectByToken = projectService.findProjectByToken(token);
                    if (projectByToken != null) {
                        user.setUsername(projectByToken.getProjectName());
                        user.setActualName(projectByToken.getProjectName());
                    }
                } catch (Exception e) {
                    log.info("获取合作渠道信息失败! 异常信息:{}",e.getMessage());
                }

            }
        }
    }

}
