package org.springblade.auth.granter;

import lombok.extern.slf4j.Slf4j;
import org.springblade.auth.constant.WxAppletConstant;
import org.springblade.auth.enums.LoginTypeEnum;
import org.springblade.auth.service.BladeUserDetails;
import org.springblade.auth.utils.JwtUtil;
import org.springblade.auth.utils.TokenUtil;
import org.springblade.auth.utils.WxAppletUtil;
import org.springblade.common.cache.CacheNames;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.tool.utils.ObjectUtil;
import org.springblade.core.tool.utils.SpringUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.WebUtil;
import org.springblade.system.user.entity.User;
import org.springblade.system.user.feign.IUserClient;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException;
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;

/**
 * 验证码TokenGranter
 *
 * @author Chill
 */
@Slf4j
public class CaptchaTokenGranter extends AbstractTokenGranter {

    private static final String GRANT_TYPE = "captcha";

    private final AuthenticationManager authenticationManager;

    private BladeRedis bladeRedis;

    public CaptchaTokenGranter(AuthenticationManager authenticationManager,
                               AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, BladeRedis bladeRedis) {
        this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
        this.bladeRedis = bladeRedis;
    }

    protected CaptchaTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
                                  ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
        super(tokenServices, clientDetailsService, requestFactory, grantType);
        this.authenticationManager = authenticationManager;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

        HttpServletRequest request = Objects.requireNonNull(WebUtil.getRequest());

        Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());

        Authentication userAuth;

        switch (LoginTypeEnum.get(parameters.get("type"))) {
            // 杰生小程序登录逻辑
            case STORAGE_APPLET:
                userAuth = storageAppletLogin(parameters, WxAppletConstant.STORAGE_APPID, WxAppletConstant.STORAGE_SECRET);
                break;

            // 外贸超人
            case FOREIGN_TRADE_SUPERMAN:
                userAuth = foreignTradeSupermanLogin(parameters);
                break;

            // 无需验证码登录
            case LOGIN_WITHOUT_VERIFICATION_CODE:
                // 校验账号密码
                userAuth = verificationLogin(parameters);
                break;

            // 默认登录
            default:
                // 校验验证码
                verificationCode(request);
                // 校验账号密码
                userAuth = verificationLogin(parameters);
        }

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

    /**
     * 校验验证码
     */
    private void verificationCode(HttpServletRequest request) {

        // 增加验证码判断
        String key = request.getHeader(TokenUtil.CAPTCHA_HEADER_KEY);
        String code = request.getHeader(TokenUtil.CAPTCHA_HEADER_CODE);
        if (ObjectUtil.isEmpty(key) || ObjectUtil.isEmpty(code)) {
            throw new UserDeniedAuthorizationException(TokenUtil.CAPTCHA_NOT_CORRECT);
        }
        // 获取验证码
        String redisCode = bladeRedis.get(CacheNames.CAPTCHA_KEY + key);
        // 删除验证码缓存
        bladeRedis.del(CacheNames.CAPTCHA_KEY + key);

        // 判断验证码
        if (!StringUtil.equalsIgnoreCase(redisCode, code)) {
            throw new UserDeniedAuthorizationException(TokenUtil.CAPTCHA_NOT_CORRECT);
        }
    }

    /**
     * 登录校验
     */
    private Authentication verificationLogin(Map<String, String> parameters) {
        String username = parameters.get("username");
        String password = parameters.remove("password");

        Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
        try {
            userAuth = authenticationManager.authenticate(userAuth);
        } catch (Exception ase) {
            //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
            throw new InvalidGrantException(ase.getMessage());
        }

        return userAuth;
    }

    /**
     * 小程序登录
     */
    private Authentication storageAppletLogin(Map<String, String> parameters, String appid, String secret) {
        Authentication userAuth;

        String username = parameters.get("username");
        String password = parameters.get("password");
        String jsCode = parameters.get("jsCode");

        // 获取openId
        String openId;
        try {
            openId = WxAppletUtil.getOpenId(appid, secret, jsCode);
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new UserDeniedAuthorizationException("调用微信接口出现错误");
        }

        IUserClient userClient = SpringUtil.getBean(IUserClient.class);

        if (ObjectUtil.isNotEmpty(username) && ObjectUtil.isNotEmpty(password)) {
            userAuth = verificationLogin(parameters);

            BladeUserDetails bladeUserDetails = (BladeUserDetails) userAuth.getPrincipal();
            Long userId = bladeUserDetails.getUserId();

            User user = new User();
            user.setId(userId);
            user.setAppletOpenId(openId);

            userClient.updateUserById(user);
        } else {
            try {
                User user = userClient.getUserInfoByAppletOpenId(openId);
                parameters.put("username", user.getAccount());

                userAuth = verificationLogin(parameters);
            } catch (Exception e) {
                throw new UserDeniedAuthorizationException("登录信息已过期，请重新登录");
            }
        }

        return userAuth;
    }

    /**
     * 外贸超人登录
     */
    private Authentication foreignTradeSupermanLogin(Map<String, String> parameters) {
        String token = parameters.get("token");
        String username = parameters.get("username");

        if (token == null || username == null) {
            throw new UserDeniedAuthorizationException("非法登录");
        }

        String jwtUsername = JwtUtil.parseToken(token, String.class);

        if (!username.equals(jwtUsername)) {
            throw new UserDeniedAuthorizationException("非法登录");
        }

        return verificationLogin(parameters);
    }


}
