package org.springblade.common.auth.aspect;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import org.springblade.common.auth.UserContext;
import org.springblade.common.auth.UserContextUtil;
import org.springblade.common.auth.permission.PermissionHandler;
import org.springblade.common.auth.producer.OperationLogProducer;
import org.springblade.common.auth.support.Application;
import org.springblade.common.auth.support.AuthUtil;
import org.springblade.common.auth.support.MenuV2;
import org.springblade.common.auth.support.Operation;
import org.springblade.common.auth.support.OperationLog;
import org.springblade.common.auth.support.RoleV2;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.HandlerMapping;

/**
 * 统一用户管理操作日志切面
 *
 * @author wpp
 * @date 2019/10/14 13:42
 */
@Component
@Aspect
@ConditionalOnProperty(name = "cas.support.flag", havingValue = "true", matchIfMissing = false)
public class OperationLogAspect {

    private static final Logger logger = LoggerFactory.getLogger(OperationLogAspect.class);

    @Value("${spring.application.name}")
    private String applicationName;
//    @Autowired
    private PermissionHandler permissionHandler = new PermissionHandler();
    @Autowired
    private OperationLogProducer producer;

    @Pointcut("execution(* com.*.*.*..*Controller.*(..))")
    private void pointCutMethod() {
    }

    @AfterReturning(returning = "ret", pointcut = "pointCutMethod()")
    public void doAfterReturning(JoinPoint joinPoint, Object ret) throws Throwable {

        /**
         *
         * @step1  :获取用户信息
         * @step2  :组装当前用户和应用的信息
         * @step3  :组装角色信息
         * @step4  :组装菜单ID,菜单名称，操作ID,操作名称
         * @step5  :组装入参和出参
         * @step6  :通知idms新增日志
         */
        try {
            //获取RequestAttributes
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            //从获取RequestAttributes中获取HttpServletRequest的信息
            HttpServletRequest request = (HttpServletRequest) requestAttributes
                    .resolveReference(RequestAttributes.REFERENCE_REQUEST);
            // step1:获取用户信息
            UserContext userContext = UserContextUtil.getUserContext(request.getSession());
            if (userContext == null) {
                return;
            }
            logger.info("[OPERATION LOG] 当前登录用户账号: {}", userContext.getLoginName());

            // 获取操作code
            String url = "/" + applicationName + request.getServletPath();
            logger.info("[OPERATION LOG] 请求地址: {}, 请求URL：{}, 请求方法: {}", request.getRemoteAddr(), url,
                    request.getMethod());
            Map pathVariables = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            String handledUrl = AuthUtil.handleRequestUrl(url, pathVariables);
            String code = permissionHandler.getPermissionCode(handledUrl, request.getMethod());
            if (code == null) {
                logger.info("系统对请求地址{}没有访问权限控制！直接返回...", url);
                return;
            }

            if (!userContext.isContainOperationCode(code, handledUrl)) {
                logger.info("用户{}无权访问请求地址{}！直接返回...", userContext.getUserName(), url);
                return;
            }

            // 应用ID,应用名称
            Application application = userContext.getCurrentApplication(code, handledUrl);
            if (application == null) {
                return;
            }
            logger.info("[OPERATION LOG] 当前应用名称: {}", application.getName());

            OperationLog operationLog = new OperationLog();

            // step2:组装当前用户和应用的信息
            genUserAndApplication(operationLog, userContext, application);

            // step3  :组装角色信息
            genRole(operationLog, application);

            // step4:菜单ID,菜单名称，操作ID,操作名称
            genMenuAndOperation(operationLog, userContext, application, code);

            // step5:组装入参和出参
            genRequestAndResponse(operationLog, joinPoint, ret);

            // @step6  :通知idms新增日志
            // TODO 通知idms新增日志
            producer.sendOperationLogMessage(JSON.toJSONString(operationLog));
        } catch (Exception e) {
            logger.info("[OPERATION LOG] 记录操作记录异常：{}", e);
        }
    }

    /**
     * 组装用户和应用信息
     *
     * @param operationLog
     * @param userContext
     * @param application
     * @return void
     * @throws
     * @描述 :组装用户和应用信息
     * @创建人:WangPanPan
     * @创建时间:2019-10-14 4:53 PM
     * @修改人:
     * @修改时间:
     * @修改描述:
     * @date 2019-10-14 4:53 PM
     */
    private void genUserAndApplication(OperationLog operationLog, UserContext userContext, Application application) {
        // 用户登录Id,用户登录姓名，用户真实姓名，员工Id
        operationLog.setUserId(userContext.getUserId());
        operationLog.setLoginName(userContext.getLoginName());
        operationLog.setRealName(userContext.getUserName());
        operationLog.setStaffId(userContext.getStaffId());

        operationLog.setApplicationId(application.getId());
        operationLog.setApplicationName(application.getName());
    }

    /**
     * 组装角色信息
     *
     * @param operationLog
     * @param application
     * @return void
     * @throws
     * @描述 :组装角色信息
     * @创建人:WangPanPan
     * @创建时间:2019-10-14 6:56 PM
     * @修改人:
     * @修改时间:
     * @修改描述:
     * @date 2019-10-14 6:56 PM
     */
    private void genRole(OperationLog operationLog, Application application) {
        List<RoleV2> roleV2s = application.getRoles();
        if (CollectionUtils.isNotEmpty(roleV2s)) {
            StringBuilder roleId = new StringBuilder();
            StringBuilder roleName = new StringBuilder();
            for (RoleV2 roleV2 : roleV2s) {
                roleId.append(roleV2.getId());
                roleName.append(roleV2.getName());
            }
            String roleIdList = roleId.toString();
            if (StringUtils.isNotBlank(roleIdList) && roleIdList.length() > 256) {
                roleIdList = roleIdList.substring(0, 256);
            }
            String roleNameList = roleName.toString();
            if (StringUtils.isNotBlank(roleNameList) && roleNameList.length() > 256) {
                roleNameList = roleNameList.substring(0, 256);
            }
            operationLog.setRoleId(roleIdList);
            operationLog.setRoleName(roleNameList);
        }
    }

    /**
     * 组装菜单和操作
     *
     * @param operationLog
     * @param userContext
     * @param application
     * @param code
     * @return void
     * @throws
     * @描述 :组装菜单和操作
     * @创建人:WangPanPan
     * @创建时间:2019-10-14 5:24 PM
     * @修改人:
     * @修改时间:
     * @修改描述:
     * @date 2019-10-14 5:24 PM
     */
    private void genMenuAndOperation(OperationLog operationLog, UserContext userContext, Application application,
            String code) {
        Operation operation = userContext.getOperationByCode(code);
        if (operation == null) {
            logger.info("[OPERATION LOG] 当前登录用户{}获取操作信息失败, 操作编码: {}", userContext.getUserName(), code);
            return;
        }

        operationLog.setMenuId(operation.getMenuId());
        operationLog.setOperationId(operation.getId());
        operationLog.setOperationName(operation.getName());
        // 查询菜单名称
        if (operation.getMenuId() != null || application == null) {
            List<MenuV2> menuV2s = application.getMenus();
            if (CollectionUtils.isNotEmpty(menuV2s)) {
                genMenuName(operationLog, menuV2s, operation.getMenuId());
            }
        }
    }

    /**
     * 组装菜单名称
     *
     * @param operationLog
     * @param menuV2s
     * @return void
     * @throws
     * @描述 :组装菜单名称
     * @创建人:WangPanPan
     * @创建时间:2019-10-15 3:31 PM
     * @修改人:
     * @修改时间:
     * @修改描述:
     * @date 2019-10-15 3:31 PM
     */
    private void genMenuName(OperationLog operationLog, List<MenuV2> menuV2s, Long menuId) {
        if (CollectionUtils.isEmpty(menuV2s)) {
            return;
        }
        List<MenuV2> menuV2List = Lists.newArrayList();
        for (MenuV2 menuV2 : menuV2s) {
            if (menuId.equals(menuV2.getId())) {
                operationLog.setMenuName(menuV2.getName());
                break;
            }
            if (CollectionUtils.isNotEmpty(menuV2.getChildren())) {
                menuV2List.addAll(menuV2.getChildren());
            }
        }
        if (CollectionUtils.isNotEmpty(menuV2List)) {
            genMenuName(operationLog, menuV2List, menuId);
        }
    }

    /**
     * 组装入参和出参
     *
     * @param operationLog
     * @param joinPoint
     * @param ret
     * @return void
     * @throws
     * @描述 :组装入参和出参
     * @创建人:WangPanPan
     * @创建时间:2019-10-14 4:54 PM
     * @修改人:
     * @修改时间:
     * @修改描述:
     * @date 2019-10-14 4:54 PM
     */
    private void genRequestAndResponse(OperationLog operationLog, JoinPoint joinPoint, Object ret) {
        // 请求参数JSON,返回参数JSON
        // 入参
        logger.info("[OPERATION LOG] 请求参数: {}", joinPoint.getArgs());
        String requestJson = null;
        if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
            try {
                Object[] args = joinPoint.getArgs();
                //序列化时过滤掉request和response
                List<Object> logArgs = Arrays.asList(args).stream()
                        .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
                        .collect(Collectors.toList());
                requestJson = JSON.toJSONString(logArgs);
            } catch (Exception e) {
                logger.info("[OPERATION LOG] 处理请求参数出现异常: {}", e);
                requestJson = joinPoint.getArgs().toString();
            }
        }
        if (StringUtils.isBlank(requestJson)) {
            requestJson = "No request message!";
        } else if (requestJson.length() > 1024) {
            requestJson = requestJson.substring(0, 1024);
        }
        operationLog.setRequest(requestJson);

        // 打印参数日志
        String requestLog = requestJson;
        if (requestLog.length() > 128) {
            requestLog = requestLog.substring(0, 128);
        }
        logger.info("[OPERATION LOG] 参数日志: {}", requestLog);

        // 返回数据源
        String responseStr = null;
        if (ret == null) {
            responseStr = "No response message!";
        } else if (ret.toString().length() > 1024) {
            responseStr = ret.toString().substring(0, 1024);
        } else {
            responseStr = ret.toString();
        }
        operationLog.setResponse(responseStr);

        // 打印返回数据日志
        String responseLog = responseStr;
        if (responseLog.length() > 128) {
            responseLog = responseLog.substring(0, 128);
        }
        logger.info("[OPERATION LOG] 返回数据日志: {}", responseLog);
    }

}