package com.kym.common.utils.wx;


import com.alibaba.fastjson2.JSON;
import com.kym.common.exception.BusinessException;
import com.kym.common.utils.CommUtil;
import com.kym.common.utils.EncryptUtil;
import com.kym.common.utils.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 微信公众开发工具类
 * create by yaop at 2019/4/6 0:03
 *
 * @author yaopeng
 */
public class WxPbUtil {

    private static final Logger logger = LoggerFactory.getLogger(WxPbUtil.class);
    private static WxAccess config = null;

    /**
     * 工具类禁用公有构造方法
     */
    private WxPbUtil() {
    }

    /**
     * 发送客服消息
     *
     * @param openId   接收客户openId
     * @param content  回复消息内容
     * @param miniPath 小程序跳转地址
     */
    public static void sendCustomMessage(String openId, String content, String miniPath) {
        String accessToken = getAccessToken();
        String url = String.format(WxPbConfig.API_SEND_KF_MSG, accessToken);
        try {
            Map<String, Object> param = new HashMap<>(4);
            param.put("touser", openId);
            param.put("msgtype", "text");
            Map<String, String> text = new HashMap<>(2);
            if (CommUtil.isNotEmptyAndNull(miniPath)) {
                String xcxAppId = WxPbConfig.PUBLIC_APP_ID;
                if (CommUtil.isNotEmptyAndNull(xcxAppId)) {
                    // 哈哈哈{appid},跳转
                    content = content.replaceFirst("\\{}", xcxAppId);
//                    StringUtils.replaceOnce(content, "{}", xcxAppId);
                }
            }
            text.put("content", content);
            param.put("text", text);
            String data = JSON.toJSONString(param);
            Map<String, Object> result = sslRequest(url, "post", data);
            if (!checkResp(result)) {
                logger.error("WxPbUtil.sendCustomMessage error msg:{}", JSON.toJSONString(result));
                throw new BusinessException("服务异常");
            }
        } catch (Exception e) {
            logger.error("WxPbUtil.sendCustomMessage error ", e);
        }
    }


    /**
     * jsapi 签名
     */
    public static Map<String, Object> signJsApi(String url) {
        Map<String, Object> ret = new HashMap<>(4);
        String jsapiTicket = WxPbUtil.config.jsApiTicket;
        String nonceStr = UUID.randomUUID().toString();
        String timestamp = Long.toString(System.currentTimeMillis() / 1000);
        String string1;
        String signature = "";
        // 注意这里参数名必须全部小写，且必须有序
        string1 = "jsapi_ticket=" + jsapiTicket + "&noncestr=" + nonceStr + "&timestamp=" + timestamp + "&url=" + url;
        try {
            signature = EncryptUtil.encodeBySHA1(string1);
            ret.put("appId", WxPbConfig.PUBLIC_APP_ID);
            ret.put("timestamp", Long.valueOf(timestamp));
            ret.put("nonceStr", nonceStr);
            ret.put("signature", signature);
        } catch (Exception e) {
            logger.error("WxPbUtil.signJsApi error ", e);
        }
        return ret;
    }

    /**
     * 获取消息模板列表
     *
     * @return
     */
    public static Map<String, Object> getTemplateList() {
        String accessToken = getAccessToken();
        String url = String.format(WxPbConfig.API_TEMPLATE_LIST, accessToken);
        try {
            Map<String, Object> result = sslRequest(url, "post", null);
            if (!checkResp(result)) {
                logger.error("WxPbUtil.getTemplateList error msg:{}", JSON.toJSONString(result));
                throw new BusinessException("服务异常");
            }
            return result;
        } catch (Exception e) {
            logger.error("WxPbUtil.getTemplateList error ", e);
        }
        return null;
    }


    /**
     * 发送公众号消息
     *
     * @param openId     接收者openId
     * @param templateId
     * @param vars
     * @param url        模板跳转链接
     * @param xcxUrl     跳转小程序url
     */
    public static void sendPublicTemplateMessage(String openId, String templateId, Map<String, String> vars, String url, String xcxUrl) {
        String accessToken = getAccessToken();
        Map<String, Object> param = new HashMap<String, Object>();
        param.put("touser", openId);
        param.put("template_id", templateId);
        param.put("url", url);
        param.put("topcolor", "#FF0000");
        //跳转小程序
        if (CommUtil.isNotEmptyAndNull(xcxUrl)) {
//            String xcxAppId = WxMpConfig.XCX_APPID;
            String xcxAppId = "";
            if (CommUtil.isNotEmptyAndNull(xcxAppId)) {
                Map<String, String> xcxParam = new HashMap<>();
                xcxParam.put("appid", xcxAppId);
                xcxParam.put("pagepath", xcxUrl);
                param.put("miniprogram", xcxParam);
            }
        }
        Map<String, Object> varsMap = new HashMap<>();
        vars.forEach((k, v) -> {
            Map<String, String> varValueMap = new HashMap<>();
            varValueMap.put("value", v);
            varValueMap.put("color", "#2b2b2b");
            varsMap.put(k, varValueMap);
        });
        //
        param.put("data", varsMap);
        String data = JSON.toJSONString(param);
        String reqUrl = String.format(WxPbConfig.API_SEND_TEMPLATE_MSG, accessToken);
        try {
            Map<String, Object> result = sslRequest(reqUrl, "post", data);
            if (!checkResp(result)) {
                logger.error("WxPbUtil.sendPublicTemplateMessage error msg:{}", JSON.toJSONString(result));
                throw new BusinessException("服务异常");
            }
        } catch (Exception e) {
            logger.error("WxPbUtil.sendPublicTemplateMessage error ", e);
            throw new RuntimeException("发送模板消息失败: " + e.getMessage(), e);
        }
    }

    /**
     * 获取用户基本信息
     */
    public static Map<String, Object> getUserInfo(String openId) {
        String accessToken = getAccessToken();
        String url = String.format(WxPbConfig.API_USER_INFO, accessToken, openId);
        try {
            Map<String, Object> result = sslRequest(url, "post", null);
            if (!checkResp(result)) {
                logger.error("WxPbUtil.getUserInfo error msg:{}", JSON.toJSONString(result));
                throw new BusinessException("服务异常");
            }
            return result;
        } catch (Exception e) {
            logger.error("WxPbUtil.getUserInfo error ", e);
        }
        return null;
    }

    /**
     * <p>https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842</p>
     * <p>
     * 网页授权获取openId
     */
    public static WxAccess getOpenId(String code, String appId, String appSecret) {
        String url = String.format(WxPbConfig.API_OPENID, appId, appSecret, code);
        try {
            Map<String, Object> result = sslRequest(url, "post", null);
            if (!checkResp(result)) {
                logger.error("WxPbUtil.getOpenId error msg:{}", JSON.toJSONString(result));
                throw new BusinessException("服务异常");
            }
            WxAccess accessToken = new WxAccess();
            accessToken.accessToken = (String) result.get("access_token");
            accessToken.openId = (String) result.get("openid");
            accessToken.unionId = (String) result.get("unionId");
            accessToken.refreshToken = (String) result.get("refresh_token");
            config = accessToken;
            return accessToken;
        } catch (Exception e) {
            logger.error("WxPbUtil.getOpenId error ", e);
        }
        return null;
    }

    /**
     * 获取多媒体素材
     */
    public static byte[] getMediaFile(String mediaId) {
        String accessToken = getAccessToken();
        try {
            String url = String.format(WxPbConfig.API_MEDIAFILE, mediaId, accessToken);
            Map<String, Object> result = sslRequest(url, "post", mediaId);
            String str = (String) result.get("result");
            return str.getBytes();
        } catch (Exception e) {
            logger.error("WxPbUtil.getMediaFile error ", e);
        }
        return null;
    }

    /**
     * 获取jsapi 凭证
     */
    public static String getJsApiTicket() {
        try {
            String accessToken = getAccessToken();
            String url = String.format(WxPbConfig.API_JS_APITICKET, accessToken);
            Map<String, Object> result = sslRequest(url, "get", null);
            if (!checkResp(result)) {
                logger.error("WxPbUtil.getJsApiTicket error msg:{}", JSON.toJSONString(result));
                throw new BusinessException("服务异常");
            }
            String jsApiTicket = (String) result.get("ticket");
            config.jsApiTicket = jsApiTicket;
            return jsApiTicket;
        } catch (Exception e) {
            logger.error("WxPbUtil.getJsApiTicket error ", e);
        }
        return null;
    }

    /**
     * 普通获取访问access_token须和网页授权access_token区分
     */
    public static WxAccess reqAccessToken(String appId, String appSecret) {
        WxAccess accessToken = null;
        try {
            String url = String.format(WxPbConfig.API_ACCESS_TOKEN, appId, appSecret);
            Map<String, Object> result = sslRequest(url, "get", null);
            accessToken = new WxAccess();
            accessToken.accessToken = (String) result.get("access_token");
            accessToken.expireAt = System.currentTimeMillis() + accessToken.expire * 1000;
            accessToken.expire = (int) result.get("expires_in");
        } catch (Exception e) {
            logger.error("WxPbUtil.reqAccessToken error ", e);
            throw new IllegalStateException("can't get access token from weixin server", e);
        }

        return accessToken;
    }

    /**
     * 刷新access_token
     */
    public static void refreshAccessToken(String appId) {
        try {
            String url = String.format(WxPbConfig.API_REFRESH_TOKEN, appId, config.accessToken);
            Map<String, Object> result = sslRequest(url, "get", null);

            if (!checkResp(result)) {
                logger.error("WxPbUtil.refreshAccessToken error msg:{}", JSON.toJSONString(result));
                throw new BusinessException("token异常");
            }
            WxAccess accessToken = new WxAccess();
            accessToken.accessToken = (String) result.get("access_token");
            accessToken.expire = (int) result.get("expires_in");
            accessToken.expireAt = System.currentTimeMillis() + accessToken.expire * 1000;
            config = accessToken;
        } catch (Exception e) {
            logger.error("WxPbUtil.refreshAccessToken error ", e);
        }
    }

    /**
     * 查询用户关注公众号状态
     *
     * @return 1-绑定微信并关注
     */
    public static Integer getSubscribeState(String openId) {
        String accessToken = getAccessToken();
        String url = String.format(WxPbConfig.API_SUBSCRIBE, accessToken, openId);
        Map<String, Object> result = sslRequest(url, "get", null);
        if (!checkResp(result)) {
            logger.error("WxPbUtil.getSubscribeState error,openId:{},content:{}");
            throw new BusinessException("服务异常");
        }
        return (Integer) result.get("subscribe");
    }

    private synchronized static String getAccessToken() {
        //accesstoken过期或不存在
        if (null == config || config.expireAt < System.currentTimeMillis()) {
            config = reqAccessToken(WxPbConfig.PUBLIC_APP_ID, WxPbConfig.PUBLIC_APP_SECRET);
        }
        return config.accessToken;
    }

    private static Map<String, Object> sslRequest(String reqUrl, String method, String body) {
        logger.debug("WxPbUtil.sslRequest req url:{},method:{},body:{}", reqUrl, method, body);
        long startTime = System.currentTimeMillis();
        try {
            String result = null;
            if ("get".equals(method)) {
                result = HttpUtil.get(reqUrl);
            } else {
                result = HttpUtil.post(reqUrl, body);
            }
            logger.info("WxPbUtil.sslRequest resp cost:{}ms,content:{}", (System.currentTimeMillis() - startTime), result);
            try {
                return JSON.parseObject(result, Map.class);
            } catch (Exception e) {
                return Map.of("result", result);
            }
        } catch (Exception e) {
            logger.error("WxPbUtil.sslRequest error,", e);
        }

        return null;
    }

    private static boolean checkResp(Map<String, Object> resp) {
        return null != resp && "0".equals(resp.get("errcode").toString());
    }

    /**
     * 校验公众接口签名
     */
    public static boolean checkSign(String signature, String timestamp, String nonce) {
        logger.info("signature:{},timestamp:{},nonce:{}", signature, timestamp, nonce);
        String[] arr = new String[]{WxPbConfig.TOKEN, timestamp, nonce};
        Arrays.sort(arr);
        StringBuilder content = new StringBuilder();
        for (String s : arr) {
            content.append(s);
        }
        try {
            String sign = EncryptUtil.encodeBySHA1(content.toString());
            return signature.equalsIgnoreCase(sign);
        } catch (Exception e) {
            logger.error("WxPbUtil.checkSign error", e);
        }
        return false;
    }

    /**
     * 获取推广二维码链接（临时）<br>
     * 1.如果用户还未关注公众号，则用户可以关注公众号，关注后微信会将带场景值关注事件推送给开发者。<br>
     * 2.如果用户已经关注公众号，在用户扫描后会自动进入会话，微信也会将带场景值扫描事件推送给开发者。
     *
     * @param sceneId 场景值ID，临时二维码时为32位非0整型，永久二维码时最大值为100000（目前参数只支持1--100000）
     */
    public String spreadQrCode(int sceneId) {
        //有效期最大30天
        int expireSeconds = 2592000;
        String accessToken = getAccessToken();
        Map<String, String> wrap = new HashMap<>(2);
        wrap.put("access_token", accessToken);
        Map<String, Object> actionWrap = new HashMap<>(4);
        actionWrap.put("expire_seconds", expireSeconds);
        actionWrap.put("action_name", "QR_SCENE");
        //
        Map<String, Object> actionInfo = new HashMap<>(2);
        Map<String, Object> sceneInfo = new HashMap<>(2);
        sceneInfo.put("scene_id", sceneId);
        actionInfo.put("scene", sceneInfo);
        actionWrap.put("action_info", actionInfo);
        //
        String data = JSON.toJSONString(actionWrap);
        String url = String.format(WxPbConfig.API_QR_CODE, accessToken);
        try {
            Map<String, Object> result = sslRequest(url, "post", data);
            if (!checkResp(result)) {
                logger.error("WxPbUtil.spreadQrCode error msg:{}", JSON.toJSONString(result));
                throw new BusinessException("服务异常");
            }
            return (String) result.get("url");
        } catch (Exception e) {
            logger.error("WxPbUtil.spreadQrCode error ", e);
        }
        return null;
    }

}
