package com.td.boss.aspect;

import com.td.boss.annotation.Decrypt;
import com.td.boss.annotation.Encrypt;
import com.td.boss.common.pojo.Result;
import com.td.boss.config.enums.ResultEnum;
import com.td.boss.config.exception.AuthorizeException;
import com.td.boss.util.*;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
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.Value;
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.annotation.Annotation;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;

/**
 * AES + RSA 加解密AOP处理
 */
@Slf4j
@Aspect
@Component
public class SafetyAspect {

    private final JwtTokenUtil jwtTokenUtil;
    private final String tokenHeader;

    public SafetyAspect(JwtTokenUtil jwtTokenUtil, @Value("${jwt.token}") String tokenHeader) {
        this.jwtTokenUtil = jwtTokenUtil;
        this.tokenHeader = tokenHeader;
    }
    /**
     * Pointcut 切入点
     * 匹配
     * com.td.boss.sys.*.controller、
     * com.td.boss.*.controller包下面的所有方法
     */
    @Pointcut(value = "execution(public * com.td.boss.sys.*.controller.*.*(..)) || " +
            "execution(public * com.td.boss.*.controller.*.*(..)) && ! execution(public * com.td.boss.game.*.controller.*.*(..))")
    public void safetyAspect() {}

    /**
     * 环绕通知
     */
    @Around(value = "safetyAspect()")
    public Object around(ProceedingJoinPoint pjp) {
       try {

           //判断api加密开关是否开启
           if("N".equals(SysSettingUtil.getSysSetting().getSysApiEncrypt())){
               return pjp.proceed(pjp.getArgs());
           }

            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            assert attributes != null;
            //request对象
            HttpServletRequest request = attributes.getRequest();

            //http请求方法  post get
            String httpMethod = request.getMethod().toLowerCase();

            //method方法
            Method method = ((MethodSignature) pjp.getSignature()).getMethod();

            //method方法上面的注解
            Annotation[] annotations = method.getAnnotations();

            //方法的形参参数
            Object[] args = pjp.getArgs();

            //是否有@Decrypt
            boolean hasDecrypt = false;
            //是否有@Encrypt
            boolean hasEncrypt = false;
            for (Annotation annotation : annotations) {
                if (annotation.annotationType() == Decrypt.class) {
                    hasDecrypt = true;
                }
                if (annotation.annotationType() == Encrypt.class) {
                    hasEncrypt = true;
                }
            }

            //前端公钥
            String publicKey = null;

            //jackson
            ObjectMapper mapper = new ObjectMapper();
            //jackson 序列化和反序列化 date处理
            mapper.setDateFormat( new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

            //执行方法之前解密，且只拦截post请求
            if ("post".equals(httpMethod) && hasDecrypt) {
                //AES加密后的数据
                String data = request.getParameter("data");
                //后端RSA公钥加密后的AES的key
                String aesKey = request.getParameter("aesKey");
                //前端公钥
                publicKey = request.getParameter("publicKey");

                log.info("前端公钥：" + publicKey);

                //后端私钥解密的到AES的key
                byte[] plaintext = RsaUtil.decryptByPrivateKey(Base64.decodeBase64(aesKey), RsaUtil.getPrivateKey());
                aesKey = new String(plaintext);
                log.info("解密出来的AES的key：" + aesKey);


                //AES解密得到明文data数据
                String decrypt = AesUtil.decrypt(data, aesKey);
                log.info("解密出来的data数据：" + decrypt);

                //设置到方法的形参中，目前只能设置只有一个参数的情况
                mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

                //注：参数最好用Vo对象来接参，单用String来接，args有长度但获取为空，很奇怪不知道为什么
                if(args.length > 0){
                    args[0] = mapper.readValue(decrypt, args[0].getClass());
                }
            }

            //执行并替换最新形参参数   PS：这里有一个需要注意的地方，method方法必须是要public修饰的才能设置值，private的设置不了
            Object o = pjp.proceed(args);

            //返回结果之前加密
            if (hasEncrypt) {
                mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                //每次响应之前随机获取AES的key，加密data数据
                String key = AesUtil.getKey();
                log.info("AES的key：" + key);
                String dataString = mapper.writeValueAsString(o);
                log.info("需要加密的data数据：" + dataString);
                String data = AesUtil.encrypt(dataString, key);

                //用前端的公钥来解密AES的key，并转成Base64
                String aesKey = Base64.encodeBase64String(RsaUtil.encryptByPublicKey(key.getBytes(), publicKey));

                //转json字符串并转成Object对象，设置到Result中并赋值给返回值o
                o = Result.of(mapper.readValue("{\"data\":\"" + data + "\",\"aesKey\":\"" + aesKey + "\"}", Object.class));
            }

            //返回
            return o;

        } catch (Throwable e) {
           //输出到日志文件中
           log.error(ErrorUtil.errorInfoToString(e));
            return Result.of(null, false, "加解密异常：\n\t" + e.getMessage());
        }
    }

//
//    @Pointcut(value = "execution(public * com.td.boss.game.*.controller.*.*(..))")
//    public void gameAspect(){}
//
//    @Before(value = "gameAspect()")
//    public void gameBefore(JoinPoint joinPoint) throws Exception {
//        //获取上下文request，获取token并解析
//        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//        HttpServletRequest request = requestAttributes.getRequest();
//        //todo  token 操作
//        final String requestHeader = request.getHeader(this.tokenHeader);
//        String userId = null;
//        String authToken = null;
//
//        if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
//            authToken = requestHeader.substring(7);
//            try {
//                userId = jwtTokenUtil.getUserIdFromToken(authToken);
//                log.info("userId:{}", userId);
//                //todo 如果需要校验的话，取本地或者数据库一个userId校验
//                log.info("validateToken:{}", jwtTokenUtil.validateToken(authToken, userId));
//                //获取参数并赋值
//                Object[] args1 = joinPoint.getArgs();
//            } catch (ExpiredJwtException e) {
//            }
//        }else{
////            throw  new ExpiredJwtException(null,null,"error");
////            throw new AuthorizeException(ResultEnum.TOKEN_IS_ILLEGAL.getCode(),
////                    ResultEnum.TOKEN_IS_ILLEGAL.getMessage());
//        }
//    }



}
