package com.flossom.miniProgram.utils;

import com.alibaba.fastjson.JSON;
import com.flossom.common.core.constant.CacheConstants;
import com.flossom.common.core.domain.entity.WxParameterSetting;
import com.flossom.common.core.exception.ServiceException;
import com.flossom.common.core.mapper.WxParameterSettingMapper;
import com.flossom.common.core.utils.StringUtils;
import com.flossom.common.redis.service.RedisService;
import com.flossom.miniProgram.config.properties.WxConfig;
import com.flossom.miniProgram.domain.vo.OfficialAccountUserDetailRet;
import com.flossom.miniProgram.domain.vo.OfficialAccountUserReq;
import com.flossom.miniProgram.domain.vo.OfficialAccountUserRet;
import com.flossom.miniProgram.domain.vo.WxAccessTokenRet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 小程序工具类
 */
@Component
public class MiniProgramUtils {

    protected final static Logger logger = LoggerFactory.getLogger(MiniProgramUtils.class);

    private static WxConfig wxConfig;

    private static WxParameterSettingMapper wxParameterSettingMapper;

    private static RedisService redisService;

    /**
     * 注入 微信配置mapper，获取 appId，appKey（该配置在数据库）
     *
     * @param wxParameterSettingMapper
     */
    @Autowired
    public void setWxConfig(WxParameterSettingMapper wxParameterSettingMapper) {
        MiniProgramUtils.wxParameterSettingMapper = wxParameterSettingMapper;
    }

    @Autowired
    public void setRedisService(RedisService redisService) {
        MiniProgramUtils.redisService = redisService;
    }

    /**
     * 注入 微信配置类，读取请求路径（该配置在 nacos）
     *
     * @param wxConfig
     */
    @Autowired
    public void setWxConfig(WxConfig wxConfig) {
        MiniProgramUtils.wxConfig = wxConfig;
    }


    public static String getSessionKeyAndOpenId(String jsCode) throws Exception {
        WxParameterSetting wxParameterSetting = wxParameterSettingMapper.selectWxParameterSettingById(1L);
        return getSessionKeyAndOpenId(wxParameterSetting.getAppid(), wxParameterSetting.getAppkey(), jsCode, "authorization_code");
    }

    /**
     * 通过 code 去微信服务器换取 openid 和 session_key
     *
     * @param appid
     * @param secret
     * @param jsCode
     * @param grant_type
     * @return <p>
     * {
     * "openid":"xxxxxx",              用户唯一标识
     * "session_key":"xxxxx",          会话密钥
     * "unionid":"xxxxx",
     * "errcode":0,                    错误码
     * "errmsg":"xxxxx"
     * }
     * <p>
     * errcode ：
     * 40029	code 无效	                    js_code 无效
     * 45011	api minute-quota reach limit    mustslower  retry next minute	API 调用太频繁，请稍候再试
     * 40226	code blocked	                高风险等级用户，小程序登录拦截 。风险等级详见用户安全解方案
     * -1	    system error	                系统繁忙，此时请开发者稍候再试
     */
    public static String getSessionKeyAndOpenId(String appid, String secret, String jsCode, String grant_type) throws Exception {
        Map<String, String> params = new HashMap<>();
        params.put("appid", appid);
        params.put("secret", secret);
        params.put("js_code", jsCode);
        params.put("grant_type", grant_type);
        return HttpClientUtils.getParameters(wxConfig.getLoginUrl(), params);
    }

    /**
     * 获取 acceess_token
     * 微信小程序 / 微信公众号 获取地址是一样得，但是 要用各自得 appid 和 secret
     * <p>
     * 将 access_token 保存在 redis，官方定义说 access_token 的有效期暂定为 2个小时，
     * 使用注意事项： 从 redis 获取 access_token，存在则未过期，不存在则过期了，重新获取 access_token
     *
     * @return
     * @throws Exception
     */
    public static String getAccessToken(String appid, String secret, String cacheKey) throws Exception {
        String accessToken = redisService.getCacheObject(cacheKey);
        if (StringUtils.isBlank(accessToken)) {
            // 获取 access_token
            Map<String, String> params = new HashMap();
            params.put("appid", appid);
            params.put("secret", secret);
            params.put("grant_type", "client_credential");
            String result = HttpClientUtils.getParameters(wxConfig.getAccessTokenUrl(), params);
            logger.info("请求微信服务器获取access_token返回结果：{}", result);
            WxAccessTokenRet wxAccessTokenRet = JSON.parseObject(result, WxAccessTokenRet.class);
            if (wxAccessTokenRet.getErrcode() != null) {
                logger.error("获取 access_token 失败: {}", wxAccessTokenRet.getErrmsg());
                return null;
            }
            // 保存缓存，按照返回的有效期来缓存，如果没有有效期则临时使用一次
            if (wxAccessTokenRet.getExpires_in() != null && wxAccessTokenRet.getExpires_in() > 0) {
                Integer minutes = wxAccessTokenRet.getExpires_in() / 60;
                redisService.setCacheObject(cacheKey,
                        wxAccessTokenRet.getAccess_token(), minutes.longValue(), TimeUnit.MINUTES);
            }
            return wxAccessTokenRet.getAccess_token();
        }
        return accessToken;
    }

    /**
     * 注意 要使用 微信小程序得 appid 和 appKey
     *
     * @param code
     * @return <p></p>
     * errcode	number	错误码
     * errmsg	string	错误信息
     * phone_info	object	用户手机号信息
     * 属性	                类型	        说明
     * phoneNumber	        string	    用户绑定的手机号（国外手机号会有区号）
     * purePhoneNumber	    string	    没有区号的手机号
     * countryCode     	string	    区号
     */
    public static String getPhone(String code) throws Exception {
        WxParameterSetting wxParameterSetting = wxParameterSettingMapper.selectWxParameterSettingById(1L);
        String accessToken = getAccessToken(wxParameterSetting.getAppid(), wxParameterSetting.getAppkey(), CacheConstants.WX_MINI_PROGRAM_ACCESS_TOKEN_CACHE);
        if (StringUtils.isBlank(accessToken)) {
            throw new ServiceException("获取用户手机号码失败");
        }

        Map<String, String> params = new HashMap();
        params.put("code", code);
        return HttpClientUtils.postParameters(wxConfig.getObtainPhoneUrl() + "?access_token=" + accessToken, JSON.toJSONString(params));
    }

    /**
     * 分页 获取关注微信公众号的用户列表
     * TODO: 一次拉取调用最多拉取10000个关注者的OpenID
     *
     * @param nextOpenid 下一个用户openid
     * @return
     * @throws Exception
     */
    public static OfficialAccountUserRet obtainWxOfficialAccountUser(String nextOpenid) throws Exception {
        String accessToken = getAccessToken(wxConfig.getOfficialAccountAppid(), wxConfig.getOfficialAccountAppSecret(), CacheConstants.WX_OFFICIAL_ACCOUNT_ACCESS_TOKEN_CACHE);
        if (StringUtils.isBlank(accessToken)) {
            logger.error("获取关注微信公众号用户列表失败，获取accessToken失败！");
            throw new ServiceException("操作失败");
        }

        Map<String, String> params = new HashMap();
        params.put("access_token", accessToken);
        if (StringUtils.isNotBlank(nextOpenid)) {
            params.put("next_openid", nextOpenid);
        }
        String result = HttpClientUtils.getParameters(wxConfig.getObtainWxOfficialAccountUserUrl(), params);
        logger.info("请求微信服务器获取关注微信公众号的用户列表：{}", result);
        OfficialAccountUserRet officialAccountUserRet = JSON.parseObject(result, OfficialAccountUserRet.class);
        if (officialAccountUserRet.getErrcode() != null) {
            logger.error("获取 关注微信公众号的用户列表 失败: {}", officialAccountUserRet.getErrmsg());
            throw new ServiceException("操作失败");
        }
        return officialAccountUserRet;
    }

    /**
     * 获取 关注微信公众号用户的 unionid
     *  TODO: officialAccountUserReqList 最多支持 100 条
     */
    public static OfficialAccountUserDetailRet ObtainUserInfoByOpenid(List<OfficialAccountUserReq> officialAccountUserReqList) throws Exception {
        String accessToken = getAccessToken(wxConfig.getOfficialAccountAppid(), wxConfig.getOfficialAccountAppSecret(), CacheConstants.WX_OFFICIAL_ACCOUNT_ACCESS_TOKEN_CACHE);
        if (StringUtils.isBlank(accessToken)) {
            logger.error("获取关注微信公众号用户的unionid，获取accessToken失败！");
            throw new ServiceException("操作失败");
        }

        Map<String, Object> params = new HashMap();
        params.put("user_list", officialAccountUserReqList);
//        String result = HttpClientUtils.postJsonParameters(wxConfig.getObtainUserInfoByOpenidUrl() + "?access_token=" + accessToken, JSON.toJSONString(params));
        String result = HttpClientUtils.postParameters(wxConfig.getObtainUserInfoByOpenidUrl() + "?access_token=" + accessToken, JSON.toJSONString(params));
        logger.info("请求微信服务器获获取 关注微信公众号用户的 unionid列表：{}", result);
        OfficialAccountUserDetailRet officialAccountUserDetailRet = JSON.parseObject(result, OfficialAccountUserDetailRet.class);
        if (officialAccountUserDetailRet.getErrcode() != null) {
            logger.error("获取 关注微信公众号的用户列表 失败: {}", officialAccountUserDetailRet.getErrmsg());
            throw new ServiceException("操作失败");
        }
        return officialAccountUserDetailRet;
    }

    /**
     * 是否关注微信公众号
     *
     * @return
     */
    public static Boolean isAttentionOfficialAccount(String unionId) throws Exception {
        String nextOpenid = null;
        Integer currentTotal = 0;
        while (true) {
            // 1、获取关注微信公众号的用户列表
            OfficialAccountUserRet officialAccountUserRet = obtainWxOfficialAccountUser(null);
            if (officialAccountUserRet.getCount() == null || officialAccountUserRet.getCount() == 0) {
                return false;
            }
            currentTotal += officialAccountUserRet.getCount();
            nextOpenid = officialAccountUserRet.getNext_openid();
            List<String> openidList = officialAccountUserRet.getOpenidList();
            logger.info("微信公众号关注列表：{}", openidList);

            // 2、获取关注微信公众号的unionid
            int pageSize = 10;
            int page = officialAccountUserRet.getTotal() % pageSize == 0 ? officialAccountUserRet.getTotal() / pageSize : officialAccountUserRet.getTotal() / pageSize + 1;
            if (page > 0) {
                List<String> pageOpenidList = officialAccountUserRet.getOpenidList();
                for (int pageNo = 1; pageNo <= page; pageNo++) {
                    // 构建分页集合
                    List<String> pageList = pageOpenidList.stream().skip((pageNo - 1) * pageSize).limit(pageSize).collect(Collectors.toList());
                    // 获取 unionid 集合
                    List<OfficialAccountUserReq> officialAccountUserReqList = new ArrayList<>();
                    for (String openid : pageList) {
                        OfficialAccountUserReq officialAccountUserReq = new OfficialAccountUserReq(openid);
                        officialAccountUserReqList.add(officialAccountUserReq);
                    }
                    OfficialAccountUserDetailRet officialAccountUserDetailRet = ObtainUserInfoByOpenid(officialAccountUserReqList);

                    // 3、判断当前unionid是否在关注者列表
                    if (officialAccountUserDetailRet.isContainUnionid(unionId)) {
                        return true;
                    }
                }
            }

            // 4、 查完全部，未找到退出
            if (StringUtils.isBlank(nextOpenid) || officialAccountUserRet.getTotal() == currentTotal) {
                break;
            }
        }
        return false;
    }


}
