package cn.quantgroup.xyqb.aspect.limit;

import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.entity.User;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.session.XyqbSessionContextHolder;
import cn.quantgroup.xyqb.util.IpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.Set;

/**
 * 免密访问校验切面
 *
 * @author 任文超
 * @version 1.0.0
 * @since 2017-11-21
 */
@Slf4j
@Aspect
@Component
public class PasswordFreeAccessValidateAdvisor {

    private static final String USER_ID = "userId";

    /**
     * 免密访问校验切面
     */
    @Pointcut("@annotation(cn.quantgroup.xyqb.aspect.limit.PasswordFreeAccessValidator)")
    private void passwordFreeAccess() {
    }

    /**
     * 执行免密访问校验
     *
     * @throws Throwable
     */
    @Around("passwordFreeAccess()")
    private Object checkToken(ProceedingJoinPoint pjp) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        boolean valid = tokenValid(request);
        if (valid) {
            return pjp.proceed();
        }
        return JsonResult.buildErrorStateResult("拒绝访问", "");
    }

    /**
     * 校验免密访问
     * 规则：token 与 身份标记（phoneNo、userId匹配）
     *
     * @return True or False
     */
    private boolean tokenValid(HttpServletRequest request) {
        Objects.requireNonNull(request, "无效请求");
        String clientIp = IpUtil.getRemoteIP(request);
        Set<String> paramKeys = request.getParameterMap().keySet();
        if (!paramKeys.contains(Constants.PHONE_NO) && !paramKeys.contains(USER_ID)) {
            log.info("非法请求 - 缺少参数, paramKeys={}, clientIp={}", paramKeys, clientIp);
            return false;
        }
        // 当前请求的phoneNo/userId
        String phoneNo = request.getParameter(Constants.PHONE_NO);
        String userId = request.getParameter(USER_ID);
        if (StringUtils.isBlank(phoneNo) && StringUtils.isBlank(userId)) {
            log.info("非法请求 - 缺少参数, phoneNo={}, userId={}, clientIp={}", phoneNo, userId, clientIp);
            return false;
        }
        // 当前请求的Token
        String token = request.getHeader(Constants.X_AUTH_TOKEN);
        if (StringUtils.length(token) != Constants.TOKEN_LENGTH) {
            log.info("非法请求 - 无效token, token={}, phoneNo={}, userId={}, clientIp={}", token, phoneNo, userId, clientIp);
            return false;
        }
        // 当前session
        SessionStruct session = XyqbSessionContextHolder.getXSessionFromRedis(token);
        if (Objects.isNull(session) || Objects.isNull(session.getValues()) || Objects.isNull(session.getValues().getUser())) {
            log.info("非法请求 - 未登录, token={}, phoneNo={}, userId={}, clientIp={}", token, phoneNo, userId, clientIp);
            return false;
        }
        // 当前用户
        User user = session.getValues().getUser();
        if (Objects.isNull(user.getId()) && StringUtils.isBlank(user.getPhoneNo())) {
            log.info("非法请求 - 未登录, token={}, phoneNo={}, userId={}, clientIp={}", token, phoneNo, userId, clientIp);
            return false;
        }
        // 校对用户信息是否匹配
        boolean valid = (Objects.nonNull(user.getId()) && Objects.equals(userId, user.getId().toString()));
        valid = valid || (StringUtils.isNotBlank(phoneNo) && Objects.equals(phoneNo, user.getPhoneNo()));
        if (!valid) {
            log.info("非法请求 - 身份不匹配, token={}, phoneNo=({},{}), userId=({},{}), clientIp={}", token, phoneNo, user.getPhoneNo(), userId, user.getId(), clientIp);
        }
        return valid;
    }

}
