package com.ruoyi.framework.service;

import cn.hutool.captcha.*;
import cn.hutool.captcha.generator.MathGenerator;
import cn.hutool.core.math.Calculator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.constants.CacheConstant;
import com.ruoyi.common.utils.RedisUtil;
import com.ruoyi.common.web.exception.ServiceException;
import com.ruoyi.framework.config.CaptchaConfig;
import com.ruoyi.system.service.ISysConfigService;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.handle.Result;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * 验证码处理
 * @author ruoyi
 */
@Component
public class CaptchaService {

    // 图形验证码宽高
    private static final int CAPTCHA_WIDTH = 160;
    private static final int CAPTCHA_HEIGHT = 60;

    @Inject
    CaptchaConfig captchaConfig;

    @Inject
    RedisUtil redisUtil;

    @Inject
    ISysConfigService sysConfigService;

    /**
     * 获取验证码
     */
    public Result<Map<String, Object>> getCaptcha() {
        Map<String, Object> map = new HashMap<>();
        boolean captchaEnabled = captchaConfig.getEnabled();
        map.put("captchaEnabled", captchaEnabled);
        // 是否开启注册用户
        String registerUser = sysConfigService.selectConfigByKey("sys.account.registerUser");
        map.put("register", "true".equals(registerUser));
        if (captchaEnabled) {
            // 如果开启了验证码，生成验证码
            String invadeType = captchaConfig.getInvadeType();
            // 干扰类型
            if ("circle".equals(invadeType)) {
                // 圆圈干扰验证码（宽、高、验证码字符数、干扰元素个数）
                CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(CAPTCHA_WIDTH, CAPTCHA_HEIGHT, 4, 10);
                createCaptcha(circleCaptcha, map);
            } else if("shear".equals(invadeType)) {
                // 扭曲干扰验证码（宽、高、验证码字符数、干扰线宽度）
                ShearCaptcha shearCaptcha = CaptchaUtil.createShearCaptcha(CAPTCHA_WIDTH, CAPTCHA_HEIGHT, 4, 5);
                createCaptcha(shearCaptcha, map);
            } else {
                // 线段干扰验证码（宽、高、验证码字符数、干扰元素个数）
                LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(CAPTCHA_WIDTH, CAPTCHA_HEIGHT, 4, 15);
                createCaptcha(lineCaptcha, map);
            }
        }
        return Result.succeed(map);
    }

    /**
     * 校验登录时填写的验证码是否正确
     * @param code  前端登陆时传过来的验证码
     * @param uuid  获取验证码后返回的随机id
     */
    public void checkCapcha(String code, String uuid) {
        // 如果开启了验证码
        if(captchaConfig.getEnabled()) {
            if(!StrUtil.isAllNotEmpty(code, uuid)) {
                throw new ServiceException("验证码必填");
            }
            String codeKey = CacheConstant.CAPTCHA_CODE_KEY + uuid;
            // 校验
            String cacheCode = redisUtil.getCacheObject(codeKey);
            if(StrUtil.isEmpty(cacheCode)) {
                throw new ServiceException("验证码已失效");
            }
            redisUtil.deleteCacheObject(codeKey);
            if(!StrUtil.equalsIgnoreCase(code, cacheCode)) {
                throw new ServiceException("验证码错误");
            }
        }
    }

    /**
     * 生成验证码
     * @param t  继承自 AbstractCaptcha 抽象类的验证码类
     * @param map  将验证码写入的返回结果
     * @param <T>
     */
    private <T extends AbstractCaptcha> void createCaptcha(T t, Map<String, Object> map) {
        String uuid = IdUtil.simpleUUID();
        String codeKey = CacheConstant.CAPTCHA_CODE_KEY + uuid;
        // 验证码类型
        String type = captchaConfig.getType();
        // 验证码的内容（如果是字符串类型，则验证码的内容就是结果）
        String code = t.getCode();
        // 如果是四则运算
        if("math".equals(type)) {
            t.setGenerator(new MathGenerator(1));
            // 重新生成code
            t.createCode();
            // 运算结果
            int result = (int) Calculator.conversion(t.getCode());
            code = String.valueOf(result);
        }
        String img = t.getImageBase64();

        // 将结果放入缓存
        redisUtil.setCacheObject(codeKey, code, Duration.ofMinutes(CacheConstant.CAPTCHA_CODE_EXPIRATION));

        map.put("uuid", uuid);
        map.put("img", img);
    }

}
