package com.njcn.gather.system.config.advice;

import cn.hutool.core.util.StrUtil;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.gather.system.log.pojo.dto.SysLogAuditRecord;
import com.njcn.gather.system.log.service.ISysLogAuditService;
import com.njcn.web.utils.ReflectCommonUtil;
import com.njcn.web.utils.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.annotation.Nonnull;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;

/**
 * @author caozehui
 * @data 2024-12-2
 */
@Slf4j
@ControllerAdvice
public class LogAdvice implements ResponseBodyAdvice<Object> {

    @Resource
    private ISysLogAuditService logService;

    @Resource(name = "logAuditExecutor")
    private Executor logAuditExecutor;

    private static final List<String> UN_LOG_INFO = Collections.singletonList("未知业务");

    private static final List<String> FILTER_CODE = Arrays.asList(
            CommonResponseEnum.SUCCESS.getCode(),
            CommonResponseEnum.FAIL.getCode(),
            CommonResponseEnum.NO_DATA.getCode()
    );

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, @Nonnull MethodParameter returnType, @Nonnull MediaType selectedContentType,
                                  @Nonnull Class selectedConverterType, @Nonnull ServerHttpRequest request,
                                  @Nonnull ServerHttpResponse response) {
        if (body instanceof HttpResult) {
            HttpResult<?> httpResult = (HttpResult<?>) body;
            if (FILTER_CODE.contains(httpResult.getCode())) {
                Method method = returnType.getMethod();
                String methodDescribe = resolveMethodDescribe(method);
                if (!UN_LOG_INFO.contains(methodDescribe)) {
                    SysLogAuditRecord logRecord = buildAdviceLogRecord(method, httpResult, methodDescribe);
                    submitLogTask(() -> logService.recodeAdviceLog(logRecord));
                }
            }
        }
        return body;
    }

    private SysLogAuditRecord buildAdviceLogRecord(Method method, HttpResult<?> httpResult, String methodDescribe) {
        Integer level = resolveOperateLevel(method);
        return SysLogAuditRecord.builder()
                .userId(resolveUserId())
                .loginName(resolveLoginName())
                .ip(resolveUserIp())
                .operate(methodDescribe)
                .operateType(resolveOperateType(method))
                .result(CommonResponseEnum.FAIL.getCode().equalsIgnoreCase(httpResult.getCode())
                        ? CommonResponseEnum.FAIL.getMessage()
                        : CommonResponseEnum.SUCCESS.getMessage())
                .type(resolveEventType(method))
                .level(level)
                .warn(level == 1 ? 1 : 0)
                .build();
    }

    private void submitLogTask(Runnable task) {
        try {
            logAuditExecutor.execute(() -> {
                try {
                    task.run();
                } catch (Exception e) {
                    log.error("异步记录审计日志失败", e);
                }
            });
        } catch (RuntimeException e) {
            log.error("提交审计日志任务失败", e);
        }
    }

    private String resolveMethodDescribe(Method method) {
        if (method == null) {
            return "未知业务";
        }
        try {
            String methodDescribe = ReflectCommonUtil.getMethodDescribeByMethod(method);
            return StrUtil.isBlank(methodDescribe) ? "未知业务" : methodDescribe;
        } catch (Exception e) {
            log.warn("解析审计日志方法描述失败，method={}", method.getName(), e);
            return "未知业务";
        }
    }

    private Integer resolveEventType(Method method) {
        try {
            if (method == null) {
                return 1;
            }
            String type = ReflectCommonUtil.getOperateInfoByMethod(method).getOperateType();
            return "业务事件".equalsIgnoreCase(type) ? 0 : 1;
        } catch (Exception e) {
            return 1;
        }
    }

    private Integer resolveOperateLevel(Method method) {
        try {
            if (method == null) {
                return 0;
            }
            String level = ReflectCommonUtil.getOperateInfoByMethod(method).getOperateLevel();
            if ("中等".equals(level)) {
                return 1;
            }
            if ("严重".equals(level)) {
                return 2;
            }
        } catch (Exception e) {
            return 0;
        }
        return 0;
    }

    private String resolveOperateType(Method method) {
        try {
            if (method == null) {
                return "";
            }
            return ReflectCommonUtil.getOperateTypeByMethod(method);
        } catch (Exception e) {
            return "";
        }
    }

    private String resolveUserId() {
        try {
            String userId = RequestUtil.getUserId();
            return StrUtil.isBlank(userId) ? "" : userId;
        } catch (Exception e) {
            return "";
        }
    }

    private String resolveLoginName() {
        try {
            String loginName = RequestUtil.getLoginName();
            return StrUtil.isBlank(loginName) ? "" : loginName;
        } catch (Exception e) {
            return "";
        }
    }

    private String resolveUserIp() {
        try {
            String userIp = RequestUtil.getUserIp();
            return StrUtil.isBlank(userIp) ? "" : userIp;
        } catch (Exception e) {
            return "";
        }
    }
}
