package cn.lili.modules.connect.util;

import cn.hutool.json.JSONUtil;
import cn.lili.cache.Cache;
import cn.lili.cache.CachePrefix;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.enums.ResultUtil;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.token.Token;
import cn.lili.common.vo.ResultMessage;
import cn.lili.common.properties.ApiProperties;
import cn.lili.common.properties.DomainProperties;
import cn.lili.common.enums.ClientTypeEnum;
import cn.lili.modules.connect.config.AuthConfig;
import cn.lili.modules.connect.config.ConnectAuthEnum;
import cn.lili.modules.connect.entity.dto.AuthCallback;
import cn.lili.modules.connect.entity.dto.AuthResponse;
import cn.lili.modules.connect.entity.dto.ConnectAuthUser;
import cn.lili.modules.connect.exception.AuthException;
import cn.lili.modules.connect.request.BaseAuthQQRequest;
import cn.lili.modules.connect.request.AuthRequest;
import cn.lili.modules.connect.request.BaseAuthWeChatPCRequest;
import cn.lili.modules.connect.request.BaseAuthWeChatRequest;
import cn.lili.modules.connect.service.ConnectService;
import cn.lili.modules.system.entity.dos.Setting;
import cn.lili.modules.system.entity.dto.connect.QQConnectSetting;
import cn.lili.modules.system.entity.dto.connect.WechatConnectSetting;
import cn.lili.modules.system.entity.dto.connect.dto.QQConnectSettingItem;
import cn.lili.modules.system.entity.dto.connect.dto.WechatConnectSettingItem;
import cn.lili.modules.system.entity.enums.SettingEnum;
import cn.lili.modules.system.service.SettingService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 联合登陆工具类
 *
 * @author Chopper
 * @version v1.0
 * 2020-11-25 21:16
 */
@Slf4j
@Component
public class ConnectUtil {

    @Autowired
    private Cache cache;
    @Autowired
    private ConnectService connectService;
    @Autowired
    private SettingService settingService;
    @Autowired
    private ApiProperties apiProperties;
    @Autowired
    private DomainProperties domainProperties;


    static String prefix = "/buyer/connect/callback/";

    /**
     * 回调地址获取
     * @param connectAuthEnum 用户枚举
     * @return 回调地址
     */
    String getRedirectUri(ConnectAuthEnum connectAuthEnum) {
        return apiProperties.getBuyer() + prefix + connectAuthEnum.getName();
    }

    /**
     * 登录回调
     *
     * @param type
     * @param callback
     * @param httpServletResponse
     * @param httpServletRequest
     * @throws IOException
     */
    public void callback(String type, AuthCallback callback, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
        AuthRequest authRequest = this.getAuthRequest(type);
        AuthResponse<ConnectAuthUser> response = authRequest.login(callback);
        ResultMessage<Object> resultMessage;
        //联合登陆处理，如果响应正常，则录入响应结果到redis
        if (response.ok()) {
            ConnectAuthUser authUser = response.getData();
            Token token;
            try {
                token = connectService.unionLoginCallback(type, authUser, callback.getState());
                resultMessage = ResultUtil.data(token);
            } catch (ServiceException e) {
                throw new ServiceException(ResultCode.ERROR, e.getMessage());
            }
        }
        //否则录入响应结果，等待前端获取信息
        else {
            throw new ServiceException(ResultCode.ERROR, response.getMsg());
        }
        //缓存写入登录结果，300秒有效
        cache.put(CachePrefix.CONNECT_RESULT.getPrefix() + callback.getCode(), resultMessage, 300L);

        //跳转地址
        String url = this.check(httpServletRequest.getHeader("user-agent")) ?
                domainProperties.getWap() + "/pages/passport/login?state=" + callback.getCode() :
                domainProperties.getPc() + "/login?state=" + callback.getCode();

        try {
            httpServletResponse.sendRedirect(url);
        } catch (Exception e) {
            log.error("登录回调错误",e);
        }
    }

    /**
     * 获取响应结果
     *
     * @param state
     * @return
     */
    public ResultMessage<Object> getResult(String state) {
        Object object = cache.get(CachePrefix.CONNECT_RESULT.getPrefix() + state);
        if (object == null) {
            return null;
        } else {
            cache.remove(CachePrefix.CONNECT_RESULT.getPrefix() + state);
            return (ResultMessage<Object>) object;
        }
    }

    /**
     * 联合登录
     *
     * @param type 枚举
     * @return
     */
    public AuthRequest getAuthRequest(String type) {
        ConnectAuthEnum authInterface = ConnectAuthEnum.valueOf(type);
        if (authInterface == null) {
            throw new ServiceException(ResultCode.CONNECT_NOT_EXIST);
        }
        AuthRequest authRequest = null;
        switch (authInterface) {
            case WECHAT: {
                //寻找配置
                Setting setting = settingService.get(SettingEnum.WECHAT_CONNECT.name());
                WechatConnectSetting wechatConnectSetting = JSONUtil.toBean(setting.getSettingValue(), WechatConnectSetting.class);

                for (WechatConnectSettingItem wechatConnectSettingItem : wechatConnectSetting.getWechatConnectSettingItems()) {
                    if (wechatConnectSettingItem.getClientType().equals(ClientTypeEnum.H5.name())) {
                        authRequest = new BaseAuthWeChatRequest(AuthConfig.builder()
                                .clientId(wechatConnectSettingItem.getAppId())
                                .clientSecret(wechatConnectSettingItem.getAppSecret())
                                .redirectUri(getRedirectUri(authInterface))
                                .build(), cache);
                    }
                }
                break;
            }
            case WECHAT_PC: {
                //寻找配置
                Setting setting = settingService.get(SettingEnum.WECHAT_CONNECT.name());
                WechatConnectSetting wechatConnectSetting = JSONUtil.toBean(setting.getSettingValue(), WechatConnectSetting.class);
                for (WechatConnectSettingItem wechatConnectSettingItem : wechatConnectSetting.getWechatConnectSettingItems()) {
                    if (wechatConnectSettingItem.getClientType().equals(ClientTypeEnum.PC.name())) {
                        authRequest = new BaseAuthWeChatPCRequest(AuthConfig.builder()
                                .clientId(wechatConnectSettingItem.getAppId())
                                .clientSecret(wechatConnectSettingItem.getAppSecret())
                                .redirectUri(getRedirectUri(authInterface))
                                .build(), cache);
                    }
                }

                break;
            }
            case QQ:

                //寻找配置
                Setting setting = settingService.get(SettingEnum.QQ_CONNECT.name());
                QQConnectSetting qqConnectSetting = JSONUtil.toBean(setting.getSettingValue(), QQConnectSetting.class);
                for (QQConnectSettingItem qqConnectSettingItem : qqConnectSetting.getQqConnectSettingItemList()) {
                    if (qqConnectSettingItem.getClientType().equals(ClientTypeEnum.PC.name())) {
                        authRequest = new BaseAuthQQRequest(AuthConfig.builder()
                                .clientId(qqConnectSettingItem.getAppId())
                                .clientSecret(qqConnectSettingItem.getAppKey())
                                .redirectUri(getRedirectUri(authInterface))
                                //这里qq获取unionid 需要配置为true，详情可以查阅属性说明，内部有帮助文档
                                .unionId(true)
                                .build(), cache);
                    }
                }

                break;
            default:
                break;
        }
        if (null == authRequest) {
            throw new AuthException("暂不支持第三方登陆");
        }
        return authRequest;
    }

    /**
     * \b 是单词边界(连着的两个(字母字符 与 非字母字符) 之间的逻辑上的间隔),
     * 字符串在编译时会被转码一次,所以是 "\\b"
     * \B 是单词内部逻辑间隔(连着的两个字母字符之间的逻辑上的间隔)
     */
    static String phoneReg = "\\b(ip(hone|od)|android|opera m(ob|in)i"
            + "|windows (phone|ce)|blackberry"
            + "|s(ymbian|eries60|amsung)|p(laybook|alm|rofile/midp"
            + "|laystation portable)|nokia|fennec|htc[-_]"
            + "|mobile|up.browser|[1-4][0-9]{2}x[1-4][0-9]{2})\\b";
    static String tableReg = "\\b(ipad|tablet|(Nexus 7)|up.browser"
            + "|[1-4][0-9]{2}x[1-4][0-9]{2})\\b";

    /**
     * 移动设备正则匹配：手机端、平板
     */
    static Pattern phonePat = Pattern.compile(phoneReg, Pattern.CASE_INSENSITIVE);
    static Pattern tablePat = Pattern.compile(tableReg, Pattern.CASE_INSENSITIVE);

    /**
     * 检测是否是移动设备访问
     *
     * @param userAgent 浏览器标识
     * @return true:移动设备接入，false:pc端接入
     * @Title: check
     */
    private boolean check(String userAgent) {
        if (null == userAgent) {
            userAgent = "";
        }
        //匹配
        Matcher matcherPhone = phonePat.matcher(userAgent);
        Matcher matcherTable = tablePat.matcher(userAgent);
        if (matcherPhone.find() || matcherTable.find()) {
            return true;
        } else {
            return false;
        }
    }
}

