package com.bcxin.message.ascept;

import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.digest.MD5;
import com.alibaba.fastjson.JSONObject;
import com.bcxin.message.common.CommonConstant;
import com.bcxin.message.common.emus.MsgSendSource;
import com.bcxin.message.common.utils.AssertUtils;
import com.bcxin.message.dtos.request.SendRobotMessageRequest;
import com.bcxin.message.entity.messagecenter.MsgAdminUserEntity;
import com.bcxin.message.entity.messagecenter.MsgAdminUserSenderEntity;
import com.bcxin.message.service.messagecenter.MsgAdminUserSenderService;
import com.bcxin.message.service.messagecenter.MsgAdminUserService;
import com.bcxin.message.service.messagecenter.MsgRequestLogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * description：鉴权切面
 * author：linchunpeng
 * date：2023/11/7
 */
@Slf4j
@Component
@Aspect
public class AuthAspect {

    @Autowired
    private MsgAdminUserService msgAdminUserService;

    @Autowired
    private MsgAdminUserSenderService msgAdminUserSenderService;

    @Autowired
    private MsgRequestLogService msgRequestLogService;

    @Value("${api-auth.admin-secret}")
    private String adminSecret;

    @Value("${api-auth.valid-minute}")
    private Integer validMinute;

    @Value("${api-auth.is-md5-secret}")
    private Boolean isMd5Secret;
    /**
     * 路径校验器
     */
    private final AntPathMatcher antPathMatcher =  new AntPathMatcher();

    /**
     * description：前置切面，判断sender绑定
     * author：linchunpeng
     * date：2023/11/15
     */
    @Before("execution(* com.bcxin.message.controller.*.*(..))")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = servletRequestAttributes.getRequest();

        if(!this.isMatch(request.getRequestURI(), getPublicUris())) {
            String appid = request.getHeader(CommonConstant.MSG_REQUEST_HEADER_APPID);
            if (joinPoint.getArgs() != null && joinPoint.getArgs()[0] != null && joinPoint.getArgs()[0] instanceof  SendRobotMessageRequest) {
                SendRobotMessageRequest sendRobotMessageRequest = (SendRobotMessageRequest) joinPoint.getArgs()[0];
                AssertUtils.isNotEmptyString(sendRobotMessageRequest.getSender(), "sender不能为空");
                MsgAdminUserSenderEntity msgAdminUserSenderEntity = msgAdminUserSenderService.findByAppidAndSender(appid, sendRobotMessageRequest.getSender());
                AssertUtils.isNotNullObject(msgAdminUserSenderEntity, String.format("appid：%s，未授权sender：%s", appid, sendRobotMessageRequest.getSender()));
                sendRobotMessageRequest.setSenderType(msgAdminUserSenderEntity.getSenderType());
            }
        }
    }

    /**
     * description：环绕切面，鉴权
     * author：linchunpeng
     * date：2023/11/15
     */
    @Around("execution(* com.bcxin.message.controller.*.*(..))")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = servletRequestAttributes.getRequest();

        String appid = request.getHeader(CommonConstant.MSG_REQUEST_HEADER_APPID);
        String secret = request.getHeader(CommonConstant.MSG_REQUEST_HEADER_SECRET);

        //获取传参信息
        Object[] args = proceedingJoinPoint.getArgs();
        //过滤无法序列化
        Stream<?> stream = ArrayUtils.isEmpty(args) ? Stream.empty() : Arrays.stream(args);
        List<Object> logArgs = stream
                .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
                .collect(Collectors.toList());
        //过滤后序列化无异常
        String requestId = UUID.randomUUID(). toString();
        String requestParam = JSONObject.toJSONString(logArgs);
        log.info("请求id：{}，请求地址：{}，请求头部：appid={}，secret={}，请求参数：{}",
                requestId, request.getRequestURI(), appid, secret, requestParam);

        //创建请求日志
        msgRequestLogService.createLog(requestId, appid, request.getRequestURI(), requestParam);

        //参数放入请求域
        request.setAttribute(CommonConstant.MSG_REQUEST_ID, requestId);
        request.setAttribute(CommonConstant.MSG_REQUEST_PARAM, requestParam);

        if(this.isMatch(request.getRequestURI(), getPublicUris())) {
            return proceed(proceedingJoinPoint, request, requestId);
        }

        AssertUtils.isNotEmptyString(secret, "secret不能为空");

        if (!adminSecret.equals(secret)) {
            AssertUtils.isNotEmptyString(appid, "appid不能为空");
            MsgAdminUserEntity msgAdminUserEntity = msgAdminUserService.findByAppid(appid);
            AssertUtils.isNotNullObject(msgAdminUserEntity, "appid不存在");

            Calendar now = Calendar.getInstance();
            now.setTime(new Date());
            //验证user 与 token 的正确性，token = user + "2023-11-07 15:39"，取md5
            boolean isRight = false;
            if (isMd5Secret) {
                //有效期5分钟
                int minute = validMinute;
                while (!isRight && minute > 0) {
                    String secret_ = MD5.create().digestHex(appid.concat(DateUtil.format(now.getTime(), "yyyyMMddHHmm")));
                    if (secret.toUpperCase(Locale.ROOT).equals(secret_.toUpperCase(Locale.ROOT))) {
                        isRight = true;
                    } else {
                        now.set(Calendar.MINUTE, now.get(Calendar.MINUTE) - 1);
                        minute--;
                    }
                }
            } else {
                isRight = secret.equals(msgAdminUserEntity.getSecret());
            }

            AssertUtils.isTrue(isRight, "无效的secret");
        }

        //设置参数
        if (proceedingJoinPoint.getArgs()[0] instanceof  SendRobotMessageRequest) {
            SendRobotMessageRequest sendRobotMessageRequest = (SendRobotMessageRequest) proceedingJoinPoint.getArgs()[0];
            sendRobotMessageRequest.setRequestId(requestId);
            sendRobotMessageRequest.setAppid(appid);
            sendRobotMessageRequest.setSource(MsgSendSource.SEND_API.getCode());
        }
        return proceed(proceedingJoinPoint, request, requestId);
    }

    /**
     * description：执行与执行结果
     * author：linchunpeng
     * date：2023/11/17
     */
    private Object proceed(ProceedingJoinPoint proceedingJoinPoint, HttpServletRequest request, String requestId) throws Throwable {
        Object proceed = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
        String requestResult = JSONObject.toJSONString(proceed);
        log.info("请求id：{}，请求地址：{}，返回参数：{}", requestId, request.getRequestURI(), requestResult);
        //更新请求结果
        msgRequestLogService.updateRequestResult(requestId, requestResult);
        return proceed;
    }

    /**
     * description：实付匹配
     * author：linchunpeng
     * date：2023/11/15
     */
    private boolean isMatch(String url, List<String> uris) {
        if(uris==null){
            return false;
        }
        for (String uri : uris) {
            if (this.antPathMatcher.match(uri, url)) {
                return true;
            }
        }
        return false;
    }

    /**
     * description：不需要鉴权的接口url
     * author：linchunpeng
     * date：2023/11/15
     */
    private List<String> getPublicUris() {
        return Arrays.asList("/manager/create/robot", "/manager/create/channel");
    }
}
