package com.jwipc.log.annotation.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.jwipc.constant.OaProcessType;
import com.jwipc.constant.ShareProcessType;
import com.jwipc.log.annotation.ApiOperateLog;
import com.jwipc.log.constant.ApiDirection;
import com.jwipc.log.constant.ThirdSysType;
import com.jwipc.log.dto.OaCallBackLogDTO;
import com.jwipc.log.dto.ShareCallBackDTO;
import com.jwipc.log.factory.ApiLogTaskFactory;
import com.jwipc.log.logic.LogManager;
import com.jwipc.util.RequestUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Objects;

/**
 * @author shenyang.ou
 * @date 2024/3/18 14:20
 * @desc 自定义api操作日志注解切面处理类
 */

@Aspect
@Component
@Order(value = 1)
public class ApiOperateLogAspect {
    private static Logger logger = LoggerFactory.getLogger(ApiOperateLogAspect.class);
    //开始时间
    private final ThreadLocal<Long> startTime = new ThreadLocal<>();
    private final ThreadLocal<LocalDateTime> startTimeLocal = new ThreadLocal<>();
    private final ThreadLocal<String> body = new ThreadLocal<>();
    private final ThreadLocal<String> info = new ThreadLocal<>();

    /**
     * 切入点
     */
    @Pointcut("@annotation(com.jwipc.log.annotation.ApiOperateLog)")
    public void ApiOperateLog() {
    }

    /**
     * 环绕通知
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("ApiOperateLog()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        ApiOperateLog apiOperateLog = method.getAnnotation(ApiOperateLog.class);
        //结果
        Object result = joinPoint.proceed();
        //请求结束时间戳
        long end = System.currentTimeMillis();
        info.set(info.get() + StrUtil.format("请求耗时：{}秒\n", (end - startTime.get()) / 1000));
        logger.info("around{}", info.get());
        saveLog(apiOperateLog, JSON.toJSONString(result));
        return result;
    }

    @Before("ApiOperateLog()")
    public void before(JoinPoint joinPoint) {
        startTime.set(System.currentTimeMillis());
        startTimeLocal.set(LocalDateTime.now());
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = null;
        if (Objects.nonNull(attributes)) {
            request = attributes.getRequest();
        }

        Signature signature = joinPoint.getSignature();
        info(request, signature);
        //获取入参
        Object[] args = joinPoint.getArgs();
        Object safeArgs = buildSafeLogArgs(args);
        body.set(JSON.toJSONString(safeArgs));
    }

    @AfterThrowing(value = "ApiOperateLog()", throwing = "exception")
    public void afterThrowing(JoinPoint joinPoint, Exception exception) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        ApiOperateLog apiOperateLog = method.getAnnotation(ApiOperateLog.class);
        //请求结束时间戳
        long end = System.currentTimeMillis();
        info.set(info.get() + StrUtil.format("请求耗时：{}秒\n", (end - startTime.get()) / 1000));
        logger.info("afterThrowing:{}", info.get());
        saveLog(apiOperateLog, exception.getMessage());
    }

    public void info(HttpServletRequest request, Signature signature) {
        if (Objects.isNull(request)) {
            return;
        }
        StringBuilder stringBuilder = new StringBuilder();
        //请求类型
        stringBuilder.append(StrUtil.format("接口类型：{}\n", request.getMethod()));
        //请求方法
        stringBuilder.append(StrUtil.format("请求方法：{}.{}\n", signature.getDeclaringTypeName(), signature.getName()));
        //请求IP
        stringBuilder.append(StrUtil.format("请求IP：{}\n", RequestUtil.getIpAddress(request)));
        info.set(stringBuilder.toString());
    }

    public void saveLog(ApiOperateLog apiOperateLog, String result) {
        if (apiOperateLog.direction().getStatus() == ApiDirection.OTHER_TO_SRM.getStatus()) {
            String name = apiOperateLog.name();
            //如果是OA回调获取OA流程名称
            if (apiOperateLog.thirdSysType().getType().equals(ThirdSysType.OA.getType())) {
                try {
                    final OaCallBackLogDTO oaCallBackLogDTO = JSON.parseObject(body.get(), OaCallBackLogDTO.class);
                    name = OaProcessType.getTitle(oaCallBackLogDTO.getIT_DATA().type);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            //如果是共享回调获取OA流程名称
            if (apiOperateLog.thirdSysType().getType().equals(ThirdSysType.SHARE.getType())) {
                try {
                    final ShareCallBackDTO oaCallBackLogDTO = JSON.parseObject(body.get(), ShareCallBackDTO.class);
                    name = ShareProcessType.getTitle(oaCallBackLogDTO.getIT_DATA().type);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            LogManager.me().executeLog(ApiLogTaskFactory.saveLog(apiOperateLog.code(), name,
                    apiOperateLog.thirdSysType().getType(),
                    body.get(),
                    result,
                    info.get(),
                    startTimeLocal.get()
            ));
        } else {
            //调用方日志
            LogManager.me().executeLog(ApiLogTaskFactory.saveCallLog(apiOperateLog.code(), apiOperateLog.name(),
                    apiOperateLog.thirdSysType().getType(),
                    body.get(),
                    result,
                    info.get(),
                    startTimeLocal.get()
            ));
        }
    }

    /**
     * 构建安全的接口日志参数
     * @param args 参数
     * @return 处理后的参数
     */
    private Object buildSafeLogArgs(Object[] args) {
        if (args == null) {
            return null;
        }

        Object[] safeArgs = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            safeArgs[i] = convertArg(args[i]);
        }
        return safeArgs;
    }

    private Object convertArg(Object arg) {
        if (arg == null) {
            return null;
        }

        // byte[]
        if (arg instanceof byte[]) {
            return StrUtil.format("byte[{}]", ((byte[]) arg).length);
        }

        // Byte[]
        if (arg instanceof Byte[]) {
            return StrUtil.format("Byte[{}]", ((Byte[]) arg).length);
        }

        // 其他对象，正常返回
        return arg;
    }

}
