package com.securitycontrol.common.log.aspect;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.securitycontrol.common.core.constant.Constant;
import com.securitycontrol.common.core.utils.ServletUtils;
import com.securitycontrol.common.core.utils.aes.DateTimeHelper;
import com.securitycontrol.common.core.utils.ip.IpUtils;
import com.securitycontrol.common.log.annotation.Log;
import com.securitycontrol.common.log.enums.BusinessStatus;
import com.securitycontrol.common.log.service.AsyncLogService;
import com.securitycontrol.common.security.utils.SecurityUtils;
import com.securitycontrol.system.api.domain.SysLog;
import com.securitycontrol.system.api.domain.SysUser;
import com.securitycontrol.system.api.model.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.UUID;

/**
 * @author：cwchen
 * @date：2024-02-28-16:47
 * @version：1.0
 * @description：日志切面
 */
@Aspect
@Component
@Slf4j
public class OperLogAspect {

    private static String[] constantArr;

    static {
        constantArr = new String[]{"业务日志,系统日志,异常日志"};
    }

    ThreadLocal<Long> currentTime = new ThreadLocal<>();

    @Autowired
    private AsyncLogService asyncLogService;

    /**
     * 设置操作日志切入点 记录操作日志 在注解的位置切入代码
     */
    @Pointcut("@annotation(com.securitycontrol.common.log.annotation.Log)")
    public void operLogPointCut() {
    }

    /**
     * 设置操作异常切入点记录异常日志 扫描所有controller包下操作
     */
    @Pointcut("execution( * com.securitycontrol..*.controller..*(..))")
    public void operExceptionLogPointCut() {
    }

    @Around("operLogPointCut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result;
        currentTime.set(System.currentTimeMillis());
        //正常处理开始
        result = joinPoint.proceed();
        //正常处理结束
        Long time = System.currentTimeMillis() - currentTime.get();
        currentTime.remove();

        handleLog(joinPoint, null, result, time);

        return result;
    }


    @AfterThrowing(pointcut = "operExceptionLogPointCut()", throwing = "e")
    public void logAfterThrowing(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, e, null, null);
    }

    protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult, Long time) {
        try {

            // 获取RequestAttributes
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            // 从获取RequestAttributes中获取HttpServletRequest的信息
            HttpServletRequest request = (HttpServletRequest) requestAttributes
                    .resolveReference(RequestAttributes.REFERENCE_REQUEST);

            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            //获取注解
            Log aopLog = method.getAnnotation(Log.class);
            SysLog sysLog = new SysLog();
            // 获取当前的用户
            LoginUser loginUser = SecurityUtils.getLoginUser();
            if (null != loginUser && null != loginUser.getSysUser()) {
                SysUser sysUser = loginUser.getSysUser();
                sysLog.setUserId(sysUser.getUserId());
                sysLog.setOperaUserName(sysUser.getNickName());
            }
            String id = UUID.randomUUID().toString().replaceAll("-", "");
            sysLog.setLogId(id);
            sysLog.setOperUri(request.getRequestURI());
            //操作结果 成功/失败
            sysLog.setResult(BusinessStatus.SUCCESS.ordinal());
            sysLog.setTimes(time != null ? time + "" : null);
            // 请求的地址
            String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
            sysLog.setIp(ip);

            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            //设置请求方法名
            sysLog.setMethod(className + "." + methodName + "()");
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, aopLog, sysLog);

            //利用是否有异常定性记录失败信息
            String result = JSON.toJSONString(jsonResult);
            JSONObject jsonObject = JSON.parseObject(result);
            if (e != null) {
                //失败
                sysLog.setResult(BusinessStatus.FAIL.ordinal());
                System.err.println(e.getClass().getName());
                sysLog.setFailureReason(StringUtils.substring(e.getMessage(), 0, 2000));
                log.error("耗时:{}  用户id:{}   用户名username: {}  请求ip:{}  User-Agent:{} 方法路径:{} 方法参数:{}",
                        sysLog.getTimes(),
                        sysLog.getUserId(),
                        sysLog.getOperaUserName(),
                        sysLog.getIp(),
                        null,
                        methodName,
                        sysLog.getParams());
                log.error("==控制层方法通知异常==");
                log.error("异常信息:{}", e.getMessage());
            } else if (e == null && jsonObject != null && !Objects.equals(Integer.parseInt(jsonObject.getString(Constant.CODE)), Constant.SUCCESS)) {
                sysLog.setResult(BusinessStatus.FAIL.ordinal());
                sysLog.setFailureReason(StringUtils.substring(jsonObject.getString("msg"), 0, 2000));
                log.error("耗时:{}  用户id:{}   用户名username: {}  请求ip:{}  User-Agent:{} 方法路径:{} 方法参数:{}",
                        sysLog.getTimes(),
                        sysLog.getUserId(),
                        sysLog.getOperaUserName(),
                        sysLog.getIp(),
                        null,
                        methodName,
                        sysLog.getParams());
                log.error("==控制层方法通知异常==");
                log.error("异常信息:{}", jsonObject.getString(Constant.MSG));
            }
            // 保存数据库
            log.info("耗时:{}  用户id:{}   用户名username: {}  请求ip:{}  User-Agent:{} 方法路径:{} 方法参数:{}",
                    sysLog.getTimes(),
                    sysLog.getUserId(),
                    sysLog.getOperaUserName(),
                    sysLog.getIp(),
                    null,
                    methodName,
                    sysLog.getParams());
            asyncLogService.addSaveSysLog(sysLog);
        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("==前置通知异常==");
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        }

    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param log    日志
     * @param sysLog 操作日志
     * @throws Exception
     */
    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysLog sysLog) throws Exception {
        if (log != null) {
            sysLog.setModel(log.menu());
            sysLog.setOperType(log.grade().getInfo());
            sysLog.setOperateDetail(log.details());
            sysLog.setOperTime(DateTimeHelper.getNowTime());
            sysLog.setLogType(Objects.equals(log.type(), constantArr[0]) ? 1 : 0);
        } else {
            //用于无注解的控制层方法 异常抛出 记录
            sysLog.setModel("未知模块");
            sysLog.setOperType("未知操作类型");
            sysLog.setOperateDetail("未知操作详情");
            sysLog.setOperTime(DateTimeHelper.getNowTime());
            sysLog.setLogType(3);
        }

        // 是否需要保存request，参数和值
        if (log.isSaveRequestData()) {
            // 获取参数的信息，传入到数据库中。
            setRequestValue(joinPoint, sysLog);
        }
    }

    /**
     * 获取请求的参数，放到log中
     *
     * @param sysLog 操作日志
     * @throws Exception 异常
     */
    private void setRequestValue(JoinPoint joinPoint, SysLog sysLog) throws Exception {
        String params = argsArrayToString(joinPoint.getArgs());
        //避免过长的无用信息
        sysLog.setParams(StringUtils.substring(params, 0, 2000));
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null && paramsArray.length > 0) {
            for (int i = 0; i < paramsArray.length; i++) {

                //排除不需要记录的参数
                if (!isFilterObject(paramsArray[i])) {

                    Object value = paramsArray[i];
                    if (value instanceof MultipartFile) {
                        MultipartFile file = (MultipartFile) value;
                        Object jsonObj = JSON.toJSON(file.getOriginalFilename());
                        params.append(jsonObj.toString()).append(" ");
                    } else if (value instanceof MultipartFile[]) {
                        MultipartFile[] files = (MultipartFile[]) value;
                        for (MultipartFile file : files) {
                            Object jsonObj = JSON.toJSON(file.getOriginalFilename());
                            params.append(jsonObj.toString()).append(" ");
                        }
                    } else {
                        if (value != null) {
                            Object jsonObj = JSON.toJSON(value);
                            params.append(jsonObj.toString()).append(" ");
                        }
                    }
                }
            }
        }
        return params.toString();
    }


    /**
     * 判断是否需要过滤
     *
     * @param o 对象信息。
     * @return 是需要过滤的对象，则返回true；否则返回false。
     */
    public boolean isFilterObject(final Object o) {
        return o instanceof HttpServletRequest || o instanceof HttpServletResponse;
    }
}