package com.jeesharp.business.modules.member.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.jeesharp.business.modules.member.constants.MobileAuthConstants;
import com.jeesharp.business.modules.member.service.SmsCodeService;
import com.jeesharp.framework.constant.basecache.SysCacheConstant;
import com.jeesharp.framework.message.api.MessageReceiver;
import com.jeesharp.framework.message.api.MessageSendFacade;
import com.jeesharp.framework.message.api.MessageSendResult;
import com.jeesharp.framework.sms.core.SmsSendResult;
import com.jeesharp.framework.sms.service.SmsGatewayService;
import com.jeesharp.framework.utils.base.ExceptionUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 短信验证码服务实现：验证码存储于 Redis；登录场景经消息中心 {@link MobileAuthConstants#MESSAGE_SEND_CONFIG_APP_LOGIN} 发送，其余场景走短信网关。
 *
 * @author jeesharp
 * @since 2026-07-13
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SmsCodeServiceImpl implements SmsCodeService {

    private static final int CODE_LENGTH = 6;
    private static final int EXPIRE_SECONDS = 300;
    private static final String PARAM_CODE = "code";
    private static final String PARAM_LOGIN_DATE = "loginDate";
    private static final DateTimeFormatter LOGIN_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    private final StringRedisTemplate redisTemplate;
    private final SmsGatewayService smsGatewayService;
    private final MessageSendFacade messageSendFacade;

    @Override
    public int sendCode(String mobile, String scene) {
        if (StrUtil.isBlank(mobile) || StrUtil.isBlank(scene)) {
            ExceptionUtil.error("手机号和场景不能为空");
        }
        String code = RandomUtil.randomNumbers(CODE_LENGTH);
        String key = buildKey(mobile, scene);
        redisTemplate.opsForValue().set(key, code, EXPIRE_SECONDS, TimeUnit.SECONDS);
        log.info("短信验证码已生成: mobile={}, scene={}, expireSeconds={}", mobile, scene, EXPIRE_SECONDS);

        if (isLoginScene(scene)) {
            sendLoginCodeByMessageCenter(mobile, scene, code);
        } else {
            sendCodeBySmsGateway(mobile, scene, code);
        }
        return EXPIRE_SECONDS;
    }

    @Override
    public boolean verify(String mobile, String scene, String smsCode) {
        if (StrUtil.isBlank(mobile) || StrUtil.isBlank(scene) || StrUtil.isBlank(smsCode)) {
            return false;
        }
        String key = buildKey(mobile, scene);
        String cached = redisTemplate.opsForValue().get(key);
        return smsCode.equals(cached);
    }

    @Override
    public boolean verifyAndRemove(String mobile, String scene, String smsCode) {
        if (!verify(mobile, scene, smsCode)) {
            return false;
        }
        String key = buildKey(mobile, scene);
        redisTemplate.delete(key);
        return true;
    }

    private void sendLoginCodeByMessageCenter(String mobile, String scene, String code) {
        String bizId = scene.toLowerCase() + ":" + mobile;
        MessageSendResult result = messageSendFacade.send(
                MobileAuthConstants.MESSAGE_SEND_CONFIG_APP_LOGIN,
                List.of(MessageReceiver.mobile(mobile)),
                Map.of(
                        PARAM_CODE, code,
                        PARAM_LOGIN_DATE, LocalDate.now().format(LOGIN_DATE_FORMAT)
                ),
                bizId
        );
        if (!result.isSuccess()) {
            log.warn("登录验证码消息中心发送失败: mobile={}, scene={}, sendConfig={}, msg={}",
                    mobile, scene, MobileAuthConstants.MESSAGE_SEND_CONFIG_APP_LOGIN, result.getFailReason());
            if (result.getChannels() != null) {
                result.getChannels().stream()
                        .filter(channel -> !channel.isSuccess())
                        .forEach(channel -> log.warn("登录验证码渠道 {} 发送失败: {}",
                                channel.getChannelType(), channel.getFailReason()));
            }
            ExceptionUtil.error("验证码发送失败，请稍后重试");
        }
    }

    private void sendCodeBySmsGateway(String mobile, String scene, String code) {
        SmsSendResult result = smsGatewayService.send(mobile, scene, Map.of(PARAM_CODE, code));
        if (!result.isSuccess()) {
            log.warn("短信验证码发送失败: mobile={}, scene={}, msg={}", mobile, scene, result.getFailReason());
            ExceptionUtil.error("验证码发送失败，请稍后重试");
        }
    }

    private static boolean isLoginScene(String scene) {
        return MobileAuthConstants.SMS_SCENE_LOGIN.equalsIgnoreCase(scene);
    }

    private String buildKey(String mobile, String scene) {
        return SysCacheConstant.MOBILE_SMS_REDIS_PREFIX + ":" + scene.toLowerCase() + ":" + mobile;
    }
}
