package cn.axzo.msg.center.notices.integration.client.impl;

import cn.axzo.msg.center.notices.common.enums.ReturnCodeEnum;
import cn.axzo.msg.center.notices.common.exception.BizException;
import cn.axzo.msg.center.notices.integration.client.LianLuClient;
import cn.axzo.msg.center.notices.integration.dto.request.LianLuSendTemplateSmsRequest;
import cn.axzo.msg.center.notices.integration.dto.request.SendSmsCommonRequest;
import cn.axzo.msg.center.notices.integration.dto.response.LianLuBaseResponse;
import cn.axzo.msg.center.notices.integration.support.LianLuConfig;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * @description
 *   联麓短信发送client具体实现
 *
 * @author cold_blade
 * @date 2023/9/7
 * @version 1.0
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class LianLuClientImpl implements LianLuClient {

    private final LianLuConfig lianLuConfig;

    @Override
    public LianLuBaseResponse sendSingleTemplateSms(LianLuSendTemplateSmsRequest request) {
        // 参数检测
        check(request);
        // 构造发送模板短信的入参
        Map<String, Object> requestData = buildRequestData(request);
        // post请求
        String jsonStr = Request.requestOnce(Constants.URL_TEMPLATE_SMS_SEND, JSON.toJSONString(requestData));
        log.info("[mns <- lianlu] LianLuClientImpl.sendTemplateSms result: {}", jsonStr);
        LianLuBaseResponse response = JSON.parseObject(jsonStr, LianLuBaseResponse.class);
        response.setRequestId(request.getRequestChannelNo());
        return response;
    }

    private void check(SendSmsCommonRequest request) {
        BizException.error(Objects.nonNull(request), ReturnCodeEnum.INVALID_PARAMETER, "request is required");
        BizException.error(StringUtils.isNotBlank(request.getRequestChannelNo()), ReturnCodeEnum.INVALID_PARAMETER,
                "requestChannelNo is required");
        BizException.error(StringUtils.isNotBlank(request.getPhoneNo()), ReturnCodeEnum.INVALID_PARAMETER,
                "phoneNo is required");
        BizException.error(StringUtils.isNotBlank(request.getTemplateCode()), ReturnCodeEnum.INVALID_PARAMETER,
                "templateCode is required");
    }

    private Map<String, Object> buildRequestData(LianLuSendTemplateSmsRequest request) {
        Map<String, Object> requestData = new HashMap<>();
        requestData.put(Constants.FIELD_MCH_ID, lianLuConfig.getTradeCompanyId());
        requestData.put(Constants.FIELD_APP_ID, lianLuConfig.getTradeAppId());
        requestData.put(Constants.FIELD_VERSION, Constants.VERSION_TEMPLATE_SMS_SEND);
        requestData.put(Constants.FIELD_SIGN_TYPE, Constants.SIGN_TYPE_MD5);
        requestData.put(Constants.FIELD_TIME_STAMP, String.valueOf(System.currentTimeMillis()));
        requestData.put(Constants.FIELD_TEMPLATE_ID, request.getTemplateCode());
        requestData.put(Constants.FIELD_PHONE_NUMBER_SET, request.getPhoneNo().split(","));
        requestData.put(Constants.FIELD_TEMPLATE_PARAM_SET, Objects.isNull(request.getTemplateParams()) ?
                new String[]{} : request.getTemplateParams());
        requestData.put(Constants.FIELD_TYPE, Constants.TYPE_TEMPLATE_SMS_SEND);
        requestData.put(Constants.FIELD_TAG, request.getRequestChannelNo());
        requestData.put(Constants.FIELD_SIGNATURE, Encoder.generateSignature(requestData, lianLuConfig.getTradeAppKey()));
        return requestData;
    }

    private static class Request {

        private static final int CONNECT_TIMEOUT_MILLS = 5000;
        private static final int READ_TIMEOUT_MILLS = 3000;

        /**
         * 请求，只请求一次，不做重试
         *
         */
        static String requestOnce(String url, String data) {
            BasicHttpClientConnectionManager connManager =
                    new BasicHttpClientConnectionManager(RegistryBuilder.<ConnectionSocketFactory>create()
                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
                            .register("https", SSLConnectionSocketFactory.getSocketFactory()).build(), null, null, null);

            HttpPost httpPost = new HttpPost(url);

            try (CloseableHttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connManager).build()) {
                RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(READ_TIMEOUT_MILLS)
                        .setConnectTimeout(CONNECT_TIMEOUT_MILLS).build();
                httpPost.setConfig(requestConfig);
                StringEntity postEntity = new StringEntity(data, StandardCharsets.UTF_8);
                httpPost.setHeader("Accept", ContentType.APPLICATION_JSON.toString());
                httpPost.setHeader("Content-Type", ContentType.APPLICATION_JSON.toString());
                httpPost.setEntity(postEntity);

                HttpResponse httpResponse = httpClient.execute(httpPost);
                HttpEntity httpEntity = httpResponse.getEntity();
                if (httpEntity != null) {
                    return EntityUtils.toString(httpEntity, StandardCharsets.UTF_8);
                }
                throw new BizException(ReturnCodeEnum.SYSTEM_ERROR, "invoke LianLuClientImpl.sendTemplateSms fail");
            } catch (Exception e) {
                log.error("[mns <- lianlu] LianLuClientImpl.sendTemplateSms is fail", e);
                throw new BizException(ReturnCodeEnum.SYSTEM_ERROR, "invoke LianLuClientImpl.sendTemplateSms fail");
            }
        }
    }

    private static class Encoder {

        /**
         * 生成签名. 注：默认采用MD5的方式加密
         *
         * @param data 待签名数据
         * @param apiSecret API密钥
         * @return 签名
         */
        static String generateSignature(final Map<String, Object> data, String apiSecret) {
            Set<String> keySet = data.keySet();
            String[] keyArray = keySet.toArray(new String[0]);
            // 对key数组进行一个升序的排序
            Arrays.sort(keyArray);
            StringBuilder sb = new StringBuilder();
            for (String key : keyArray) {
                if (Constants.EXCLUDE_SIGN_FIELDS.contains(key)) {
                    continue;
                }
                Object val = data.get(key);
                // 参数值为空，则不参与签名
                if (Objects.nonNull(val) && StringUtils.isNotBlank(val.toString())) {
                    sb.append(key).append("=").append(val.toString().trim()).append("&");
                }
            }
            sb.append("key=").append(apiSecret);
            return generateMD5(sb.toString()).toUpperCase();
        }

        /**
         * 生成 MD5
         *
         * @param data 待处理数据
         * @return MD5结果
         */
        private static String generateMD5(String data) {
            MessageDigest md = null;
            try {
                md = MessageDigest.getInstance(Constants.SIGN_TYPE_MD5);
            } catch (NoSuchAlgorithmException e) {
                log.error("[mns <- lianlu] LianLuClientImpl.generateMD5 is fail", e);
                BizException.error(ReturnCodeEnum.SYSTEM_ERROR);
            }
            byte[] array = md.digest(data.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte item : array) {
                sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3);
            }
            return sb.toString().toUpperCase();
        }
    }

    private static class Constants {

        /**
         * 模板短信发送的URL
         */
        private static final String URL_TEMPLATE_SMS_SEND = "https://apis.shlianlu.com/sms/trade/template/send";

        /**
         * 模板短信接口版本
         */
        private static final String VERSION_TEMPLATE_SMS_SEND = "1.1.0";

        private static final String TYPE_TEMPLATE_SMS_SEND = "3";

        /**
         * 加密类型
         */
        private static final String SIGN_TYPE_MD5 = "MD5";

        /*********************** 入参的属性名称映射 ***************************/
        private static final String FIELD_MCH_ID = "MchId";
        private static final String FIELD_APP_ID = "AppId";
        private static final String FIELD_VERSION = "Version";
        private static final String FIELD_TYPE = "Type";
        private static final String FIELD_PHONE_NUMBER_SET = "PhoneNumberSet";
        private static final String FIELD_TEMPLATE_ID = "TemplateId";
        private static final String FIELD_TEMPLATE_PARAM_SET = "TemplateParamSet";
        private static final String FIELD_TIME_STAMP = "TimeStamp";
        private static final String FIELD_SIGN_TYPE = "SignType";
        private static final String FIELD_SIGNATURE = "Signature";
        private static final String FIELD_TAG = "Tag";

        /**
         * 加密时需要怕排除的属性集合
         */
        private static final Set<String> EXCLUDE_SIGN_FIELDS = Sets.newHashSet(
                FIELD_PHONE_NUMBER_SET,
                FIELD_TEMPLATE_PARAM_SET,
                FIELD_SIGNATURE
        );
    }
}
