package com.sf.oauth.request;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpUtil;
import com.sf.common.constant.HttpStatus;
import com.sf.oauth.config.AuthConfig;
import com.sf.oauth.config.AuthSource;
import com.sf.oauth.exception.AuthException;
import com.sf.oauth.domain.AuthCallback;
import com.sf.oauth.domain.AuthToken;
import com.sf.oauth.domain.AuthUser;
import com.sf.oauth.utils.AuthChecker;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 默认的request处理类
 *
 * @author zoukun
 */
@Slf4j
public abstract class AuthDefaultRequest implements AuthRequest {
    protected AuthConfig config;
    protected AuthSource source;

    public AuthDefaultRequest(AuthConfig config, AuthSource source) {
        this.config = config;
        this.source = source;
        if (!AuthChecker.isSupportedAuth(config, source)) {
            throw new AuthException(HttpStatus.ERROR,"Parameter incomplete", source);
        }
        // 校验配置合法性
        // AuthChecker.checkConfig(config, source);
    }

    /**
     * 获取access token
     *
     * @param authCallback 授权成功后的回调参数
     * @return token
     * @see AuthDefaultRequest#authorize()
     * @see AuthDefaultRequest#authorize(String)
     */
    protected abstract AuthToken getAccessToken(AuthCallback authCallback);

    /**
     * 使用token换取用户信息
     *
     * @param authToken token信息
     * @return 用户信息
     * @see AuthDefaultRequest#getAccessToken(AuthCallback)
     */
    protected abstract AuthUser getUserInfo(AuthToken authToken);

    /**
     * 统一的登录入口。当通过{@link AuthDefaultRequest#authorize(String)}授权成功后，会跳转到调用方的相关回调方法中
     * 方法的入参可以使用{@code AuthCallback}，{@code AuthCallback}类中封装好了OAuth2授权回调所需要的参数
     *
     * @param authCallback 用于接收回调参数的实体
     * @return AuthResponse
     */
    @Override
    public AuthUser callback(AuthCallback authCallback) {
        try {
            checkCode(authCallback);
            if (!config.isIgnoreCheckState()) {
                AuthChecker.checkState(authCallback.getState(), source);
            }

            AuthToken authToken = this.getAccessToken(authCallback);
            return this.getUserInfo(authToken);
        } catch (Exception e) {
            log.error("Failed to callback with oauth authorization", e);
            throw new AuthException(HttpStatus.UNAUTHORIZED,"Failed to callback with oauth authorization");
        }
    }

    protected void checkCode(AuthCallback authCallback) {
        AuthChecker.checkCode(source, authCallback);
    }


    /**
     * 返回授权url，可自行跳转页面
     * <p>
     * 不建议使用该方式获取授权地址，不带{@code state}的授权地址，容易受到csrf攻击。
     * 建议使用{@link AuthDefaultRequest#authorize(String)}方法生成授权地址，在回调方法中对{@code state}进行校验
     *
     * @return 返回授权地址
     * @see AuthDefaultRequest#authorize(String)
     */
    @Override
    public String authorize() {
        return this.authorize(null);
    }

    /**
     * 返回带{@code state}参数的授权url，授权回调时会带上这个{@code state}
     *
     * @param state state 验证授权流程的参数，可以防止csrf
     * @return 返回授权地址
     */
    @Override
    public String authorize(String state) {
        Map<String, Object> form = new HashMap<>(8);
        form.put("client_id", config.getClientId());
        form.put("client_secret", config.getClientSecret());
        form.put("redirect_uri", config.getRedirectUri());
        form.put("state", getRealState(state));
        form.put("response_type","code");
        return HttpUtil.get(source.authorize(), form);
    }

    /**
     * 返回获取accessToken的url
     *
     * @param code 授权码
     * @return 返回获取accessToken的url
     */
    protected String accessTokenUrl(String code) {
        Map<String, Object> form = new HashMap<>(8);
        form.put("code", code);
        form.put("client_id", config.getClientId());
        form.put("client_secret", config.getClientSecret());
        form.put("grant_type", "authorization_code");
        form.put("redirect_uri", config.getRedirectUri());
        return HttpUtil.get(source.accessToken(),form);
    }




    /**
     * 获取state，如果为空， 则默认取当前日期的时间戳
     *
     * @param state 原始的state
     * @return 返回不为null的state
     */
    protected String getRealState(String state) {
        if (StrUtil.isEmpty(state)) {
            state = UUID.fastUUID().toString();
        }
        // todo 需要缓存state
        return state;
    }


    /**
     * 获取以 {@code separator}分割过后的 scope 信息
     *
     * @param separator     多个 {@code scope} 间的分隔符
     * @param encode        是否 encode 编码
     * @param defaultScopes 默认的 scope， 当客户端没有配置 {@code scopes} 时启用
     * @return String
     */
    protected String getScopes(String separator, boolean encode, List<String> defaultScopes) {
        List<String> scopes = config.getScopes();
        if (null == scopes || scopes.isEmpty()) {
            if (null == defaultScopes || defaultScopes.isEmpty()) {
                return "";
            }
            scopes = defaultScopes;
        }
        if (null == separator) {
            // 默认为空格
            separator = " ";
        }
        String scopeStr = String.join(separator, scopes);
        return encode ? URLUtil.encode(scopeStr) : scopeStr;
    }

}
