package com.simuwang.deploy.components;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.json.JSONUtil;
import com.simuwang.logging.Logging;
import com.simuwang.logging.LoggingService;
import com.simuwang.logging.SystemLog;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.Order;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.Objects;

/**
 * @author wangzaijun
 * @date 2024/9/14 14:07
 * @description 系统日志拦截切面
 */
@Order(2)
@Aspect
public class LoggingAspect {
    private final LoggingService service;

    public LoggingAspect(LoggingService service) {
        this.service = service;
    }

    @Around("@annotation(annotation)")
    public Object around(ProceedingJoinPoint joinPoint, SystemLog annotation) {
        Class<?> clazz = AopUtils.getTargetClass(joinPoint.getTarget());
        SystemLog systemLog = clazz.getAnnotation(SystemLog.class);
        String title = annotation.value();
        if (systemLog != null && StrUtil.isNotBlank(systemLog.value())) {
            title = systemLog.value() + "-" + title;
        }
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        Logging.Builder builder = Logging.builder().title(title).type(annotation.type().getValue()).method(request.getMethod())
                .requestUri(URLUtil.getPath(request.getRequestURI())).remoteAddr(this.getIpAddr(request))
                .params(joinPoint.getArgs() == null ? Objects.toString(request.getParameterMap()) : JSONUtil.toJsonStr(joinPoint.getArgs()));
        StopWatch watch = new StopWatch();
        watch.start();
        Object result = null;
        try {
            result = joinPoint.proceed();
            if (annotation.saveResult()) {
                builder.result(JSONUtil.toJsonStr(result));
            }
        } catch (Throwable throwable) {
            builder.exception(ExceptionUtil.stacktraceToString(throwable));
        } finally {
            watch.stop();
            builder.executeTime(watch.getTotalTimeMillis());
            this.service.asyncSave(builder.build());
        }
        return result;
    }

    public String getIpAddr(HttpServletRequest request) {
        if (request == null) {
            return "unknown";
        }
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
    }

    private String getMultistageReverseProxyIp(String ip) {
        // 多级反向代理检测
        if (ip != null && ip.indexOf(",") > 0) {
            final String[] ips = ip.trim().split(",");
            for (String subIp : ips) {
                if (!isUnknown(subIp)) {
                    ip = subIp;
                    break;
                }
            }
        }
        return ip;
    }

    private boolean isUnknown(String checkString) {
        return StrUtil.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
    }
}
