package com.sckw.log.aspect;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.filter.ValueFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @desc: controller层接口日志打印
 * @author: yzc
 * @date: 2023-08-21 11:23
 */
@Aspect
@Component
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class LogInfoAspect {

    public static final String TIME_PATTERN = "yyyy-MM-dd HH:mm:ss:SSS";

    @Pointcut("execution(* com.sckw.*.controller..*.*(..))")
    public void cutController() {
    }

    @Around(value = "cutController()", argNames = "p")
    public Object cutController(ProceedingJoinPoint p) throws Throwable {
        return getResult(p);

    }

    @Pointcut("execution(* com.sckw.*.dubbo..*.*(..))")
    public void cutDubbo() {
    }

    @Around(value = "cutDubbo()", argNames = "p")
    public Object around(ProceedingJoinPoint p) throws Throwable {
        return getResult(p);
    }

    // 定义最大字符串长度（例如 10000 字符）
    private static final int MAX_STR_LEN = 10000;

    // 复用 ValueFilter 实例，避免重复创建
    private static final ValueFilter TRUNCATE_FILTER = (object, name, value) -> {
        if (value instanceof String) {
            String str = (String) value;
            if (str.length() > MAX_STR_LEN) {
                return str.substring(0, MAX_STR_LEN) + "...(truncated)";
            }
        }
        return value;
    };

    /**
     * 安全序列化对象，截断超大字符串
     */
    private String safeToJSONString(Object obj) {
        if (obj == null) {
            return null;
        }
        try {
            return JSON.toJSONString(obj, TRUNCATE_FILTER);
        } catch (Exception e) {
            // 如果序列化依然失败，返回简单类型信息
            return "Serialization failed: " + e.getMessage() + " - object type: " + obj.getClass().getSimpleName();
        }
    }

    private Object getResult(ProceedingJoinPoint p) throws Throwable {
        Object result = null;
        Date startTime = new Date();
        String targetName = p.getTarget().getClass().getName();
        String methodName = p.getSignature().getName();
        String callerMethodName = getCallerMethodName();
        Object[] args = p.getArgs();
        Stream<?> stream = ArrayUtils.isEmpty(args) ? Stream.empty() : Arrays.stream(args);
        List<Object> logArgs = stream
                .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)) && !(arg instanceof MultipartFile))
                .collect(Collectors.toList());
        // 参数序列化（也可使用安全版本，但通常参数较小）
        String param = safeToJSONString(logArgs);  // 替换为安全方法
        String exception = "";
        try {
            result = p.proceed();
        } catch (Throwable e) {
            exception = e.getMessage();
            throw e;
        } finally {
            Date endTime = new Date();
            long time = endTime.getTime() - startTime.getTime();
            Boolean slowRequest = (time > 1500L);

            // 使用 safeToJSONString 替代直接序列化
            String resultJson = safeToJSONString(result);
            if (exception != null && !exception.isEmpty()) {
                log.error("\nAPI调用异常 - {}.{} \n调用方 - {} \n参数:{} \n结果:{} \n异常:{} \n耗时:{}ms \n慢请求:{}",
                        targetName, methodName, callerMethodName, param, resultJson, exception, time, slowRequest);
            } else if (slowRequest) {
                log.warn("\nAPI慢请求 - {}.{} \n调用方 - {} \n参数:{} \n结果:{} \n耗时:{}ms \n慢请求:{}",
                        targetName, methodName, callerMethodName, param, resultJson, time, slowRequest);
            } else {
                log.info("\nAPI调用成功 - {}.{} \n调用方 - {} \n参数:{} \n结果:{} \n耗时:{}ms",
                        targetName, methodName, callerMethodName, param, resultJson, time);
            }

            // debug 日志同样使用安全序列化
            log.debug("\n接口调用 - {}.{} \n调用方 - {}\nparam={}\nresult={}\nexception={}\n[{}->{}],slowRequest{}=[{}]",
                    targetName,
                    methodName,
                    callerMethodName,
                    param,
                    resultJson,
                    exception,
                    DateFormatUtils.format(startTime, TIME_PATTERN),
                    DateFormatUtils.format(endTime, TIME_PATTERN),
                    slowRequest,
                    time);
        }
        return result;
    }

    /**
     * 获取调用方的方法名称
     * @return 调用方方法名称
     */
    private String getCallerMethodName() {
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        // 查找调用链中属于业务代码的部分
        for (int i = 0; i < stackTrace.length; i++) {
            StackTraceElement element = stackTrace[i];
            // 找到LogInfoAspect类的调用点
            if (element.getClassName().equals(LogInfoAspect.class.getName()) && 
                ("cutController".equals(element.getMethodName()) || "around".equals(element.getMethodName()))) {
                // 继续向上查找真正的调用者
                for (int j = i + 1; j < stackTrace.length; j++) {
                    StackTraceElement caller = stackTrace[j];
                    // 排除Java内置类和框架类
                    if (!caller.getClassName().startsWith("java.") && 
                        !caller.getClassName().startsWith("org.springframework.") &&
                        !caller.getClassName().startsWith("org.aspectj.") &&
                        !caller.getClassName().equals(LogInfoAspect.class.getName())) {
                        return caller.getClassName() + "." + caller.getMethodName();
                    }
                }
            }
        }
        return "Unknown";
    }

    @Pointcut("execution(* com.sckw.*.service..*.*(..))")
    public void cutService() {
    }

    @AfterThrowing(pointcut = "cutService()", throwing = "e")
public void doAfterThrowing(JoinPoint point, Throwable e) throws Throwable {
        Date startTime = new Date();
        String targetName = point.getTarget().getClass().getName();
        String methodName = point.getSignature().getName();
        String callerMethodName = getCallerMethodName();
        StringBuilder params = new StringBuilder();
        if (point.getArgs() != null && point.getArgs().length > 0) {
            for (int i = 0; i < point.getArgs().length; i++) {
                Object arg = point.getArgs()[i];
                if (!(arg instanceof MultipartFile) && !(arg instanceof HttpServletResponse) && !(arg instanceof HttpServletRequest)) {
                    // 使用安全序列化
                    params.append(safeToJSONString(arg)).append(";");
                }
            }
        }
        Date endTime = new Date();
        long time = endTime.getTime() - startTime.getTime();
        log.error("\nSERVICE异常 - {}.{} \n调用方 - {} \n参数:{} \n异常:{} \n耗时:{}ms \n时间:[{}->{}]",
                targetName, methodName,
                callerMethodName,
                params,
                e.getMessage(),
                time,
                DateFormatUtils.format(startTime, TIME_PATTERN),
                DateFormatUtils.format(endTime, TIME_PATTERN));
    }

}
