package org.jeecg.config.aop.logprint;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.jeecg.config.aop.logprint.annotation.LogPrintPlus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 切面AOP记录日志信息
 * @author wsc
 */
@Aspect
@Component
public class SystemLogPrintAspect {
    private static final Logger logger = LoggerFactory.getLogger(SystemLogPrintAspect.class);

    /**
     * 1，表示在哪个类的哪个方法进行切入。配置有切入点表达式。
     * 2，对有@SystemLog标记的方法,记录其执行参数及返回结果。
     */
    @Pointcut("@annotation(org.jeecg.config.aop.logprint.annotation.LogPrintPlus)")
    public void aspect() {
    }

    /**
     * 前置通知
     */
    @Before("aspect()")
    public void before(JoinPoint point) throws Throwable {
        try {
            StringBuffer sb = new StringBuffer();
            ThreadLocalUtil.setTlsb(sb);
            ThreadLocalUtil.append("***********开始*************");

            // 获取接口的路径地址
            String url = point.getTarget().getClass().getName() + "." + point.getSignature().getName() + "()";
            // 获取接口的请求参数
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            LogPrintPlus log = method.getAnnotation(LogPrintPlus.class);
            String desc = log.desc();
            String moudle = log.moudle();

            String paramsStr = Arrays.toString(point.getArgs());
            String params = JSON.toJSONString(paramsStr);
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            ThreadLocalUtil.append("模块名称", moudle);
            ThreadLocalUtil.append("详情描述", desc);
            ThreadLocalUtil.append("请求时间", DateUtil.now());
            ThreadLocalUtil.append("请求方法", url);
            if (requestAttributes != null){
                HttpServletRequest request = requestAttributes.getRequest();
                ThreadLocalUtil.append("请求类型", request.getMethod());
                ThreadLocalUtil.append("请求地址", request.getRequestURL());
            }
            ThreadLocalUtil.append("请求参数", params);

        } catch (Exception e) {
            logger.error("日志切面前置异常：", e);
        }
    }


    @AfterReturning(returning = "ret", pointcut = "aspect()")
    public void afterReturning(JoinPoint joinPoint,Object ret) throws Throwable {
        extracted(joinPoint,ret, true);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "aspect()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        extracted(joinPoint,e, false);
    }

    /**
     * 提取后置处理方法
     *
     * @param ret       出参
     * @param isSuccess 是否正常
     */
    private static void extracted(JoinPoint point,Object ret, boolean isSuccess) {

        try {
            String result;
            if (isSuccess) {
                result = JSON.toJSONString(ret);
                if ("null".equals(result)||result ==null){
                    result = "";
                }
            } else {
                if (ret == null) {
                    result = "java.lang.NullPointerException";
                } else {
                    Exception ex = (Exception) ret;
                    result = ex.getMessage();
                }
            }
            ThreadLocalUtil.append("返回时间",DateUtil.now());
            ThreadLocalUtil.append("交易状态",isSuccess);
            ThreadLocalUtil.append("返回参数",result);
            ThreadLocalUtil.append("***********结束*************");

            StringBuffer sb = ThreadLocalUtil.getTlsb();

            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            LogPrintPlus loggers = method.getAnnotation(LogPrintPlus.class);

            if (loggers.isPrint()){
                extractedStr(sb);
            }

        } catch (Exception e) {
            logger.error("日志切面后置异常:", e);
        } finally {
            ThreadLocalUtil.removeTlsb();
        }
    }

    /**
     * 提取str
     *
     * @param
     */
    private static void extractedStr(StringBuffer sb) {
        String str = sb.toString();
        List<String> split = StrUtil.split(str, "\n");

        List<LogPrintInfo> logInfos = new ArrayList<>();
        Integer num = 1;

        //从日志中提取日志对象
        for (String info : split) {

            String time = StrUtil.subBetween(info, "《", "》");
            String thread = StrUtil.sub(info, info.lastIndexOf("《")+1, info.lastIndexOf("》"));

            String operate = StrUtil.sub(info, info.lastIndexOf("》")+1, info.length());
            if (StrUtil.isBlank(operate)){
                continue;
            }

            logInfos.add(LogPrintInfo.builder().time(time).thread(thread).operate(operate).num(num).build());

            num++;
        }

        //第一条记录 为主线程 获取主线程名称
        String finalThreadMain = logInfos.stream().min(Comparator.comparingInt(LogPrintInfo::getNum)).get().getThread();
        //获取主线程集合
        List<LogPrintInfo> logInfoMains = logInfos.stream().filter(o -> o.getThread().equals(finalThreadMain)).collect(Collectors.toList());

        int[] ints = logInfoMains.stream().mapToInt(o -> o.getNum()).toArray();

        List<Integer> list = IsSeries(ints, 1, logInfoMains.size());

        //只有主线程 没有异步线程
        if (list.size() == 0){
            logger.info("\n"+str);
            return;
        }
        //模型为 主线程 -> 线程次（异步线程）-> 主线程

        //获取主线程中断后的一条
        Integer maxNum = list.stream().max(Integer::compareTo).get();

        //获取主线程开始
        List<LogPrintInfo> logInfoMainsStart = logInfoMains.stream().filter(o -> o.getNum() < maxNum)
                .sorted(Comparator.comparing(LogPrintInfo::getNum))
                .collect(Collectors.toList());
        //主线程结束
        List<LogPrintInfo> logInfoMainsEnd = logInfoMains.stream().filter(o -> o.getNum() > maxNum)
                .sorted(Comparator.comparing(LogPrintInfo::getNum))
                .collect(Collectors.toList());

        //异步线程 排序 线程名称  时间
        List<LogPrintInfo> logInfoThreads = logInfos.stream()
                .filter(o -> !o.getThread().equals(finalThreadMain))
                .sorted(Comparator.comparing(LogPrintInfo::getThread).thenComparing(LogPrintInfo::getTime))
                .collect(Collectors.toList());

        logInfos.clear();
        logInfos.addAll(logInfoMainsStart);
        logInfos.addAll(logInfoThreads);
        logInfos.addAll(logInfoMainsEnd);

        //重新组排后的日志打印
        StringBuffer sbs = new StringBuffer();
        logInfos.forEach(o->sbs.append(StrUtil.format("《{}》《{}》{}\n",
                o.getTime(),o.getThread(),o.getOperate())));

        logger.info("\n"+sbs);

    }

    /**
     * 获取一个数组中部连续的项
     * @param num 目标数组
     * @param start 数组的开始项
     * @param end 数组的结束项
     * @return
     */
    private static List<Integer> IsSeries(int[] num, int start, int end) {

        List<Integer> r = new ArrayList<Integer>();
        // 循环数组中所有的数据
        for (int i = 0; i < num.length; i++) {
            // 判断是不是到了数组末尾
            if (i < num.length - 1) {
                // 排除重复数据的情况
                // 当数据不连续的时候
                if (num[i] + 1 != num[i + 1]) {
                    // 当数据处于循环再开始时
                    if (num[i + 1] - num[i] < 0) {
                        // 判断最后的数字是不是最大的开始数字start
                        int cha = end - num[i];
                        for (int j = 1; j <= cha; j++) {
                            r.add(num[i] + 1 * j);
                        }
                        // 判断刚开始的数字是不是end
                        int cha1 = num[i + 1] - start;
                        for (int j = 0; j < cha1; j++) {
                            r.add(start + 1 * j);
                        }
                    } else {
                        // 处理既不连续又不是开头和结尾的情况
                        int cha = num[i + 1] - num[i];
                        for (int j = 1; j < cha; j++) {
                            r.add(num[i] + 1 * j);
                        }
                    }

                }

            }
        }
        return r;

    }

}
