package com.lesingle.edu.common.aspect;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.filter.ValueFilter;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.lesingle.edu.common.annotation.Log;
import com.lesingle.edu.common.security.SecurityUtils;
import com.lesingle.edu.entity.OperationLog;
import com.lesingle.edu.service.OperationLogService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.LocalDateTime;

/**
 * 操作日志切面
 * <p>
 * 拦截带有 @Log 注解的方法，记录操作日志到数据库
 * </p>
 *
 * @author reading-platform
 * @since 2026-03-13
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LogAspect {

    private final OperationLogService operationLogService;

    /**
     * 线程变量，用于存储操作日志信息
     */
    private static final ThreadLocal<OperationLog> LOG_LOCAL = new ThreadLocal<>();

    /**
     * FastJSON2 值过滤器 - 用于脱敏敏感字段
     */
    private static final ValueFilter SENSITIVE_FILTER = (obj, name, value) -> {
        if (value == null) {
            return null;
        }
        // 检查字段名是否包含敏感词
        String lowerName = name.toLowerCase();
        if (lowerName.contains("password") ||
            lowerName.contains("passwd") ||
            lowerName.contains("secret") ||
            lowerName.contains("token")) {
            return "***";
        }
        return value;
    };

    /**
     * 前置通知：在方法执行前记录日志
     *
     * @param joinPoint 切面连接点
     * @param logAnnotation       日志注解
     */
    @Before("@annotation(logAnnotation)")
    public void before(JoinPoint joinPoint, Log logAnnotation) {
        try {
            // 获取请求信息
            HttpServletRequest request = getRequest();

            // 构建操作日志对象
            OperationLog operationLog = new OperationLog();
            operationLog.setId(IdWorker.getId());
            operationLog.setModule(logAnnotation.module().toString());
            operationLog.setAction(logAnnotation.type().getCode());
            operationLog.setDetails(logAnnotation.description());
            operationLog.setCreatedAt(LocalDateTime.now());

            // 记录操作人信息
            try {
                Long currentUserId = SecurityUtils.getCurrentUserId();
                String currentUserRole = SecurityUtils.getCurrentRole();
                if (currentUserId != null) {
                    operationLog.setUserId(currentUserId);
                    operationLog.setUserRole(currentUserRole);
                }
            } catch (Exception e) {
                // 未登录或获取用户信息失败，不记录用户信息
                log.debug("获取当前用户信息失败：{}", e.getMessage());
            }

            // 设置租户 ID：超管端使用 0，其他端使用当前租户 ID
            try {
                Long tenantId = SecurityUtils.getCurrentTenantId();
                if (tenantId != null) {
                    operationLog.setTenantId(tenantId);
                } else {
                    // 超管端操作，使用 0 作为标记
                    operationLog.setTenantId(0L);
                }
            } catch (Exception e) {
                // 获取租户 ID 失败（可能是超管端），使用 0 作为标记
                operationLog.setTenantId(0L);
            }

            // 记录请求信息
            operationLog.setIpAddress(getIpAddress(request));
            operationLog.setUserAgent(request.getHeader("User-Agent"));
            operationLog.setRequestUri(request.getRequestURI());

            // 记录请求参数
            if (logAnnotation.recordParams()) {
                try {
                    Object[] params = getRequestParams(joinPoint);
                    // 使用 ValueFilter 脱敏敏感字段后序列化
                    String paramsJson = JSON.toJSONString(params, SENSITIVE_FILTER);
                    operationLog.setRequestParams(paramsJson);
                } catch (Exception e) {
                    log.warn("记录请求参数失败：{}", e.getMessage());
                }
            }

            // 存入线程变量
            LOG_LOCAL.set(operationLog);
        } catch (Exception e) {
            log.error("记录操作日志失败：{}", e.getMessage(), e);
        }
    }

    /**
     * 返回通知：在方法执行成功后记录日志
     *
     * @param joinPoint 切面连接点
     * @param logAnnotation       日志注解
     * @param result    方法返回值
     */
    @AfterReturning(pointcut = "@annotation(logAnnotation)", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Log logAnnotation, Object result) {
        try {
            OperationLog operationLog = LOG_LOCAL.get();
            if (operationLog != null) {
                // 保存日志到数据库
                operationLogService.save(operationLog);
                log.info("操作日志记录成功 - 模块：{}, 操作：{}", logAnnotation.module(), logAnnotation.description());
            }
        } catch (Exception e) {
            log.error("保存操作日志失败：{}", e.getMessage(), e);
        } finally {
            // 清理线程变量
            LOG_LOCAL.remove();
        }
    }

    /**
     * 异常通知：在方法抛出异常后记录日志
     *
     * @param joinPoint 切面连接点
     * @param logAnnotation       日志注解
     * @param exception 异常对象
     */
    @AfterThrowing(pointcut = "@annotation(logAnnotation)", throwing = "exception")
    public void afterThrowing(JoinPoint joinPoint, Log logAnnotation, Exception exception) {
        try {
            OperationLog operationLog = LOG_LOCAL.get();
            if (operationLog != null) {
                // 记录异常信息
                operationLog.setDetails("操作失败：" + exception.getMessage());
                // 保存日志到数据库
                operationLogService.save(operationLog);
                log.warn("操作日志记录（异常） - 模块：{}, 操作：{}, 异常：{}",
                        logAnnotation.module(), logAnnotation.description(), exception.getMessage());
            }
        } catch (Exception e) {
            log.error("保存操作日志失败：{}", e.getMessage(), e);
        } finally {
            // 清理线程变量
            LOG_LOCAL.remove();
        }
    }

    /**
     * 获取当前请求
     *
     * @return HttpServletRequest
     */
    private HttpServletRequest getRequest() {
        ServletRequestAttributes attributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            return attributes.getRequest();
        }
        throw new IllegalStateException("当前请求上下文为空");
    }

    /**
     * 获取请求 IP 地址
     *
     * @param request 请求对象
     * @return IP 地址
     */
    private String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 如果是多个 IP，取第一个
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }

    /**
     * 获取请求参数
     *
     * @param joinPoint 切面连接点
     * @return 参数字典
     */
    private Object[] getRequestParams(JoinPoint joinPoint) {
        return joinPoint.getArgs();
    }
}
