package com.microservices.cms.doc.service.impl;

import com.alibaba.fastjson2.JSONObject;
import com.microservices.cms.doc.domain.WechatSignatureInput;
import com.microservices.cms.doc.domain.WechatSignatureVo;
import com.microservices.cms.doc.service.ICmsWechatService;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.httpClient.service.HttpAPIService;
import com.microservices.common.redis.service.RedisService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

/**
 * @author otto
 */
@Service
public class CmsWechatServiceImpl implements ICmsWechatService {
    private static final Logger logger = LoggerFactory.getLogger(CmsWechatServiceImpl.class);
    @Autowired
    private HttpAPIService httpAPIService;
    @Autowired
    private RedisService redisService;

    @Value("${cms.wechat.appID:}")
    public String appID;
    @Value("${cms.wechat.appSecret:}")
    public String appSecret;

    @Override
    public WechatSignatureVo getWechatSignature(WechatSignatureInput wechatSignatureInput) {
        if (StringUtils.isEmpty(appID) || StringUtils.isEmpty(appSecret)) {
            throw new ServiceException("请配置微信参数");
        }
        // 待签名的参数
        Map<String, String> params = new HashMap<>();
        String nonceStr = generateSecureRandomString(16);
        String jsapiTicket = getJsapiTicket();
        Long timestamp = System.currentTimeMillis() / 1000;
        params.put("noncestr", nonceStr);
        params.put("jsapi_ticket", jsapiTicket);
        params.put("timestamp", String.valueOf(timestamp));
        params.put("url", wechatSignatureInput.getUrl());
        // 将参数按照字段名ASCII码从小到大排序
        Map<String, String> sortedParams = new TreeMap<>(params);
        // 拼接成字符串string1
        StringBuilder string1 = new StringBuilder();
        for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
            string1.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }
        // 移除最后一个"&"符号
        string1.deleteCharAt(string1.length() - 1);
        // 对string1进行SHA1加密
        String signature = sha1Encrypt(string1.toString());
        WechatSignatureVo wechatSignatureVo = new WechatSignatureVo();
        wechatSignatureVo.setTimestamp(timestamp);
        wechatSignatureVo.setNoncestr(nonceStr);
        wechatSignatureVo.setSignature(signature);
        return wechatSignatureVo;
    }

    private String getAccessToken() {
        String redisKey = "wechatAccessToken-" + appID;
        if (redisService.hasKey(redisKey)) {
            return redisService.getCacheObject(redisKey);
        }
        String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"
                , appID, appSecret);

        String accessTokenKey = "access_token";
        String expiresInKey = "expires_in";
        JSONObject res = doGetWechat(url);
        if (res.containsKey(accessTokenKey)) {
            String accessToken = res.getString(accessTokenKey);
            Long expiresIn = res.getLong(expiresInKey);
            if (expiresIn > 1) {
                redisService.setCacheObject(redisKey, accessToken, expiresIn - 1, TimeUnit.SECONDS);
                return accessToken;
            }
        }
        logger.error("获取微信公众号AccessToken失败:{}", res);
        throw new ServiceException("获取微信公众号AccessToken失败，请联系管理员");
    }

    private String getJsapiTicket() {
        String redisKey = "wechatJsapiTicket-" + appID;
        if (redisService.hasKey(redisKey)) {
            return redisService.getCacheObject(redisKey);
        }
        String accessToken = getAccessToken();
        String url = String.format("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi"
                , accessToken);
        JSONObject res = doGetWechat(url);
        String errCodeKey = "errcode";
        String ticketKey = "ticket";
        String expiresInKey = "expires_in";
        if (res.containsKey(errCodeKey) && res.getInteger(errCodeKey).equals(0)) {
            String ticket = res.getString(ticketKey);
            Long expiresIn = res.getLong(expiresInKey);
            if (expiresIn > 1) {
                redisService.setCacheObject(redisKey, ticket, expiresIn - 1, TimeUnit.SECONDS);
                return ticket;
            }
        }
        logger.error("获取微信公众号JsapiTicket失败:{}", res);
        throw new ServiceException("获取微信公众号JsapiTicket失败，请联系管理员");
    }

    private JSONObject doGetWechat(String url) {
        try {
            return httpAPIService.doGet(url, null);
        } catch (Exception e) {
            logger.error("获取微信公众号签名失败:{}", e.getMessage());
            throw new ServiceException("获取微信公众号签名失败，请联系管理员");
        }
    }

    // SHA1加密
    private static String sha1Encrypt(String input) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
            messageDigest.update(input.getBytes());
            byte[] digest = messageDigest.digest();
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            logger.error("对签名进行SHA1加密时失败：{}", e.getMessage());
            throw new ServiceException("获取微信公众号签名失败，请联系管理员");
        }
    }

    // 生成随机字符串
    private static String generateNonceStr() {
        // 随机数生成器
        StringBuilder nonceStr = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < 16; i++) {
            int num = random.nextInt(10); // 0-9
            nonceStr.append(num);
        }
        return nonceStr.toString();
    }

    public static String generateSecureRandomString(int length) {
        char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
        SecureRandom random = new SecureRandom();
        char[] text = new char[length];
        for (int i = 0; i < length; i++) {
            text[i] = chars[random.nextInt(chars.length)];
        }
        return new String(text);
    }

}
