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.bean.dto.LogArgsDto;
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.CustomizedOperationTypeEnum;
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.ResultUtil;
import com.qmth.teachcloud.common.util.ServletUtil;
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.*;

/**
 * @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 {
        Object res = null;
        long beginTime = System.currentTimeMillis();
        long endTime = 0;
        String runStatus = "成功";
        try {
            res = joinPoint.proceed();
            endTime = System.currentTimeMillis();
            return res;
        } catch (Exception e) {
            endTime = System.currentTimeMillis();
            res = "Exception: " + e.getMessage();
            runStatus = "失败";
            log.error(SystemConstant.LOG_ERROR, e);
            if (e instanceof ApiException) {
                return ResultUtil.error((ApiException) e, e.getMessage());
            } else {
                return ResultUtil.error(e.getMessage());
            }
        } finally {
            try {
                //方法执行完成后增加日志
                addOperationLog(joinPoint, res, endTime - beginTime, runStatus);
            } catch (Exception e) {
                log.error("LogAspect 操作失败：" + e.getMessage());
                e.printStackTrace();
            }
        }

    }

    /**
     * 记录日志操作
     *
     * @param joinPoint joinPoint
     * @param res       返回值
     * @param time      方法执行时间
     * @param runStatus 方法执行状态
     */
    private void addOperationLog(JoinPoint joinPoint, Object res, long time, String runStatus) throws IllegalAccessException {
        SysUser requestUser = (SysUser) ServletUtil.getRequestUser();
        String userName = requestUser.getRealName();
        HttpServletRequest request = ServletUtil.getRequest();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Long privilegeId = ServletUtil.getCurrentPrivilegeId();
        String clazzName = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        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.setUrl(request.getServletPath());
        operationLog.setClazzName(clazzName);
        operationLog.setMethod(methodName);
        operationLog.setArgs(this.getTranslateArgs(paramsName, args));
        operationLog.setRunTime(time);
        operationLog.setRunStatus(runStatus);
        operationLog.setReturnValue(JSON.toJSONString(res));
        operationLog.setUserId(requestUser.getId());
        operationLog.setUserName(userName);
        operationLog.setIp(ServletUtil.getIP());
        operationLog.setCreateTime(System.currentTimeMillis());
        operationLog.setOrgId(requestUser.getOrgId());
        operationLog.setPrivilegeId(privilegeId);

        // 注解中的信息
        OperationLogDetail annotation = signature.getMethod().getAnnotation(OperationLogDetail.class);
        if (annotation != null) {
            String detail = annotation.detail();
            if (SystemConstant.strNotNull(detail)) {
                operationLog.setDetail(getDetail(((MethodSignature) joinPoint.getSignature()).getParameterNames(), joinPoint.getArgs(), annotation));
            } else {
                operationLog.setDetail(autoCreateOperationLogDetailByUrlAndPrivilegeId(request.getServletPath(),privilegeId));
            }
            operationLog.setLevel(annotation.level());
            CustomizedOperationTypeEnum customizedOperationType = annotation.customizedOperationType();
            if (CustomizedOperationTypeEnum.EDIT.equals(customizedOperationType)) {
                customizedOperationType = this.getAddOrUpdateByParam(args);
            }

            operationLog.setCustomizedOperationType(customizedOperationType);


            OperationTypeEnum operationType = annotation.operationType();
            if (OperationTypeEnum.UN_KNOW.equals(operationType)) {
                operationType = customizedOperationType.getOperationType();
            }
            operationLog.setOperationType(operationType);
            operationLog.setOperationUnit(annotation.operationUnit());
            operationLog.setSystemType(annotation.system());
        }
        // 保存操作日志
        basicOperationLogService.save(operationLog);
    }

    private String getTranslateArgs(String[] paramsName, Object[] args) {
        List<LogArgsDto> logArgsDtoList = new ArrayList<>();
        for (int i = 0; i < args.length; i++) {
            if (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;
            }
            LogArgsDto logArgsDto = new LogArgsDto();
            logArgsDto.setArg(args[i]);
            logArgsDto.setParamName(paramsName[i]);
            logArgsDtoList.add(logArgsDto);
        }
        return JSON.toJSONString(logArgsDtoList);
    }

    private CustomizedOperationTypeEnum getAddOrUpdateByParam(Object[] args) throws IllegalAccessException {
        for (Object arg : args) {
            if (Objects.nonNull(arg) && (arg instanceof HttpServletRequest || arg instanceof HttpServletResponse || arg instanceof MultipartFile || arg instanceof BeanPropertyBindingResult)) {
                continue;
            }
            Class<?> clazz = arg.getClass();
            Field[] fields = clazz.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.nonNull(obj)) {
                        return CustomizedOperationTypeEnum.UPDATE;
                    } else {
                        return CustomizedOperationTypeEnum.ADD;
                    }
                }
            }
            Class<?> supperClazz = clazz.getSuperclass();
            if (Objects.nonNull(supperClazz)) {
                Field[] supperFields = supperClazz.getDeclaredFields();
                for (Field field : supperFields) {
                    EditKey editKey = field.getAnnotation(EditKey.class);
                    if (editKey != null) {
                        // 捕捉到编辑对象的id
                        field.setAccessible(true);
                        Object obj = field.get(arg);
                        if (Objects.nonNull(obj)) {
                            return CustomizedOperationTypeEnum.UPDATE;
                        } else {
                            return CustomizedOperationTypeEnum.ADD;
                        }
                    }
                }
            }
        }
        return CustomizedOperationTypeEnum.EDIT;
    }

    /**
     * 对当前登录用户和占位符处理
     *
     * @param argNames   方法参数名称数组
     * @param args       方法参数数组
     * @param annotation 注解信息
     * @return 返回处理后的描述
     */
    private String getDetail(String[] argNames, Object[] args, OperationLogDetail annotation) {
        SysUser requestUser = (SysUser) ServletUtil.getRequestUser();

        Map<Object, Object> map = new HashMap<>();
        for (int i = 0; i < argNames.length; i++) {
            if (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(argNames[i], args[i]);
        }

        String detail = annotation.detail();
        try {
            detail = "'" + requestUser.getRealName() + "'=》" + annotation.detail();
            for (Map.Entry<Object, Object> entry : map.entrySet()) {
                Object k = entry.getKey();
                Object v = entry.getValue();
                detail = detail.replace("{{" + k + "}}", JSON.toJSONString(v));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return detail;
    }

    /**
     * 根据权限url自动创建操作日志详情
     *
     * @param url url
     * @return 操作日志详情
     */
    private String autoCreateOperationLogDetailByUrl(String url) {
        String detail = "";
        List<SysPrivilege> sysPrivilegeList = sysPrivilegeService.list(new QueryWrapper<SysPrivilege>()
                .lambda()
                .eq(SysPrivilege::getUrl, url)
                .eq(SysPrivilege::getType, PrivilegeEnum.URL)
                .orderByAsc(SysPrivilege::getId));
        if (sysPrivilegeList.size() > 0) {
            SysPrivilege sysPrivilege = sysPrivilegeList.get(0);
            Long parentId = sysPrivilege.getParentId();
            String operationName = sysPrivilege.getName();
            String path = sysPrivilegeService.findPrivilegeUrlCatalog(parentId);
            if (SystemConstant.strNotNull(path)) {
                path = path.substring(0, path.length() - SystemConstant.CATALOG_LINK.length());
                detail = path + SystemConstant.OPERATE_LINK + operationName;
            } else {
                detail = operationName;
            }
        }
        return detail;
    }

    /**
     * 根据权限url和所在菜单的权限id自动创建操作日志详情
     *
     * @param url         url
     * @param privilegeId 所属菜单的权限id
     * @return 操作日志详情
     */
    private String autoCreateOperationLogDetailByUrlAndPrivilegeId(String url, Long privilegeId) {
        String result = "";
        SysPrivilege menu = sysPrivilegeService.getById(privilegeId);
        SysPrivilege urlP = sysPrivilegeService.getOne(new QueryWrapper<SysPrivilege>()
                .lambda()
                .select(SysPrivilege::getName)
                .eq(SysPrivilege::getUrl, url)
                .eq(SysPrivilege::getType, PrivilegeEnum.URL));
        if (Objects.nonNull(menu) && Objects.nonNull(urlP)) {
            result = result + menu.getName() + SystemConstant.CATALOG_LINK + urlP.getName();
        } else if (Objects.nonNull(menu)) {
            result = result + menu.getName();
        } else if (Objects.nonNull(urlP)) {
            result = result + urlP.getName();
        }
        return result;
    }

    @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("方法最后执行.....");
    }
}
