package com.YuyeTech.TPlat.aspect;

import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import com.YuyeTech.TPlat.annotation.RedisUpdate;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.YuyeTech.TPlat.annotation.Decrypt;
import com.YuyeTech.TPlat.annotation.Encrypt;
import com.YuyeTech.TPlat.common.pojo.Result;
import com.YuyeTech.TPlat.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
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.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 {

    @Autowired
    private RedisSettingMap redisSettingMap;

    private final JwtTokenUtil jwtTokenUtil;
    private final String tokenHeader;

    public SafetyAspect(JwtTokenUtil jwtTokenUtil, @Value("${jwt.token}") String tokenHeader) {
        this.jwtTokenUtil = jwtTokenUtil;
        this.tokenHeader = tokenHeader;
    }

    /**
     * Pointcut 切入点
     */
    @Pointcut(value = "execution(public * com.YuyeTech.TPlat.sys.*.controller.*.*(..)) || " +
            "execution(public * com.YuyeTech.TPlat.*.controller.*.*(..))||" +
            "execution(public * com.YuyeTech.TPlat.game.*.controller.*.*(..))")
    public void safetyAspect() {
    }

    /**
     * 环绕通知
     */
    @Around(value = "safetyAspect()")
    public Object around(ProceedingJoinPoint pjp) {
        try {
            log.info("---------------进入aop---------------");
            //判断api加密开关是否开启 SysSettingUtil.getSysSetting()
            if ("N".equals(redisSettingMap.getSysSettingMap().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;
            //是否有@RedisUpdate
            boolean hasRedisUpdate = false;
            for (Annotation annotation : annotations) {
                if (annotation.annotationType() == Decrypt.class) {
                    hasDecrypt = true;
                }
                if (annotation.annotationType() == Encrypt.class) {
                    hasEncrypt = true;
                }
                if(annotation.annotationType() == RedisUpdate.class){
                    hasRedisUpdate = 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
                //切换一个方式
                log.info(redisSettingMap.getRSAPrivateKeyBase64());
                RSA rsa = new RSA(redisSettingMap.getRSAPrivateKeyBase64(), null);
                byte[] plaintext = rsa.decrypt(Base64.decodeBase64(aesKey), KeyType.PrivateKey);
                //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());
                }
                //HashMap hashMap = mapper.readValue(decrypt, HashMap.class);
                //ParameterRequestWrapper parameterRequestWrapper = new ParameterRequestWrapper(request);
                //for (Object key : hashMap.keySet()) {
                //    parameterRequestWrapper.addParameter(String.valueOf(key), hashMap.get(key));
                //}
                //request = (HttpServletRequest) parameterRequestWrapper;
            }

            //执行并替换最新形参参数   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));
                //切换一个方式
                RSA rsa = new RSA(null, publicKey);
                String aesKey = Base64.encodeBase64String(rsa.encrypt(key.getBytes(), KeyType.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());
        }
    }

}
