package com.ho.user.aop;

import cn.hutool.core.lang.Snowflake;
import com.alibaba.fastjson.JSON;
import com.ho.common.tools.annotation.LogAnnotation;
import com.ho.common.tools.annotation.TokenIgnore;
import com.ho.common.tools.constant.CommonConstant;
import com.ho.common.tools.constant.RedisKeyConstant;
import com.ho.common.tools.entity.SimpleUser;
import com.ho.common.tools.entity.UserDetailRespVO;
import com.ho.common.tools.exception.BaseResponseCode;
import com.ho.common.tools.exception.BusinessException;
import com.ho.common.tools.util.IPUtils;
import com.ho.common.tools.util.RedisCommon;
import com.ho.user.api.entity.SysLog;
import com.ho.user.mapper.SysLogMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
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.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;


@Aspect
@Component
@Slf4j
public class SysLogAspect {

    @Autowired
    RedisCommon redisCommon;

    @Autowired
    Snowflake snowflake;

    @Autowired
    private SysLogMapper sysLogMapper;


    /**
     * 此处的切点是注解的方式
     * 只要出现 @LogAnnotation注解都会进入
     * 改为切入点是controller,不然日志记录不清晰,只有LogAnnotation才入库
     */
    //@Pointcut("@annotation(com.ho.common.tools.annotation.LogAnnotation)")
    @Pointcut("execution(* com.ho.*.controller.*.*(..))")
    public void logPointCut() {

    }

    /**
     * 环绕增强,相当于MethodInterceptor
     *
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        ServletRequestAttributes servletAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletAttributes.getRequest();
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        String url = request.getRequestURL().toString();
        String className = point.getTarget().getClass().getName();
        String methodName = signature.getName();
        log.info("请求url:{} ,method:{}, 开始时间: {}", url, method, beginTime);
        //请求参数
        String params = null;
        try {
            //请求的参数
            Object[] args = point.getArgs();
            //MultipartFile类型的跳过转Json
            if (args != null && args.length > 0 && args[0] != null) {
                String simpleClassName = args[0].getClass().getSimpleName();
                log.info("SimpleClassName:" + simpleClassName);
                //不包含文件类型才打印日志
                if (!StringUtils.isBlank(simpleClassName) && !simpleClassName.contains("Multipart")) {
                    params = JSON.toJSONString(args[0]);
                    log.info("params:" + params);
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        UserDetailRespVO user = null;
        SimpleUser simpleUser = null;
        //是否是token忽略
        TokenIgnore tokenIgnoreAnnotation = method.getAnnotation(TokenIgnore.class);
        if (tokenIgnoreAnnotation == null) {
            log.info("methodName:{}, className:{}", methodName, className);
            String token = request.getHeader(RedisKeyConstant.User.ACCESS_TOKEN);

            if (!StringUtils.isEmpty(token)) {
                log.info("token:" + token);
                //如果是登陆接口,忽略token判断
                String tokenKey = RedisKeyConstant.User.TOKEN + token;
                //用户信息
                user = (UserDetailRespVO) redisCommon.get(tokenKey);
                String userNameKey = RedisKeyConstant.User.USER_NAME + user.getLoginChannel() + ":" + user.getUsername();
                simpleUser = (SimpleUser) redisCommon.get(userNameKey);
                if(simpleUser ==null){
                    log.info("User token has expired ! token :{}", token);
                    throw new BusinessException(BaseResponseCode.TOKEN_PARSE_ERROR);
                }
                log.info("userName: {}, groupId:{}, deptId:{}", user.getUsername(), simpleUser.getGroupId(), simpleUser.getDeptId());
            }
        }
        //日志注解
        LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
        //执行目标方法
        //执行方法
        Object result = point.proceed();
        //方法执行结果
//        log.info("返回:" + JSON.toJSONString(result));
        //log.info("返回");
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        log.info("耗时:{}, url:{}" ,time, url);
        if(time> CommonConstant.timeoutMilliSeconds){
            log.error("接口超时: url:{}" ,url);
        }
        //有日志注解的入库
        if (logAnnotation != null) {
            SysLog sysLog = new SysLog();
            //设置IP地址
            sysLog.setIp(IPUtils.getIpAddr(request));
            sysLog.setParams(params);
            sysLog.setMethod(methodName);
            sysLog.setOperation(logAnnotation.title() + "-" + logAnnotation.action());
            //保存日志
            try {
                saveSysLog(sysLog, user, simpleUser, time);
            } catch (Exception e) {
                log.error("e={}", e);
            }
        }
        return result;
    }

    //保存日志
    private void saveSysLog(SysLog sysLog, UserDetailRespVO user, SimpleUser simpleUser, long time) {
        sysLog.setId(snowflake.nextId());
        sysLog.setGroupId(simpleUser.getGroupId());
        sysLog.setDeptId(simpleUser.getDeptId());
        sysLog.setUsername(user.getUsername());
        sysLog.setUserId(user.getUserId());
        sysLog.setTime((int) time);
        log.info(sysLog.toString());
        sysLogMapper.insertSelective(sysLog);
    }
}
