package com.qmth.teachcloud.common.aspect;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qmth.boot.api.exception.ApiException;
import com.qmth.teachcloud.common.annotation.EditKey;
import com.qmth.teachcloud.common.annotation.OperationLogDetail;
import com.qmth.teachcloud.common.contant.SystemConstant;
import com.qmth.teachcloud.common.entity.BasicOperationLog;
import com.qmth.teachcloud.common.entity.SysPrivilege;
import com.qmth.teachcloud.common.entity.SysUser;
import com.qmth.teachcloud.common.enums.PrivilegeEnum;
import com.qmth.teachcloud.common.enums.log.OperationTypeEnum;
import com.qmth.teachcloud.common.service.BasicOperationLogService;
import com.qmth.teachcloud.common.service.SysPrivilegeService;
import com.qmth.teachcloud.common.util.FileUtil;
import com.qmth.teachcloud.common.util.ResultUtil;
import com.qmth.teachcloud.common.util.ServletUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Description: 日志操作记录拦截aop
 * @Author: CaoZixuan
 * @Date: 2022-02-14
 */
@Aspect
@Component
public class LogAspect {
    @Resource
    private BasicOperationLogService basicOperationLogService;
    @Resource
    private SysPrivilegeService sysPrivilegeService;

    private final static Logger log = LoggerFactory.getLogger(LogAspect.class);

    /**
     * 日志注解切入点
     */
    @Pointcut("@annotation(com.qmth.teachcloud.common.annotation.OperationLogDetail)")
    public void operationLog() {

    }

    /**
     * 环绕增强，操作日志环绕切入
     *
     * @param joinPoint joinPoint
     * @return return
     * @throws Throwable Throwable
     */
    @Around(value = "operationLog()")
    public Object aroundOperationLogPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        String runStatus = "成功";
        try {
            return joinPoint.proceed();
        } catch (Exception e) {
            runStatus = "失败";
            log.error(SystemConstant.LOG_ERROR, e);
            if (e instanceof ApiException) {
                return ResultUtil.error((ApiException) e, ((ApiException) e).getCode(), e.getMessage());
            } else {
                return ResultUtil.error(e.getMessage());
            }
        } finally {
            try {
                //方法执行完成后增加日志
                addOperationLog(joinPoint, runStatus);
            } catch (Exception e) {
                log.error("LogAspect 操作失败：" + e.getMessage());
                e.printStackTrace();
            }
        }

    }

    /**
     * 记录日志操作
     *
     * @param joinPoint joinPoint
     * @param runStatus 方法执行状态
     */
    private void addOperationLog(JoinPoint joinPoint, String runStatus) throws IllegalAccessException {
        Long userId = ServletUtil.getRequestUserId();
        HttpServletRequest request = ServletUtil.getRequest();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Long privilegeId = ServletUtil.getCurrentPrivilegeId();
        Object[] args = joinPoint.getArgs();
        String[] paramsName = signature.getParameterNames();

        BasicOperationLog operationLog = new BasicOperationLog();

        // 方法中的信息
        operationLog.setId(SystemConstant.getDbUuid());
        operationLog.setSchoolId(Objects.nonNull(ServletUtil.getRequestHeaderSchoolIdByNotVaild()) ? Long.valueOf(ServletUtil.getRequestHeaderSchoolIdByNotVaild().toString()) : null);
        operationLog.setUserId(userId);
        operationLog.setIp(ServletUtil.getIP());
        operationLog.setUrl(request.getServletPath());
        operationLog.setArgs(JSON.toJSONString(getTranslateArgs(paramsName, args)));
        operationLog.setRunStatus(runStatus);
        operationLog.setCreateTime(System.currentTimeMillis());

        // 注解中的信息
        OperationLogDetail annotation = signature.getMethod().getAnnotation(OperationLogDetail.class);
        if (annotation != null) {
            OperationTypeEnum operationType = annotation.operationType();
            operationLog.setOperationType(operationType);

            if (OperationTypeEnum.SAVE.equals(operationType)) {
                operationType = this.getAddOrUpdateByParam(args);
            }
            // 退出登录，不绑定权限ID
            if (!OperationTypeEnum.LOGOUT.equals(operationType)) {
                operationLog.setPrivilegeId(privilegeId);
            }

            // functionName
            String functionName = annotation.functionName();
            if (SystemConstant.strNotNull(functionName)) {
                operationLog.setFunctionName(functionName);
            } else {
                operationLog.setFunctionName(getUrlName(request.getServletPath()));
            }

            // detail
            String detail = annotation.detail();
            if (SystemConstant.strNotNull(detail)) {
                operationLog.setDetail(getDetail(paramsName, args, detail));
            }
        }
        // 保存操作日志
        basicOperationLogService.save(operationLog);
    }

    private Map<String, Object> getTranslateArgs(String[] paramsName, Object[] args) {
        Map<String, Object> map = new HashMap<>();
        for (int i = 0; i < args.length; i++) {
            if (Objects.isNull(args[i]) || (Objects.nonNull(args[i]) && (args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse || args[i] instanceof CommonsMultipartFile || args[i] instanceof MultipartFile || args[i] instanceof BeanPropertyBindingResult))) {
                continue;
            }
            map.put(paramsName[i], args[i]);
        }
        return map;
    }

    /**
     * 处理SAVE类型方法，解析出新增或者修改
     *
     * @param args 请求参数
     */
    private OperationTypeEnum getAddOrUpdateByParam(Object[] args) throws IllegalAccessException {
        boolean isAdd = false;
        for (Object arg : args) {
            if (Objects.nonNull(arg) && (arg instanceof HttpServletRequest || arg instanceof HttpServletResponse || arg instanceof MultipartFile || arg instanceof BeanPropertyBindingResult)) {
                continue;
            }
            Field[] fields = arg.getClass().getDeclaredFields();
            for (Field field : fields) {
                EditKey editKey = field.getAnnotation(EditKey.class);
                if (editKey != null) {
                    // 捕捉到编辑对象的id
                    field.setAccessible(true);
                    Object obj = field.get(arg);
                    if (Objects.isNull(obj)) {
                        isAdd = true;
                    }
                }
            }
            Class<?> superclass = arg.getClass().getSuperclass();
            if (superclass != null) {
                Field[] superclassDeclaredField = superclass.getDeclaredFields();
                for (Field field : superclassDeclaredField) {
                    EditKey editKey = field.getAnnotation(EditKey.class);
                    if (editKey != null) {
                        // 捕捉到编辑对象的id
                        field.setAccessible(true);
                        Object obj = field.get(arg);
                        if (Objects.isNull(obj)) {
                            isAdd = true;
                        }
                    }
                }
            }
        }
        return isAdd ? OperationTypeEnum.ADD : OperationTypeEnum.UPDATE;
    }

    /**
     * 对当前登录用户和占位符处理
     *
     * @param paramsName 方法参数名称数组
     * @param args       方法参数数组
     * @param detail     注解信息
     * @return 返回处理后的描述
     */
    private String getDetail(String[] paramsName, Object[] args, String detail) {
        Map<String, Object> map = getTranslateArgs(paramsName, args);
        try {
            List<String> list = getDetailParamsReg(detail);
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                String k = entry.getKey();
                Object v = entry.getValue();

                if (CollectionUtils.isNotEmpty(list)) {
                    for (String s : list) {
                        s = s.replaceAll("\\{\\{", "").replace("}}", "");
                        if (s.contains(".")) {
                            String[] fs = s.split("\\.");
                            if (fs.length == 2) {
                                if (k.equals(fs[0])) {
                                    Field[] fields = getAllFields(v);
                                    Field field = Arrays.stream(fields).filter(f -> f.getName().equals(fs[1])).findFirst().orElse(null);
                                    Object obj = "";
                                    if (field != null) {
                                        // 捕捉到编辑对象的id
                                        field.setAccessible(true);
                                        obj = field.get(v);
                                    }
                                    detail = detail.replace("{{" + s + "}}", JSON.toJSONString(obj == null ? "" : obj));
                                }
                            } else if (fs.length == 3) {
                                if (k.equals(fs[0])) {
                                    Field[] fields = getAllFields(v);
                                    Field field = Arrays.stream(fields).filter(f -> f.getName().equals(fs[1])).findFirst().orElse(null);
                                    Object obj = "";
                                    if (field != null) {
                                        // 捕捉到编辑对象的id
                                        field.setAccessible(true);
                                        Object obj1 = field.get(v);
                                        if (obj1 != null) {
                                            Field[] fields1 = getAllFields(obj1);
                                            Field field1 = Arrays.stream(fields1).filter(f -> f.getName().equals(fs[2])).findFirst().orElse(null);
                                            if (field1 != null) {
                                                // 捕捉到编辑对象的id
                                                field1.setAccessible(true);
                                                obj = field1.get(obj1);
                                            }
                                        }
                                    }
                                    detail = detail.replace("{{" + s + "}}", JSON.toJSONString(obj == null ? "" : obj));
                                }
                            }
                        } else {
                            if (k.equals(s)) {
                                detail = detail.replace("{{" + s + "}}", JSON.toJSONString(v == null ? "" : v));
                            }
                        }
                    }
                } else {
                    return detail;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return detail;
    }

    private Field[] getAllFields(Object obj) {
        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        Class<?> superclass = clazz.getSuperclass();
        if (superclass != null) {
            Field[] superclassDeclaredField = superclass.getDeclaredFields();
            fields = ArrayUtils.addAll(fields, superclassDeclaredField);
        }
        return fields;
    }

    private List<String> getDetailParamsReg(String detail) {
        String reg = "\\{\\{(.*?)\\}\\}";
        Pattern pattern = Pattern.compile(reg);
        Matcher matcher = pattern.matcher(detail);
        List<String> list = new ArrayList<>();
        while (matcher.find()) {
            list.add(matcher.group());
        }
        return list;
    }

    /**
     * 根据url获取url名称
     *
     * @param url url
     * @return url名称
     */
    private String getUrlName(String url) {
        SysPrivilege sysPrivilege = sysPrivilegeService.getOne(new QueryWrapper<SysPrivilege>()
                .lambda()
                .eq(SysPrivilege::getUrl, url)
                .eq(SysPrivilege::getType, PrivilegeEnum.URL));
        return sysPrivilege == null ? null : sysPrivilege.getName();
    }

    @Before(value = "operationLog()")
    public void doBeforeAdvice(JoinPoint joinPoint) {
        log.info("进入方法前执行.....");
    }

    /**
     * 处理完请求，返回内容
     *
     * @param ret ret
     */
    @AfterReturning(returning = "ret", pointcut = "operationLog()")
    public void doAfterReturning(Object ret) {
        log.info("方法的返回值 : " + ret);
    }

    /**
     * 后置异常通知
     */
    @AfterThrowing(value = "operationLog()")
    public void throwss(JoinPoint jp) {
        log.info("方法异常时执行.....");
    }


    /**
     * 后置最终通知,final增强，不管是抛出异常或者正常退出都会执行
     */
    @After(value = "operationLog()")
    public void after(JoinPoint jp) {
        log.info("方法最后执行.....");
    }
}
