package com.erp.audit.aspect;

import com.erp.audit.dto.AuditLogCreateRequest;
import com.erp.audit.entity.AuditLog;
import com.erp.audit.entity.SensitiveOperationRule;
import com.erp.audit.service.AuditLogService;
import com.erp.audit.service.SensitiveOperationService;
import com.erp.common.utils.SecurityUtils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;

/**
 * 审计日志AOP切面 - 自动记录操作日志
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class AuditLogAspect {

    private final AuditLogService auditLogService;
    private final SensitiveOperationService sensitiveOperationService;

    /** 审计注解切点 */
    @Pointcut("@annotation(com.erp.audit.aspect.AuditAnnotation)")
    public void auditPointcut() {}

    /** Controller层切点 */
    @Pointcut("execution(* com.erp..controller..*.*(..))")
    public void controllerPointcut() {}

    @Around("controllerPointcut() && !auditPointcut()")
    public Object aroundController(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        HttpServletRequest request = getRequest();
        String url = request != null ? request.getRequestURI() : "";
        String method = request != null ? request.getMethod() : "";

        // 跳过非写操作
        if (!isWriteOperation(method)) {
            return joinPoint.proceed();
        }

        // 检查是否匹配敏感操作规则
        String module = extractModule(joinPoint);
        String operationType = mapHttpMethodToOperationType(method);
        SensitiveOperationRule rule = sensitiveOperationService.matchRule(url, operationType, module);

        Object result = null;
        String errorMsg = null;
        int responseStatus = 200;

        try {
            result = joinPoint.proceed();
            return result;
        } catch (Throwable e) {
            errorMsg = e.getMessage();
            responseStatus = 500;
            throw e;
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            try {
                saveAuditLog(joinPoint, url, method, module, operationType, duration, 
                        errorMsg, responseStatus, rule != null);
                
                if (rule != null) {
                    sensitiveOperationService.triggerAlert(null, rule);
                }
            } catch (Exception e) {
                log.error("保存审计日志失败", e);
            }
        }
    }

    @Around("auditPointcut()")
    public Object aroundAuditAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        AuditAnnotation annotation = method.getAnnotation(AuditAnnotation.class);

        long startTime = System.currentTimeMillis();
        HttpServletRequest request = getRequest();
        String url = request != null ? request.getRequestURI() : "";

        Object result = null;
        String errorMsg = null;

        try {
            result = joinPoint.proceed();
            return result;
        } catch (Throwable e) {
            errorMsg = e.getMessage();
            throw e;
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            try {
                AuditLogCreateRequest req = new AuditLogCreateRequest();
                req.setUsername(getCurrentUsername());
                req.setModule(annotation.module());
                req.setOperationType(annotation.operationType());
                req.setDescription(annotation.description());
                req.setRequestUrl(url);
                req.setRequestMethod(joinPoint.getSignature().getName());
                req.setSensitive(annotation.sensitive());
                req.setRemark(errorMsg != null ? "失败: " + errorMsg : "成功");

                AuditLog log = auditLogService.create(req);
                log.setDuration(duration);
                log.setErrorMsg(errorMsg);
                log.setAuditStatus(errorMsg == null ? "SUCCESS" : "FAIL");

                if (annotation.sensitive()) {
                    sensitiveOperationService.triggerAlert(log.getId(), null);
                }
            } catch (Exception e) {
                log.error("保存注解审计日志失败", e);
            }
        }
    }

    private HttpServletRequest getRequest() {
        ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attrs != null ? attrs.getRequest() : null;
    }

    private boolean isWriteOperation(String method) {
        return "POST".equalsIgnoreCase(method) || 
               "PUT".equalsIgnoreCase(method) || 
               "DELETE".equalsIgnoreCase(method) ||
               "PATCH".equalsIgnoreCase(method);
    }

    private String mapHttpMethodToOperationType(String method) {
        return switch (method.toUpperCase()) {
            case "POST" -> "CREATE";
            case "PUT", "PATCH" -> "UPDATE";
            case "DELETE" -> "DELETE";
            case "GET" -> "READ";
            default -> method;
        };
    }

    private String extractModule(ProceedingJoinPoint joinPoint) {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        // 去掉 Controller 后缀
        if (className.endsWith("Controller")) {
            className = className.substring(0, className.length() - 10);
        }
        return className;
    }

    private String getCurrentUsername() {
        try {
            return SecurityUtils.getCurrentUsername();
        } catch (Exception e) {
            return "anonymous";
        }
    }

    private void saveAuditLog(ProceedingJoinPoint joinPoint, String url, String method, 
                              String module, String operationType, long duration,
                              String errorMsg, int status, boolean sensitive) {
        try {
            HttpServletRequest request = getRequest();
            AuditLogCreateRequest req = new AuditLogCreateRequest();
            req.setUsername(getCurrentUsername());
            req.setModule(module);
            req.setOperationType(operationType);
            req.setDescription(String.format("%s %s", method, url));
            req.setRequestUrl(url);
            req.setRequestMethod(method);
            req.setSensitive(sensitive);
            req.setDuration(duration);
            req.setErrorMsg(errorMsg);
            req.setAuditStatus(errorMsg == null ? "SUCCESS" : "FAIL");

            if (request != null) {
                req.setIpAddress(getClientIp(request));
                req.setUserAgent(request.getHeader("User-Agent"));
            }

            // 获取参数
            Object[] args = joinPoint.getArgs();
            if (args.length > 0) {
                req.setRequestParams(truncateParams(Arrays.toString(args)));
            }

            auditLogService.create(req);
        } catch (Exception e) {
            log.error("保存审计日志异常", e);
        }
    }

    private String getClientIp(HttpServletRequest request) {
        String[] headers = {
                "X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP",
                "HTTP_X_FORWARDED_FOR", "HTTP_X_FORWARDED", 
                "HTTP_X_CLUSTER_CLIENT_IP", "HTTP_CLIENT_IP", "HTTP_FORWARDED_FOR",
                "X-Real-IP", "X-Client-IP"
        };
        for (String header : headers) {
            String ip = request.getHeader(header);
            if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
                return ip.split(",")[0].trim();
            }
        }
        return request.getRemoteAddr();
    }

    private String truncateParams(String params) {
        if (params.length() > 4000) {
            return params.substring(0, 4000) + "...";
        }
        return params;
    }
}
