package com.yn.bftl.thirdparty.common.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.text.DecimalFormat;
import java.util.concurrent.TimeUnit;

/**
 * redis工具类
 *
 * @author huabiao
 * @create 2021/5/29  9:26
 **/
@Component
@Slf4j
public class RedisUtil {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    private static String QUOTATION_INIT = "000000";

    /**
     * 锁redis key模板
     */
    private static String REDIS_LOCK_KEY_TEMPLATE = "_redis_:_lock_:%s";

    /**
     * 单据锁默认超时时间（秒）
     */
    private static Integer REDIS_LOCK_DEFAULT_EXPIRE_TIME = 86400;

    /**
     * 用于生成唯一的锁ID的redis key
     */
    private static String REDIS_LOCK_UNIQUE_ID_KEY = "lock_unique_id";

    /**
     * 百度ai access_token key
     */
    private static final String BAIDUAI_ACCESS_TOKEN_KEY = "BAIDUAI:ACCESS_TOKEN";

    /**
     * 获取百度ai access_token
     *
     * @return
     */
    public String getBaiduaiAccessToken() {
        ValueOperations<String, String> accessTokenOpt = stringRedisTemplate.opsForValue();
        return accessTokenOpt.get(BAIDUAI_ACCESS_TOKEN_KEY);
    }

    /**
     * 缓存百度ai access_token
     *
     * @param accessToken
     * @param timeout 有效时长 单位秒
     * @return
     */
    public void cacheBaiduaiAccessToken(String accessToken, Integer timeout) {
        stringRedisTemplate.opsForValue().set(BAIDUAI_ACCESS_TOKEN_KEY, accessToken, timeout, TimeUnit.SECONDS);
    }

    /**
     * 加锁
     * @param key
     * @param value 当前时间 + 超时时间
     * @return
     */
    public boolean lock(String key, String value) {
        if (stringRedisTemplate.opsForValue().setIfAbsent(key, value)) {
            return true;
        }
        // 避免死锁，且只让一个线程拿到锁
        String currentValue = stringRedisTemplate.opsForValue().get(key);
        // 如果锁过期了
        if (StringUtils.isNotBlank(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
            //获取上一个锁的时间
            String oldValues = stringRedisTemplate.opsForValue().getAndSet(key, value);
            if (StringUtils.isNotBlank(oldValues) && oldValues.equals(currentValue)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 解锁
     * @param key
     * @param value
     */
    public void unlock(String key, String value) {
        try {
            String currentValue = stringRedisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(currentValue) && currentValue.equals(value)) {
                stringRedisTemplate.opsForValue().getOperations().delete(key);
            }
        } catch (Exception e) {
            log.error("redis分布式锁解锁异常，{}", e);
        }
    }

    public String incr(String key, long liveTime) {
        // 基于这个key创建一个redis的值，默认的value为0
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, stringRedisTemplate.getConnectionFactory());
        // 对于这个key进行自增1的操作 然后返回自增1 之前的数据
        Long increment = entityIdCounter.getAndIncrement();

        // 如果为0  则是第一次创建这个可以，则设置这个key的过期时间为1天
        if ((null == increment || increment.longValue() == 0) & liveTime > 0) {//初始设置过期时间
            entityIdCounter.expire(liveTime, TimeUnit.DAYS);    //设置自增值过期时间，liveTime 过期时间；TimeUnit.DAYS 过期时间单位，我这边设置为天
        }
        //位数不够，前面补0
        DecimalFormat decimalFormat = new DecimalFormat(QUOTATION_INIT);
        return decimalFormat.format(increment);
    }

    /**
     * 加单据锁
     * @param intId 锁ID
     * @param intExpireTime 锁过期时间（秒）
     * @return bool|int 加锁成功返回唯一锁ID，加锁失败返回0
     */
    public Integer addLock(String intId, Long intExpireTime)
    {
        //参数校验
        if (intId == null || intExpireTime <= 0) {
            return 0;
        }

        //生成唯一锁ID，解锁需持有此ID
        Long intUniqueLockId =  generateUniqueLockId();

        //根据模板，结合单据ID，生成唯一Redis key（一般来说，单据ID在业务中系统中唯一的）
        String strKey = String.format(REDIS_LOCK_KEY_TEMPLATE, intId);

        //加锁（通过Redis setnx指令实现，从Redis 2.6.12开始，通过set指令可选参数也可以实现setnx，同时可原子化地设置超时时间）
        Boolean bolRes = stringRedisTemplate.opsForValue().setIfAbsent(strKey, String.valueOf(intUniqueLockId), intExpireTime, TimeUnit.SECONDS);

        //加锁成功返回锁ID，加锁失败返回false
        return Boolean.TRUE.equals(bolRes) ? intUniqueLockId.intValue() : 0;
    }

    /**
     * 解单据锁
     * @param intId 锁ID
     * @param intLockId 锁唯一ID
     * @return bool
     */
    public Boolean releaseLock(String intId, Integer intLockId)
    {
        //参数校验
        if (intId == null || intLockId <= 0) {
            return false;
        }

        //生成Redis key
        String strKey = String.format(REDIS_LOCK_KEY_TEMPLATE, intId);

        //监听Redis key防止在【比对lock id】与【解锁事务执行过程中】被修改或删除，提交事务后会自动取消监控，其他情况需手动解除监控
        stringRedisTemplate.watch(strKey);
        if (intLockId.toString().equals(stringRedisTemplate.opsForValue().get(strKey))) {
            stringRedisTemplate.delete(strKey);
            return true;
        }
        stringRedisTemplate.unwatch();
        return false;
    }

    /**
     * 生成锁唯一ID（通过Redis incr指令实现简易版本，可结合日期、时间戳、取余、字符串填充、随机数等函数，生成指定位数唯一ID）
     * @return mixed
     */
    private Long generateUniqueLockId()
    {
        return stringRedisTemplate.opsForValue().increment(REDIS_LOCK_UNIQUE_ID_KEY);
    }

    /**
     * 获取契约锁配置更新属性
     * 设计器未配置redis，暂时搁置
     *
     * @author chendehuihuo
     * @date 2022-08-26 11:22
     * @return Boolean
     */
    public Boolean getRenewalContractLock() {
        String isRenewalContractLock = stringRedisTemplate.opsForValue().get("table:thirdPartyPaySet:renewalContractLock");
        if ("true".equals(isRenewalContractLock)) {
            return true;
        }
        return false;
    }
}
