package com.gxwebsoft.common.core.aspect;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gxwebsoft.common.core.annotation.OperationLog;
import com.gxwebsoft.common.core.annotation.OperationModule;
import com.gxwebsoft.common.core.utils.JSONUtil;
import com.gxwebsoft.common.system.entity.OperationRecord;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.common.system.service.OperationRecordService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * 操作日志记录
 *
 * @author WebSoft
 * @since 2020-03-21 16:58:16:05
 */
@Aspect
@Component
public class OperationLogAspect {
    @Resource
    private OperationRecordService operationRecordService;

    // 参数、返回结果、错误信息等最大保存长度
    private static final int MAX_LENGTH = 1000;
    // 用于记录请求耗时
    private final ThreadLocal<Long> startTime = new ThreadLocal<>();

    @Pointcut("@annotation(com.gxwebsoft.common.core.annotation.OperationLog)")
    public void operationLog() {
    }

    @Before("operationLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        startTime.set(System.currentTimeMillis());
    }

    @AfterReturning(pointcut = "operationLog()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
        saveLog(joinPoint, result, null);
    }

    @AfterThrowing(value = "operationLog()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        saveLog(joinPoint, null, e);
    }

    /**
     * 保存操作记录
     */
    private void saveLog(JoinPoint joinPoint, Object result, Exception e) {
        OperationRecord record = new OperationRecord();
        // 记录操作耗时
        if (startTime.get() != null) {
            record.setSpendTime(System.currentTimeMillis() - startTime.get());
        }
        // 记录当前登录用户id、租户id
        User user = getLoginUser();
        if (user != null) {
            record.setUserId(user.getUserId());
            record.setTenantId(user.getTenantId());
        }
        // 记录请求地址、请求方式、ip
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = (attributes == null ? null : attributes.getRequest());
        if (request != null) {
            record.setUrl(request.getRequestURI());
            record.setRequestMethod(request.getMethod());
            UserAgent ua = UserAgentUtil.parse(ServletUtil.getHeaderIgnoreCase(request, "User-Agent"));
            record.setOs(ua.getPlatform().toString());
            record.setDevice(ua.getOs().toString());
            record.setBrowser(ua.getBrowser().toString());
            record.setIp(ServletUtil.getClientIP(request));
        }
        // 记录异常信息
        if (e != null) {
            record.setStatus(1);
            record.setError(StrUtil.sub(e.toString(), 0, MAX_LENGTH));
        }
        // 记录模块名、操作功能、请求方法、请求参数、返回结果
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        record.setMethod(joinPoint.getTarget().getClass().getName() + "." + signature.getName());
        Method method = signature.getMethod();
        if (method != null) {
            OperationLog ol = method.getAnnotation(OperationLog.class);
            if (ol != null) {
                // 记录操作功能
                record.setDescription(getDescription(method, ol));
                // 记录操作模块
                record.setModule(getModule(joinPoint, ol));
                // 记录备注
                if (StrUtil.isNotEmpty(ol.comments())) {
                    record.setComments(ol.comments());
                }
                // 记录请求参数
                if (ol.param() && request != null) {
                    record.setParams(StrUtil.sub(getParams(joinPoint, request), 0, MAX_LENGTH));
                }
                // 记录请求结果
                if (ol.result() && result != null) {
                    record.setResult(StrUtil.sub(JSONUtil.toJSONString(result), 0, MAX_LENGTH));
                }
            }
        }

        // 记录访客日志
//        System.out.println("record = " + record);
//        if (record.getMethod().equals("com.gxwebsoft.love.controller.UserProfileController.detail")) {
//          final Integer toUserId = Integer.valueOf(StrUtil.removeSuffix(record.getParams()," "));
//          if (userLookService.count(new LambdaQueryWrapper<UserLook>().eq(UserLook::getUserId,record.getUserId()).eq(UserLook::getToUserId,toUserId)) == 0) {
//            final UserLook userLook = new UserLook();
//            userLook.setUserId(record.getUserId());
//            userLook.setToUserId(toUserId);
//            userLookService.save(userLook);
//          }
//        }

        operationRecordService.saveAsync(record);
    }

    /**
     * 获取当前登录用户
     */
    private User getLoginUser() {
        Authentication subject = SecurityContextHolder.getContext().getAuthentication();
        if (subject != null) {
            Object object = subject.getPrincipal();
            if (object instanceof User) {
                return (User) object;
            }
        }
        return null;
    }

    /**
     * 获取请求参数
     *
     * @param joinPoint JoinPoint
     * @param request   HttpServletRequest
     * @return String
     */
    private String getParams(JoinPoint joinPoint, HttpServletRequest request) {
        String params;
        Map<String, String> paramsMap = ServletUtil.getParamMap(request);
        if (paramsMap.keySet().size() > 0) {
            params = JSONUtil.toJSONString(paramsMap);
        } else {
            StringBuilder sb = new StringBuilder();
            for (Object arg : joinPoint.getArgs()) {
                if (ObjectUtil.isNull(arg)
                        || arg instanceof MultipartFile
                        || arg instanceof HttpServletRequest
                        || arg instanceof HttpServletResponse) {
                    continue;
                }
                sb.append(JSONUtil.toJSONString(arg)).append(" ");
            }
            params = sb.toString();
        }
        return params;
    }

    /**
     * 获取操作模块
     *
     * @param joinPoint JoinPoint
     * @param ol        OperationLog
     * @return String
     */
    private String getModule(JoinPoint joinPoint, OperationLog ol) {
        if (StrUtil.isNotEmpty(ol.module())) {
            return ol.module();
        }
        OperationModule om = joinPoint.getTarget().getClass().getAnnotation(OperationModule.class);
        if (om != null && StrUtil.isNotEmpty(om.value())) {
            return om.value();
        }
        // 尝试获取 SpringDoc 的 @Tag 注解
        io.swagger.v3.oas.annotations.tags.Tag tag = joinPoint.getTarget().getClass().getAnnotation(io.swagger.v3.oas.annotations.tags.Tag.class);
        if (tag != null && StrUtil.isNotEmpty(tag.name())) {
            return tag.name();
        }
        return null;
    }

    /**
     * 获取操作功能
     *
     * @param method Method
     * @param ol     OperationLog
     * @return String
     */
    private String getDescription(Method method, OperationLog ol) {
        if (StrUtil.isNotEmpty(ol.value())) {
            return ol.value();
        }
        // 尝试获取 SpringDoc 的 @Operation 注解
        io.swagger.v3.oas.annotations.Operation operation = method.getAnnotation(io.swagger.v3.oas.annotations.Operation.class);
        if (operation != null && StrUtil.isNotEmpty(operation.summary())) {
            return operation.summary();
        }
        return null;
    }

}
