package com.huimv.employment.service.sms;

import com.huimv.employment.common.constant.RedisKeys;
import com.huimv.employment.common.exception.BizException;
import com.huimv.employment.common.exception.ErrorCode;
import com.huimv.employment.common.util.MobileUtils;
import com.huimv.employment.integration.sms.SmsProperties;
import com.huimv.employment.integration.sms.SmsSender;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.security.SecureRandom;
import java.util.concurrent.TimeUnit;

/**
 * 短信验证码核心服务：负责验证码的生成、Redis 存储、校验与发送调度。
 * <p>
 * Redis Key 规范（参见 {@link RedisKeys}）：
 * <ul>
 *   <li>{@code fe:sms:{mobileHash}} — 验证码本体，TTL 默认 5 分钟</li>
 *   <li>{@code fe:sms:cd:{mobileHash}} — 发送冷却标记，TTL 默认 60 秒</li>
 * </ul>
 * Key 中使用手机号 SHA-256 哈希而非明文，避免 Redis 中泄露敏感信息。
 * </p>
 * <p>
 * 短信实际发送委托给 {@link SmsSender}，根据 {@code fe.sms.mock-enabled} 切换 Mock 或阿里云实现。
 * </p>
 */
@Service
public class SmsCodeService {

    /** 密码学安全随机数，用于非 Mock 模式下生成数字验证码 */
    private static final SecureRandom RANDOM = new SecureRandom();

    private final StringRedisTemplate redisTemplate;
    private final SmsProperties smsProperties;
    private final SmsSender smsSender;

    public SmsCodeService(StringRedisTemplate redisTemplate,
                          SmsProperties smsProperties,
                          SmsSender smsSender) {
        this.redisTemplate = redisTemplate;
        this.smsProperties = smsProperties;
        this.smsSender = smsSender;
    }

    /**
     * 生成验证码、写入 Redis 并调用短信通道发送。
     *
     * @param mobile 手机号（明文，仅用于发短信，Redis Key 使用哈希值）
     * @return 当前是否 Mock 模式及验证码有效期
     * @throws BizException 发送过于频繁时抛出 {@link ErrorCode#SMS_SEND_TOO_FREQUENT}
     */
    public SmsSendResult sendLoginCode(String mobile) {
        MobileUtils.validate(mobile);
        String mobileHash = MobileUtils.hash(mobile);

        // 冷却期检查：同一手机号在 sendCooldownSeconds 内不可重复发送
        String cooldownKey = RedisKeys.smsCooldownKey(mobileHash);
        if (Boolean.TRUE.equals(redisTemplate.hasKey(cooldownKey))) {
            throw new BizException(ErrorCode.SMS_SEND_TOO_FREQUENT);
        }

        String code = generateCode();
        String codeKey = RedisKeys.smsCodeKey(mobileHash);

        // 先写 Redis 再发短信：即使短信发送失败，验证码也已生成（可由上层决定是否回滚）
        redisTemplate.opsForValue().set(codeKey, code, smsProperties.getCodeTtlSeconds(), TimeUnit.SECONDS);
        redisTemplate.opsForValue().set(cooldownKey, "1", smsProperties.getSendCooldownSeconds(), TimeUnit.SECONDS);
        smsSender.sendLoginCode(mobile, code);

        return new SmsSendResult(smsProperties.isMockEnabled(), smsProperties.getCodeTtlSeconds());
    }

    /**
     * 校验用户提交的验证码是否与 Redis 中一致。
     * <p>校验成功后立即删除 Redis 中的验证码，防止同一验证码被重复使用。</p>
     *
     * @param mobile 手机号
     * @param code   用户输入的验证码
     * @throws BizException 验证码为空、不存在或不匹配时抛出 {@link ErrorCode#SMS_CODE_INVALID}
     */
    public void verifyLoginCode(String mobile, String code) {
        MobileUtils.validate(mobile);
        if (!StringUtils.hasText(code)) {
            throw new BizException(ErrorCode.SMS_CODE_INVALID);
        }

        String mobileHash = MobileUtils.hash(mobile);
        String codeKey = RedisKeys.smsCodeKey(mobileHash);
        String attemptKey = RedisKeys.smsVerifyAttemptKey(mobileHash);

        int maxAttempts = Math.max(1, smsProperties.getMaxVerifyAttempts());
        String attemptValue = redisTemplate.opsForValue().get(attemptKey);
        int failedAttempts = 0;
        if (StringUtils.hasText(attemptValue)) {
            try {
                failedAttempts = Integer.parseInt(attemptValue);
            } catch (NumberFormatException ignored) {
                failedAttempts = 0;
            }
        }
        if (failedAttempts >= maxAttempts) {
            redisTemplate.delete(codeKey);
            redisTemplate.delete(attemptKey);
            throw new BizException(ErrorCode.SMS_CODE_INVALID);
        }

        String cachedCode = redisTemplate.opsForValue().get(codeKey);
        if (!StringUtils.hasText(cachedCode) || !cachedCode.equals(code.trim())) {
            redisTemplate.opsForValue().set(
                    attemptKey,
                    String.valueOf(failedAttempts + 1),
                    smsProperties.getCodeTtlSeconds(),
                    TimeUnit.SECONDS);
            throw new BizException(ErrorCode.SMS_CODE_INVALID);
        }
        redisTemplate.delete(codeKey);
        redisTemplate.delete(attemptKey);
    }

    /**
     * 生成验证码。
     * <ul>
     *   <li>Mock 模式：返回配置的固定验证码（默认 123456），便于本地/测试环境调试</li>
     *   <li>生产模式：随机生成指定位数的纯数字验证码</li>
     * </ul>
     */
    private String generateCode() {
        if (smsProperties.isMockEnabled()) {
            return smsProperties.getMockCode();
        }
        int length = Math.max(4, smsProperties.getCodeLength());
        StringBuilder builder = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            builder.append(RANDOM.nextInt(10));
        }
        return builder.toString();
    }

    /**
     * 发送验证码的结果摘要，返回给 Controller/前端。
     */
    public static class SmsSendResult {
        /** 是否处于 Mock 模式（前端可据此提示用户使用固定验证码） */
        private final boolean mockEnabled;
        /** 验证码有效时长（秒） */
        private final int expireSeconds;

        public SmsSendResult(boolean mockEnabled, int expireSeconds) {
            this.mockEnabled = mockEnabled;
            this.expireSeconds = expireSeconds;
        }

        public boolean isMockEnabled() {
            return mockEnabled;
        }

        public int getExpireSeconds() {
            return expireSeconds;
        }
    }
}
