package com.njcn.web.service.impl;

import com.njcn.common.config.GeneralInfo;
import com.njcn.common.pojo.constant.LogInfo;
import com.njcn.common.pojo.dto.DeviceLogDTO;
import com.njcn.common.pojo.dto.LogInfoDTO;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.PubUtils;
import com.njcn.common.utils.ReflectCommonUtil;
import com.njcn.redis.pojo.enums.RedisKeyEnum;
import com.njcn.redis.utils.RedisMessageQueueUtil;
import com.njcn.web.advice.DeviceLog;
import com.njcn.web.service.ILogService;
import com.njcn.web.utils.RequestUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.MethodArgumentNotValidException;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;


/**
 * @author hongawen
 * @version 1.0.0
 * @date 2022年03月14日 15:20
 */
@Slf4j
@Service
@AllArgsConstructor
public class LogServiceImpl implements ILogService {

    /**
     * 部分登录前的操作，获取用户名登录名的方式需特殊处理
     */
    private final static List<String> UN_LOGIN_METHOD = Arrays.asList("首次登录修改密码", "登录认证", "根据登录名查询用户信息", "认证后根据用户名判断用户状态", "更新用户登录认证密码错误次数", "根据登录名获取公钥", "根据客户端名查询信息");


    private final GeneralInfo generalInfo;
    /**
     * mqtt处理日志异步发布
     */
//    private final MqttPublisher publisher;

    /**
     * redis队列处理日志异步发布
     */
    private final RedisMessageQueueUtil redisMessageQueueUtil;


    /**
     * 异步记录controller中返回的信息内容
     *
     * @param request        请求头
     * @param returnType     返回类型
     * @param httpResult     返回结果
     * @param methodDescribe 方法描述
     */
    @Override
    @Async("asyncExecutor")
    public void recodeAdviceLog(ServerHttpRequest request, MethodParameter returnType, HttpResult httpResult, String methodDescribe) {
        //处理审计日志
        String loginName;
        String userName;
        String userIndex;
        HttpServletRequest httpServletRequest = RequestUtil.getRequest(request);
        if (UN_LOGIN_METHOD.contains(methodDescribe)) {
            loginName = RequestUtil.getLoginName(httpServletRequest);
            userName = "";
            userIndex = userName;
        } else {
            loginName = RequestUtil.getLoginNameByPayload(request);
            userName = RequestUtil.getUserNickname(request);
            userIndex = RequestUtil.getUserIndex(request);
        }
        String result = httpResult.getCode().equalsIgnoreCase(CommonResponseEnum.FAIL.getCode()) ? CommonResponseEnum.FAIL.getMessage() : CommonResponseEnum.SUCCESS.getMessage();
        String ip = RequestUtil.getRealIp(request);
        String type = ReflectCommonUtil.getOperateInfoByMethod(returnType.getMethod()).getOperateType();
        String level = ReflectCommonUtil.getOperateInfoByMethod(returnType.getMethod()).getOperateLevel();
        String operateType = ReflectCommonUtil.getOperateTypeByMethod(returnType.getMethod());
        Integer severity = levelStringToNumber(level);
        LogInfoDTO logInfoDTO = new LogInfoDTO(loginName, userName, ip, methodDescribe, operateType, result.equalsIgnoreCase("失败") ? 0 : 1, "", severity, type.equalsIgnoreCase("业务事件") ? 0 : 1, generalInfo.getMicroServiceName(), userIndex, LocalDateTime.now());
//        publisher.send("/userLog", PubUtils.obj2json(logInfoDTO), 2, false);

        redisMessageQueueUtil.pushMessage(
                RedisKeyEnum.USER_LOG_QUEUE.getKey(),
                PubUtils.obj2json(logInfoDTO)
        );

        //推送审计消息功能
        if (severity != 0) {
            if (!logInfoDTO.getLoginName().equals(LogInfo.UNKNOWN_USER)) {
                /**注意，此处是给前端推送的，如果没有了mqtt协议，此处前端就无消息可消费了*/
//                publisher.send("/userLogPush", PubUtils.obj2json(logInfoDTO), 2, false);

                //发送邮箱功能
                if (severity == 2 && logInfoDTO.getResult() == 0) {
                    //publisher.send("/userLogEmailPush", PubUtils.obj2json(logInfoDTO), 2, false);

//                    redisMessageQueueUtil.pushMessage(
//                            RedisKeyEnum.USER_LOG_EMAIL_QUEUE.getKey(),
//                            PubUtils.obj2json(logInfoDTO)
//                    );
                }
            }
        }
        //如果存在设备日志注解，则记录设备日志
        if (Objects.nonNull((returnType.getMethod())) && (returnType.getMethod()).isAnnotationPresent(DeviceLog.class)) {
            String deviceOperate = returnType.getMethod().getAnnotation(DeviceLog.class).operateType();
            DeviceLogDTO deviceLogDTO = new DeviceLogDTO(userName, deviceOperate, result.equalsIgnoreCase("失败") ? 0 : 1, "", loginName, userIndex);
//            publisher.send("/deviceLog", PubUtils.obj2json(deviceLogDTO), 2, false);
            redisMessageQueueUtil.pushMessage(
                    RedisKeyEnum.DEVICE_LOG_QUEUE.getKey(),
                    PubUtils.obj2json(deviceLogDTO)
            );
        }
    }


    /**
     * 异步记录全局异常捕获器中返回的信息内容
     *
     * @param exception 异常数据
     * @param request   请求信息
     * @param message   信息
     */
    @Override
    @Async("asyncExecutor")
    public void recodeBusinessExceptionLog(Exception exception, HttpServletRequest request, String message) {
        LogInfoDTO tempLogInfo = RequestUtil.initLogInfo(request);
        //认证前，获取用户信息
        String userIndex;
        if (Objects.equals(tempLogInfo.getLoginName(), LogInfo.UNKNOWN_USER)) {
            tempLogInfo.setLoginName(RequestUtil.getLoginName(request));
            tempLogInfo.setUserName("");
            userIndex = tempLogInfo.getLoginName();
        } else {
            userIndex = RequestUtil.getUserIndex(request);
        }
        //根据异常获取method方法
        Method method = ReflectCommonUtil.getMethod(exception);
        if (exception instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException methodArgumentNotValidException = (MethodArgumentNotValidException) exception;
            method = methodArgumentNotValidException.getParameter().getMethod();
        }
        String result = CommonResponseEnum.FAIL.getMessage();
        String type = ReflectCommonUtil.getOperateInfoByMethod(method).getOperateType();
        String level = ReflectCommonUtil.getOperateInfoByMethod(method).getOperateLevel();
        String operateType = ReflectCommonUtil.getOperateTypeByMethod(method);
        Integer severity = levelStringToNumber(level);
        LogInfoDTO logInfoDTO = new LogInfoDTO(tempLogInfo.getLoginName(), tempLogInfo.getUserName(), tempLogInfo.getIp(), ReflectCommonUtil.getMethodDescribeByMethod(method), operateType, result.equalsIgnoreCase("失败") ? 0 : 1, message, severity, type.equalsIgnoreCase("业务事件") ? 0 : 1, generalInfo.getMicroServiceName(), userIndex, LocalDateTime.now());
//        publisher.send("/userLog", PubUtils.obj2json(logInfoDTO), 1, false);
        redisMessageQueueUtil.pushMessage(
                RedisKeyEnum.USER_LOG_QUEUE.getKey(),
                PubUtils.obj2json(logInfoDTO)
        );
        auditPush(severity, logInfoDTO);
    }

    /**
     * 异步记录全局异常捕获器中返回的信息内容
     *
     * @param exception 异常数据
     * @param request   请求信息
     * @param message   信息
     * @param loginName 登录名
     */
    @Override
    @Async("asyncExecutor")
    public void recodeAuthExceptionLog(Exception exception, HttpServletRequest request, String message, String loginName) {
        //根据异常获取method方法
        Method method = ReflectCommonUtil.getMethod(exception);
        if (exception instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException methodArgumentNotValidException = (MethodArgumentNotValidException) exception;
            method = methodArgumentNotValidException.getParameter().getMethod();
        }
        String result = CommonResponseEnum.FAIL.getMessage();
        String ip = RequestUtil.getRealIp(request);
        String type = ReflectCommonUtil.getOperateInfoByMethod(method).getOperateType();
        String level = ReflectCommonUtil.getOperateInfoByMethod(method).getOperateLevel();
        Integer severity = levelStringToNumber(level);
        String operateType = ReflectCommonUtil.getOperateTypeByMethod(method);
        LogInfoDTO logInfoDTO = new LogInfoDTO(loginName, "", ip, ReflectCommonUtil.getMethodDescribeByMethod(method), operateType, result.equalsIgnoreCase("失败") ? 0 : 1, message, severity, type.equalsIgnoreCase("业务事件") ? 0 : 1, generalInfo.getMicroServiceName(), loginName, LocalDateTime.now());
//        publisher.send("/userLog", PubUtils.obj2json(logInfoDTO), 1, false);
        redisMessageQueueUtil.pushMessage(
                RedisKeyEnum.USER_LOG_QUEUE.getKey(),
                PubUtils.obj2json(logInfoDTO)
        );
        auditPush(severity, logInfoDTO);
    }

    private void auditPush(Integer severity, LogInfoDTO logInfoDTO) {
        //推送审计消息功能
        if (severity != 0) {
            if (!logInfoDTO.getLoginName().equals(LogInfo.UNKNOWN_USER)) {
                /**注意，此处是给前端推送的，如果没有了mqtt协议，此处前端就无消息可消费了*/
//                publisher.send("/userLogPush", PubUtils.obj2json(logInfoDTO), 2, false);
//                redisMessageQueueUtil.pushMessage(
//                        RedisKeyEnum.USER_LOG_QUEUE.getKey(),
//                        PubUtils.obj2json(logInfoDTO)
//                );
                //发送邮箱功能
                if (severity == 2 && logInfoDTO.getResult() == 0) {
                    //publisher.send("/userLogEmailPush", PubUtils.obj2json(logInfoDTO), 2, false);
                }
            }
        }
    }

    /**
     * 严重度 文字 转 数字
     */
    private Integer levelStringToNumber(String level) {
        switch (level) {
            case "中等":
                return 1;
            case "严重":
                return 2;
            default:
                return 0;
        }
    }


    /**
     * 严重度 数字 转 文字
     */
    private String levelNumberToString(Integer level) {
        switch (level) {
            case 1:
                return "中等";
            case 2:
                return "严重";
            default:
                return "普通";
        }
    }

}
