package com.ho.business.config;

import com.alibaba.fastjson.JSON;
import com.ho.common.tools.annotation.*;
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.service.RedisService;
import com.ho.common.tools.util.IPUtils;
import com.ho.common.tools.vo.req.PositionVo;
import com.ho.user.api.entity.SysLog;
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;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 业务模块切面
 * 目前实现日志和token校验
 */
@Aspect
@Component
@Slf4j
public class BusinessAspect {

    @Autowired
    RedisService redisService;

    @Autowired
    SaveLogComponent saveLogComponent;

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

    }

    /**
     * 环绕增强,相当于MethodInterceptor
     *
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        ServletRequestAttributes servletAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletAttributes.getRequest();
        UserDetailRespVO userDetail = null;
        SimpleUser simpleUser = null;
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        //是否是token忽略
        TokenIgnore tokenIgnoreAnnotation = method.getAnnotation(TokenIgnore.class);
        //是否大屏token
        LargeScreenToken largeScreenToken = method.getAnnotation(LargeScreenToken.class);
        //通用大屏
        CommonLargeScreenToken commonLargeScreenToken = method.getAnnotation(CommonLargeScreenToken.class);
        String token = request.getHeader(RedisKeyConstant.User.ACCESS_TOKEN);
        log.info("token:" + token);
        String url = request.getRequestURL().toString();
        String className = point.getTarget().getClass().getName();
        String methodName = signature.getName();
        log.info("请求url:{} ,method:{}, 开始时间: {}", url, method, beginTime);
        //没有tokenIgnore注解的方法,验证token
        if (tokenIgnoreAnnotation == null) {
            //判断token是否有效
            String tokenKey = checkToken(token);
            userDetail = (UserDetailRespVO) redisService.get(tokenKey);
            String userNameKey = RedisKeyConstant.User.USER_NAME + userDetail.getLoginChannel() + ":" + userDetail.getUsername();
            simpleUser = (SimpleUser) redisService.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:{}", userDetail.getUsername(), simpleUser.getGroupId(), simpleUser.getDeptId());
            //延长token时间
            redisService.expire(tokenKey, 2, TimeUnit.HOURS);
            redisService.expire(userNameKey, 2, TimeUnit.HOURS);
        }
        //有大屏token(largeScreenToken)的接口 , 因为这个token不过期,并且它的键值对和
        if (largeScreenToken != null) {
            String tokenKey = checkLargeToken(token);
            userDetail = (UserDetailRespVO) redisService.get(tokenKey);
            String userNameKey = RedisKeyConstant.User.USER_NAME_LARGE_SCREEN + "pc:" + userDetail.getUsername();
            simpleUser = (SimpleUser) redisService.get(userNameKey);
            log.info("userName: {}, userId: {}, groupId:{}", userDetail.getUsername(), simpleUser.getUserId(), simpleUser.getGroupId());
            //大屏token做延长有效期,先不让过期
            redisService.expire(tokenKey, 365, TimeUnit.DAYS);
            redisService.expire(userNameKey, 365, TimeUnit.DAYS);

        }
        //有通用大屏接口
        if(commonLargeScreenToken != null){
            String tokenKey = RedisKeyConstant.User.TOKEN_COMMON_LARGE_SCREEN + token;
            simpleUser = (SimpleUser) redisService.get(tokenKey);
            if( simpleUser != null){
                redisService.expire(tokenKey, 30, TimeUnit.DAYS);
            }
        }
        // 按钮权限校验
        HzPermission hzPermission = method.getAnnotation(HzPermission.class);
        if (null != hzPermission) {
            String tokenKey = RedisKeyConstant.User.TOKEN + token;
            userDetail = (UserDetailRespVO) redisService.get(tokenKey);
            List<String> powerList = userDetail.getPowerList();
            Map<String, String> map = new HashMap<>();
            for (String s : powerList) {
                map.put(s, s);
            }
            String[] value = hzPermission.value();
            String str = null;
            for (String s : value) {
                str = map.get(s);
                if (str != null) {
                    break;
                }
            }
            if (null == str) {
                throw new BusinessException(BaseResponseCode.PERMISSION_DENIED);
            }
        }
        //请求参数
        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.info("methodName:{}, className:{}", methodName, className);
        LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
        //执行目标方法
        //执行方法
        Object result = point.proceed();
        //方法执行结果
        //log.info("返回:" + JSON.toJSONString(result));
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        log.info("耗时:{}, url:{},param:{}", time, url,params);
        if (time > CommonConstant.timeoutMilliSeconds) {
            log.error("接口超时: time:{}, url:{},", time,url);
        }
        //有日志注解的入库
        if (logAnnotation != null) {
            SysLog sysLog = new SysLog();
            //设置IP地址
            sysLog.setIp(IPUtils.getIpAddr(request));
            //param可能过长,只保留1500字节长度
            if (params != null && params.length() > 1500) {
                params = params.substring(0, 1500);
            }
            sysLog.setParams(params);
            sysLog.setMethod(methodName);
            sysLog.setOperation(logAnnotation.title() + "-" + logAnnotation.action());
            //保存日志
            try {
                log.info(sysLog.toString());
                saveLogComponent.saveSysLog(sysLog, userDetail, simpleUser, time);
            } catch (Exception e) {
                log.error("e={}", e);
            }
        }
        return result;
    }

    //验证token
    private String checkToken(String token) {
        if (token == null || StringUtils.isEmpty(token)) {
            throw new BusinessException(BaseResponseCode.TOKEN_ERROR);
        }
        String tokenKey = RedisKeyConstant.User.TOKEN + token;
        if (!redisService.hasKey(tokenKey)) {
            throw new BusinessException(BaseResponseCode.TOKEN_PAST_DUE);
        }
        return tokenKey;
    }

    //验证大屏token
    private String checkLargeToken(String largeToken) {
        if (largeToken == null || StringUtils.isEmpty(largeToken)) {
            throw new BusinessException(BaseResponseCode.INVALID_BIG_SCREEN_TOKEN);
        }
        String tokenKey = RedisKeyConstant.User.TOKEN_LARGE_SCREEN + largeToken;
        if (!redisService.hasKey(tokenKey)) {
            throw new BusinessException(BaseResponseCode.INVALID_BIG_SCREEN_TOKEN);
        }
        return tokenKey;
    }


}
