package com.heyu.api.aop;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.heyu.api.data.constants.ApiConstants;
import com.heyu.api.data.dto.BuyerDTO;
import com.heyu.api.data.dto.VVRequestLogDTO;
import com.heyu.api.data.dto.vv.VVAdminDTO;
import com.heyu.api.data.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 日志aop
 * Created by wutao on 2018/10/12.
 */
@Aspect
@Component
@Order(1)
@Slf4j
public class ZhenZhenLogAop {

    private String BLANK_SPACE = " ";

    @Autowired
    private RedisUtils redisUtils;


    @Value("${eb.config.rabbitQueue.urlStatisticQueue}")
    private String urlStatisticQueue;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public final static  String anonymous_login_url = "/app/user/anonymous/login";
    public final static  String user_login_url = "/app/user/login";

    public final static  String _288_anonymous_login_code = "288";
    public final static  String _289_user_login_code = "289";

    public final static  String AppRealyLogin = "AppRealyLogin";


    public final static List<String> not_login_urls = Arrays.asList(user_login_url, anonymous_login_url,
            "/app/weixin/payNotify","/app/weixin/refundNotify","/app/weixin/customer/verify","/app/weixin/customer/receiveMessage");


    public Map<String,String> classHasAnnotation = new HashMap();

    @Pointcut(value = "(" + "    @within(org.springframework.web.bind.annotation.RestController) || " + "    @within(org.springframework.stereotype.Controller)" + ")")
    public void pointcut() {


    }

    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        Object result = null;
        StringBuilder sb = new StringBuilder();
        String methodName = "";
        String ip = "";
        String args = "";
        String className = "";
        String uri = "";
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String traceId = attributes.getRequest().getHeader(ApiConstants.trace_id);
        if (StringUtils.isBlank(traceId)) {
            traceId = OrderUtil.getUserPoolOrder(ApiConstants.on);
        }
        Long buyerId = null;
        Long startTime = System.currentTimeMillis();
        HttpServletRequest request = attributes.getRequest();
        uri = attributes.getRequest().getRequestURI();


        String describe = null;
        log.info("ZhenZhenLogAop uri :{} ", uri);
        // 如果不是小程序的url ，则直接过滤掉
        if (!uri.startsWith(ApiConstants.MM_URI_START) && !uri.startsWith(ApiConstants.APP_URI_START)) {
            return point.proceed();
        }

        String token = "";
        try {
            MDC.put("traceId", traceId);
            args = recordRequestLog(point.getArgs(), uri);
            Signature sig = point.getSignature();

            Method method = ((MethodSignature) sig).getMethod();
            if (sig instanceof MethodSignature) {
                methodName = ((MethodSignature) sig).getMethod().getName();
            }
            describe = AnnotationUtils.getAnnotationValueByMethod(method, "Describe", "value");
            Class clazz = point.getTarget().getClass();
            className = clazz.getName();

            Object[] argArrs = point.getArgs();
            if (uri.startsWith(ApiConstants.APP_URI_START)) {
                String neeeRealyLogin = classHasAnnotation.get(uri);              //是否需要真登录
                if(StringUtils.isBlank(neeeRealyLogin)){
                    boolean hasClassAnnotation = AnnotationUtils.hasAnnotation(clazz, AppRealyLogin);
                    boolean hasMethodAnnotation = AnnotationUtils.hasAnnotation(method, AppRealyLogin);

                    if(hasClassAnnotation || hasMethodAnnotation){
                        neeeRealyLogin = "1";
                    }else {
                        neeeRealyLogin = "0";
                    }
                    classHasAnnotation.put(uri,neeeRealyLogin);
                }
                token = attributes.getRequest().getHeader(ApiConstants.APP_TOKEN);
                BuyerDTO buyerDTO = redisUtils.get(token, BuyerDTO.class);
                // 如果不是匿名用户登录
                if (buyerDTO ==null && !not_login_urls.contains(uri)) {
                    if("1".equals(neeeRealyLogin)){
                        return R.error(_289_user_login_code, "请调用/app/user/login接口登录");    // 假登录
                    }
                    return R.error(_288_anonymous_login_code, "请调用/app/user/anonymous/login接口登录");    // 假登录
                }
                // 参数封装
                if (argArrs != null && argArrs.length > 0) {
                    for (Object argArr : argArrs) {
                        List<Method> methods = new ArrayList<>();
                        SanUtils.getClassDeclaredMethods(argArr.getClass(), methods);
                        if (buyerDTO != null) {
                            for (Method m : methods) {
                                if (ApiConstants.SET_TOKEN.equalsIgnoreCase(m.getName())) {
                                    m.setAccessible(true);
                                    m.invoke(argArr, new Object[]{token});
                                }
                                if (ApiConstants.SET_BUYER_ID.equalsIgnoreCase(m.getName())) {
                                    m.setAccessible(true);
                                    m.invoke(argArr, new Object[]{buyerDTO.getBuyerId()});
                                }
                                if (ApiConstants.SET_BUYER_NAME.equalsIgnoreCase(m.getName())) {
                                    m.setAccessible(true);
                                    m.invoke(argArr, new Object[]{buyerDTO.getBuyerName()});
                                }
                            }
                        }
                    }
                }
            } else if (uri.startsWith(ApiConstants.MM_URI_START)) {
                token = attributes.getRequest().getHeader(ApiConstants.MM_TOKEN);
                Object tokenValue = redisUtils.get(token);
                boolean adminNotNeedLogin = AnnotationUtils.hasAnnotation(clazz, "AdminNotNeedLogin");
                // 登录不拦截
                if (!adminNotNeedLogin && tokenValue == null) {
                    // return R.error("请登录");
                }
                if (tokenValue != null) {
                    try {
                        VVAdminDTO vvAdminDTO = JSONObject.parseObject(tokenValue + "", VVAdminDTO.class);
                        if (vvAdminDTO != null && argArrs != null && argArrs.length > 0) {
                            for (Object argArr : argArrs) {
                                argArr.getClass().getDeclaredMethods();
                                List<Method> methods = new ArrayList<>();
                                SanUtils.getClassDeclaredMethods(argArr.getClass(), methods);
                                for (Method m : methods) {
                                    if (ApiConstants.SET_USER_NAME.equalsIgnoreCase(m.getName())) {
                                        m.setAccessible(true);
                                        m.invoke(argArr, new Object[]{vvAdminDTO.getUsername()});
                                    }
                                    if (ApiConstants.SET_ADMIN_ID.equals(m.getName())) {
                                        m.setAccessible(true);
                                        m.invoke(argArr, new Object[]{vvAdminDTO.getAdminId()});
                                    }
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }

            result = point.proceed();
            if (result instanceof R) {
                R r = (R) result;
                Object object = r.getData();
                convertImage(object, token);
            }
        } catch (Exception e) {
            log.error("LogAop set error " + sb.toString(), e);
        } finally {
            printLog(result, args, ip, className, methodName, uri, token, traceId, startTime, buyerId, describe);
            MDC.remove("traceId");
        }
        return result;
    }


    public static void convertImage(Object object, String token) {
        if (object == null) {
            return;
        }

        if (object instanceof List) {
            List list = (List) object;
            for (Object o : list) {
                convertImage(o, token);
            }
        } else if (object instanceof Map) {
            Map map = (Map) object;
            for (Object key : map.keySet()) {
                Object value = map.get(key);
                convertImage(value, token);
            }
        } else if (object.getClass().isArray()) {
            Object[] array = (Object[]) object;
            for (Object o : array) {
                convertImage(o, token);
            }
        } else if (SanUtils.isBasicDataTypes(object.getClass())) {
            return;
        } else {
            List<Method> methods = new ArrayList<>();
            SanUtils.getClassDeclaredMethods(object.getClass(), methods);
            Map<String, Method> methodMap = new HashMap<>();
            for (Method method : methods) {
                methodMap.put(method.getName(), method);
            }
            for (Method method : methods) {
                try {
                    method.setAccessible(true);
                    if (method.getName().startsWith(ApiConstants.get)) {
                        Object image = method.invoke(object);
                        // 如果是基本数据类型
                        if (SanUtils.isBasicDataTypes(method.getReturnType())) {
                            if (method.getReturnType() == String.class) {
                                String str = (String) image;
                                if (str != null
                                        && str.startsWith("https://heyuimage.ihzhy.com")
                                ) {
                                    String newImage = str;
                                    if (str.contains(ApiConstants.ACCESS_TOKEN_EQUALS)) {
                                        String urlArray[] = str.split(ApiConstants.ACCESS_TOKEN_EQUALS);
                                        newImage = urlArray[0] + ApiConstants.QUESTION_MARK + ApiConstants.ACCESS_TOKEN_EQUALS + token;
                                    } else {
                                        newImage = str + ApiConstants.QUESTION_MARK + ApiConstants.ACCESS_TOKEN_EQUALS + token;
                                    }
                                    String setMethodName = ApiConstants.set + method.getName().substring(3);
                                    Method setMethod = methodMap.get(setMethodName);
                                    setMethod.invoke(object, new Object[]{newImage});
                                }
                            }
                        } else {
                            convertImage(image, token);
                        }
                    }
                } catch (Exception e) {
                    log.error("LogAop convertImage error " + method.getName() + ",Object:{}" + JSON.toJSONString(object), e);
                }
            }
        }
    }


    public void printLog(Object result, String args, String ip, String className, String methodName,
                         String preUri, String token, String traceId, Long startTime, Long buyerId,
                         String describe
    ) {
        StringBuilder sb = new StringBuilder();

        sb.append("ZhenZhenLogAop args:").append(args).append(BLANK_SPACE)
                .append("ip:").append(ip).append(BLANK_SPACE)
                .append("class:").append(className).append("#").append(methodName).append(BLANK_SPACE)
                .append("uri:").append(preUri).append(BLANK_SPACE)
                .append("result:").append(JSON.toJSONString(result));
        log.info(sb.toString());

        rabbitTemplate.convertAndSend(urlStatisticQueue,
                JSON.toJSONString(new VVRequestLogDTO(
                                token, // token
                                null,
                                traceId,
                                System.currentTimeMillis(), // 当前请求的接口
                                preUri, //日志编号
                                System.currentTimeMillis() - startTime,
                                args,
                                buyerId,
                                describe
                        )
                ));
    }


    private String recordRequestLog(Object[] argArrs, String uri) {
        String mediaType = "";
        String args = null;
        try {
            List<Object> list = new ArrayList<>();
            if (argArrs != null && argArrs.length > 0) {
                for (Object arg1 : argArrs) {
                    if (arg1 instanceof MultipartFile) {
                        continue;
                    } else if (arg1 instanceof HttpServletRequest) {
                        continue;
                    } else if (arg1 instanceof HttpServletResponse) {
                        continue;
                    } else if (arg1 instanceof MultipartFile[]) {
                        continue;
                    } else {
                        list.add(arg1);
                    }
                }
            }
            Object jsonData = list.size() == 1 ? list.get(0) : list;
            args = JSON.toJSONString(jsonData);
            return args;
        } catch (Exception e) {
            log.error("url " + uri + " , uri " + uri + " ,mediaType " + mediaType + ", 打印请求日志错误" + args, e);
        }

        return "";
    }
}
