package org.jeecg.modules.shop.captcha.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.shop.captcha.entity.SendSmsCaptchaDTO;
import org.jeecg.modules.shop.captcha.entity.VerifySmsCaptchaDTO;
import org.jeecg.modules.shop.captcha.enums.CaptchaKeyEnum;
import org.jeecg.modules.shop.captcha.enums.CaptchaTypeEnum;
import org.jeecg.modules.shop.captcha.service.ICaptchaService;
import org.jeecg.modules.shop.captcha.util.EmailUtil;
import org.jeecg.modules.shop.captcha.util.SmsUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@RequiredArgsConstructor
public class CaptchaServiceImpl implements ICaptchaService {
    

    private final RedisTemplate<String, String> redisTemplate;
    
    private final SmsUtil smsUtil;

    private final EmailUtil emailUtil;


    
    @Override
    public void sendSmsCaptcha(SendSmsCaptchaDTO dto) {
        // ************************************************
        // * 流控和限额都在阿里云那边配置了，这边不再重复校验
        // ************************************************
        Assert.isTrue(CaptchaTypeEnum.isValidType(dto.getType()), "不是有效的验证码类型");
        String captcha = generateCaptcha();
//        if (StrUtil.equalsAny(dto.getType(), CaptchaTypeEnum.SUPPLIER_LOGIN.getType(), CaptchaTypeEnum.SUPPLIER_FORGOT_PASSWORD.getType())) {
//            Assert.isTrue(customerService.count(new LambdaQueryWrapper<S003Customer>().eq(S003Customer::getEmail, dto.getAccount()))>0, "邮箱未注册");
//        } else if (StrUtil.equals(dto.getType(), CaptchaTypeEnum.SUPPLIER_REGISTER.getType())) {
//            Assert.isFalse(customerService.count(new LambdaQueryWrapper<S003Customer>().eq(S003Customer::getEmail, dto.getAccount()))<=1, "邮箱已注册");
//        }
        Assert.isTrue(tryLock(dto.getAccount()), "流控");
        emailUtil.sendRegisterEmail(dto.getAccount(),captcha);
        String cacheKey = CaptchaKeyEnum.generateEmailCaptchaKey(dto.getType(), dto.getAccount());
        redisTemplate.opsForValue().set(cacheKey, captcha, 10, TimeUnit.MINUTES);
    }

    @Override
    public void sendNotifiedCaptcha(SendSmsCaptchaDTO dto) {
        Assert.isTrue(CaptchaTypeEnum.isValidType(dto.getType()), "不是有效的验证码类型");
        //分为手机号和邮箱的情况
        if (StrUtil.containsAny(dto.getAccount(), "@")) {
            Assert.isTrue(tryLock(dto.getAccount()), "流控");
            emailUtil.sendNotifiedEmail(dto.getAccount(), dto.getName());
        }else {
            try {
                Assert.isTrue(tryLock(dto.getAccount()), "流控");
                smsUtil.sendSmsNotified(dto.getAccount(), dto.getName());
            } catch (Exception e) {
                String errMsg = "验证码发送失败";
                if (StrUtil.contains(e.getMessage(), "流控")) {
                    errMsg += "，触发流控限制";
                }
                throw new RuntimeException(errMsg);
            }
        }
    }

    @Override
    public String verifySmsCaptcha(VerifySmsCaptchaDTO dto) {
        Assert.isTrue(CaptchaTypeEnum.isValidType(dto.getType()), "参数错误");
        int maxAccessCount = 5;
        String captchaKey = "";
        String captcha = "";
        //分手机号和邮件两张情况
        if (StrUtil.containsAny(dto.getAccount(), "@")){
            captchaKey = CaptchaKeyEnum.generateEmailCaptchaKey(dto.getType(), dto.getAccount());
            captcha = getCaptcha(captchaKey, maxAccessCount);
        }else{
            captchaKey = CaptchaKeyEnum.generateSmsCaptchaKey(dto.getType(), dto.getAccount());
            captcha = getCaptcha(captchaKey, maxAccessCount);
        }
        Assert.notBlank(captcha, "验证码错误");
        Assert.isTrue(StrUtil.equals(dto.getCaptcha(), captcha), "验证码错误");
        String token = IdUtil.simpleUUID();
        String tokenKey = CaptchaKeyEnum.generateCaptchaTokenKey(captchaKey);
        redisTemplate.opsForValue().set(tokenKey, token, 10, TimeUnit.MINUTES);
        clearCaptcha(captchaKey);
        return token;
    }

    @Override
    public void verifyToken(String captchaKey, String token) {
        Assert.isTrue(StrUtil.isAllNotEmpty(captchaKey, token), "参数错误");
        String tokenKey = CaptchaKeyEnum.generateCaptchaTokenKey(captchaKey);
        String _token = redisTemplate.opsForValue().get(tokenKey);
        Assert.isTrue(StrUtil.equals(_token, token), "token无效");
        redisTemplate.delete(tokenKey);
    }

    @Override
    public String getCaptcha(String captchaKey, int maxAccessCount) {
        Assert.notEmpty(captchaKey, "captchaKey 不能为空");
        Assert.isTrue(maxAccessCount > 0, "maxAccessCount 必须大于 0");
        String script = "local captchaKey = KEYS[1]\n" +
                "local countKey = KEYS[2]\n" +
                "local captcha = redis.call('GET', captchaKey)\n" +
                "if not captcha then\n" +
                "    return nil\n" +
                "end\n" +
                "local currentCount = tonumber(redis.call('GET', countKey))\n" +
                "if not currentCount or currentCount < tonumber(ARGV[1]) then\n" +
                "    redis.call('INCR', countKey)\n" +
                "    local captchaKeyTTL = redis.call('TTL', captchaKey)\n" +
                "    if captchaKeyTTL < 0 then\n" +
                "        captchaKeyTTL = 600\n" +
                "    end\n" +
                "    redis.call('EXPIRE', countKey, captchaKeyTTL)\n" +
                "    return captcha\n" +
                "else\n" +
                "    redis.call('DEL', captchaKey)\n" +
                "    redis.call('DEL', countKey)\n" +
                "    return nil\n" +
                "end";
        String countKey = CaptchaKeyEnum.generateCaptchaAccessCountKey(captchaKey);
        DefaultRedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
        return redisTemplate.execute(redisScript, Arrays.asList(captchaKey, countKey), String.valueOf(maxAccessCount));
    }

    @Override
    public void clearCaptcha(String captchaKey) {
        String countKey = CaptchaKeyEnum.generateCaptchaAccessCountKey(captchaKey);
        redisTemplate.delete(Arrays.asList(captchaKey, countKey));
    }

    private String generateCaptcha() {
        return String.valueOf(ThreadLocalRandom.current().nextInt(100000, 999999));
    }
    
    private boolean tryLock(String receiver) {
        Boolean isLock = redisTemplate.opsForValue().setIfAbsent(CaptchaKeyEnum.generateCaptchaSendLockKey(receiver), "1", 1, TimeUnit.MINUTES);
        return Boolean.TRUE.equals(isLock);
    }
    
}
