package com.njcn.auth.security.granter;

import cn.hutool.core.util.StrUtil;
import com.njcn.common.pojo.constant.SecurityConstants;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.common.utils.sm.DesUtils;
import com.njcn.common.utils.sm.Sm2;
import com.njcn.redis.utils.RedisUtil;
import com.njcn.user.enums.UserResponseEnum;
import com.njcn.web.utils.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;

import javax.servlet.http.HttpServletRequest;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author hongawen
 * @version 1.0.0
 * @date 2021年12月15日 14:23
 */
@Slf4j
public class CaptchaTokenGranter extends AbstractTokenGranter {

    private final AuthenticationManager authenticationManager;

    private final RedisUtil redisUtil;

    public CaptchaTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
                               OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager,
                               RedisUtil redisUtil
    ) {
        //SecurityConstants.GRANT_CAPTCHA：申明为验证码模式
        super(tokenServices, clientDetailsService, requestFactory, SecurityConstants.GRANT_CAPTCHA);
        this.authenticationManager = authenticationManager;
        this.redisUtil = redisUtil;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
        String username = parameters.get(SecurityConstants.USERNAME);
        username = DesUtils.aesDecrypt(username);
        String verifyCode = parameters.get(SecurityConstants.VERIFY_CODE);
        //判断是否需要校验图形验证码，用户错误后，前端要求用户填写验证码
        if(StrUtil.isEmpty(verifyCode)||verifyCode.equals("1")){
            if (!judgeImageCode(parameters.get(SecurityConstants.IMAGE_CODE), RequestUtil.getRequest())) {
                throw new BusinessException(UserResponseEnum.LOGIN_WRONG_CODE);
            }
        }
        String password = parameters.get(SecurityConstants.PASSWORD);
        String ip = Objects.requireNonNull(RequestUtil.getRequest()).getHeader(SecurityConstants.REQUEST_HEADER_KEY_CLIENT_REAL_IP);
        //密码处理
        String privateKey = redisUtil.getStringByKey(username + ip);
        //秘钥用完即删
        redisUtil.delete(username + ip);
        //对SM2解密面进行验证
        password = Sm2.getPasswordSM2Verify(privateKey, password);
        if (StrUtil.isBlankIfStr(password)) {
            throw new BusinessException(UserResponseEnum.PASSWORD_TRANSPORT_ERROR);
        }
        //1、不将密码放入details内，防止密码泄漏
        parameters.remove(SecurityConstants.PASSWORD);
        //2、组装用户密码模式的认证信息
        Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
        try {
            //3、认证组装好的信息
            userAuth = authenticationManager.authenticate(userAuth);
        } catch (AccountStatusException | BadCredentialsException ase) {
            //covers expired, locked, disabled cases
            throw new InvalidGrantException(ase.getMessage());
        }
        // If the username/password are wrong the spec says we should send 400/invalid grant
        if (userAuth == null || !userAuth.isAuthenticated()) {
            throw new InvalidGrantException("无法认证用户: " + username);
        }

        OAuth2Request storedOauth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOauth2Request, userAuth);
    }

    /**
     * @param imageCode 图形验证码
     */
    private boolean judgeImageCode(String imageCode, HttpServletRequest request) {
        if (StrUtil.isBlankIfStr(imageCode)) {
            return false;
        }
        String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
        String ip = request.getHeader(SecurityConstants.REQUEST_HEADER_KEY_CLIENT_REAL_IP);
        String key = userAgent + ip;
        String redisImageCode = redisUtil.getStringByKey(key);
        if (imageCode.equalsIgnoreCase(redisImageCode)) {
            redisUtil.delete(key);
            return true;
        }
        return false;
    }
}
