package com.cf.imes.module.system.service.sms;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Pair;
import com.cf.imes.framework.common.enums.CommonStatusEnum;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.module.system.dal.redis.RedisKeyConstants;
import com.cf.imes.module.system.framework.sms.config.SmsCodeProperties;
import com.cf.imes.module.system.framework.sms.core.client.SmsClient;
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;
import com.cf.imes.module.system.framework.sms.core.client.dto.SmsSendRespDTO;
import com.cf.imes.module.system.dal.dataobject.sms.SmsTemplateDO;
import com.cf.imes.module.system.mq.message.sms.SmsSendMessage;
import com.cf.imes.module.system.enums.ErrorCodeConstants;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import jakarta.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 短信发送 Service 发送的实现
 *
 * @author 晨丰科技
 */
@Service
@Slf4j
public class SmsSendServiceImpl implements SmsSendService {

    @Resource
    private SmsChannelService smsChannelService;
    @Resource
    private SmsTemplateService smsTemplateService;
    @Resource
    private SmsLogService smsLogService;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private SmsCodeProperties smsCodeProperties;

    @Override
    public Long sendSingleSms(String mobile, Long userId, Integer userType,
                              String templateCode, Map<String, Object> templateParams) {
        // 校验短信模板是否合法
        SmsTemplateDO template = validateSmsTemplate(templateCode);

        // 校验手机号码是否存在
        mobile = validateMobile(mobile);
        // 构建有序的模板参数。为什么放在这个位置，是提前保证模板参数的正确性，而不是到了插入发送日志
        List<Pair<String, Object>> newTemplateParams = buildTemplateParams(template, templateParams);

        // 创建发送日志。如果模板被禁用，则不发送短信，只记录日志
        Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus());
        String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams);
        Long sendLogId = smsLogService.createSmsLog(mobile, userId, userType, isSend, template, content, templateParams);

        // 发送 MQ 消息，异步执行发送短信
        if (isSend) {
            SmsSendMessage message = new SmsSendMessage().setLogId(sendLogId).setMobile(mobile);
            message.setChannelId(template.getChannelId()).setApiTemplateId(template.getApiTemplateId()).setTemplateType(template.getType()).setTemplateParams(newTemplateParams);
            doSendSms(message);
        }
        return sendLogId;
    }

    @VisibleForTesting
    SmsTemplateDO validateSmsTemplate(String templateCode) {
        // 获得短信模板。考虑到效率，从缓存中获取
        SmsTemplateDO template = smsTemplateService.getSmsTemplateByCodeFromCache(templateCode);
        // 短信模板不存在
        if (template == null) {
            throw new ServiceException(ErrorCodeConstants.SMS_SEND_TEMPLATE_NOT_EXISTS);
        }
        return template;
    }

    /**
     * 将参数模板，处理成有序的 KeyValue 数组
     * <p>
     * 原因是，部分短信平台并不是使用 key 作为参数，而是数组下标，例如说 <a href="https://cloud.tencent.com/document/product/382/39023">腾讯云</a>
     *
     * @param template       短信模板
     * @param templateParams 原始参数
     * @return 处理后的参数
     */
    @VisibleForTesting
    List<Pair<String, Object>> buildTemplateParams(SmsTemplateDO template, Map<String, Object> templateParams) {
        return template.getParams().stream().map(key -> {
            Object value = templateParams.get(key);
            if (value == null) {
                throw new ServiceException(ErrorCodeConstants.SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS, key);
            }
            return new Pair<>(key, value);
        }).collect(Collectors.toList());
    }

    @VisibleForTesting
    public String validateMobile(String mobile) {
        if (StringUtils.isEmpty(mobile)) {
            throw new ServiceException(ErrorCodeConstants.SMS_SEND_MOBILE_NOT_EXISTS);
        }
        return mobile;
    }

    @Override
    public void doSendSms(SmsSendMessage message) {
        // 获得渠道对应的 SmsClient 客户端
        SmsClient smsClient = smsChannelService.getSmsClient();
        Assert.notNull(smsClient, "短信客户端({}) 不存在", message.getChannelId());

        List<Pair<String, Object>> templateParams = message.getTemplateParams();
        String channelCode = null;
        // 发送短信
        try {
            SmsSendRespDTO sendResponse = smsClient.sendSms(message.getLogId(), message.getMobile(),
                    message.getApiTemplateId(), templateParams);
            channelCode = sendResponse.getChannelCode();
            // 发送成功后操作
            if (sendResponse.getSuccess()) {
                for (Pair<String, Object> keyValue : templateParams) {
                    if ("code".equals(keyValue.getKey())) {
                        redisTemplate.opsForValue().set(String.format(RedisKeyConstants.SMS_CAPTCHA_VERIFICATION, sendResponse.getMobile()), keyValue.getValue(), smsCodeProperties.getExpireTimes());
                    }
                }
            }
            smsLogService.updateSmsSendResult(message.getLogId(), sendResponse.getSuccess(),
                    sendResponse.getApiCode(), sendResponse.getApiMsg(),
                    sendResponse.getApiRequestId(), sendResponse.getSerialNo(), channelCode);
        } catch (Throwable ex) {
            log.error("[doSendSms][发送短信异常，日志编号({})]", message.getLogId(), ex);
            smsLogService.updateSmsSendResult(message.getLogId(), false,
                    "EXCEPTION", ExceptionUtil.getRootCauseMessage(ex), null, null, channelCode);
        }
    }

    @Override
    public void receiveSmsStatus(String channelCode, String text) throws Throwable {
        // 获得渠道对应的 SmsClient 客户端
        SmsClient smsClient = smsChannelService.getSmsClient();
        Assert.notNull(smsClient, "短信客户端({}) 不存在", channelCode);
        // 解析内容
        List<SmsReceiveRespDTO> receiveResults = smsClient.parseSmsReceiveStatus(text);
        if (CollUtil.isEmpty(receiveResults)) {
            return;
        }
        // 更新短信日志的接收结果. 因为量一般不大，所以先使用 for 循环更新
        receiveResults.forEach(result -> smsLogService.updateSmsReceiveResult(result.getLogId(),
                result.getSuccess(), result.getReceiveTime(), result.getErrorCode(), result.getErrorMsg()));
    }

}
