package com.clx.performance.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class RedisUtil {
    public static final String NAMESPACE_SEPARATOR = "clx_order::";
    private final ThreadLocal<String> lockSeqThreadLocal = new ThreadLocal<>();
    private final static String COMPARE_AND_DELETE =
            "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) " + "else return 0 end";


    @Autowired
    private StringRedisTemplate redisTemplate;

    private ObjectMapper objectMapper;

    public void setValue(String key, Object value) throws Exception {
        objectMapper = new ObjectMapper();
        this.redisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(value));
    }

    public void setValue(String key, Object value, Long timeout, TimeUnit timeUnit) throws JsonProcessingException {
        objectMapper = new ObjectMapper();
        this.redisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(value), timeout, timeUnit);
    }

    public boolean setIfAbsent(String key, Object value, Long timeout, TimeUnit timeUnit) throws JsonProcessingException {
        boolean result = this.redisTemplate.opsForValue().setIfAbsent(key, objectMapper.writeValueAsString(value));
        if (result) this.redisTemplate.expire(key, timeout, timeUnit);
        return result;
    }


    public <T> T getValue(String key, Class<T> tClass) throws IOException {
        String value = getValue(key);
        if (value == null) {
            return null;
        }
        objectMapper = new ObjectMapper();
        return objectMapper.readValue(value, tClass);
    }

    public <T> T getValue(String key, TypeReference typeReference) throws IOException {
        String value = getValue(key);
        if (value == null) {
            return null;
        }
        objectMapper = new ObjectMapper();
        return (T) objectMapper.readValue(value, typeReference);
    }

    private String getValue(String key) {
        return this.redisTemplate.opsForValue().get(key);
    }



    public boolean delete(String key) {
        this.redisTemplate.delete(key);
        return true;
    }

    public boolean lock(String key, String lockSeqId) {
        return setAtomicLockKey(key, lockSeqId);
    }

    public boolean lock(String key, String lockSeqId, int second) {
        return setAtomicLockKey(key, lockSeqId, second);
    }

    private void setLockSeqId(String lockSeqId) {
        lockSeqThreadLocal.set(lockSeqId);
    }

    private String getLockSeqId() {
        return lockSeqThreadLocal.get();
    }

    public Boolean unlock(String key, String uuid) {
        RedisConnection redisConnection = redisTemplate.getConnectionFactory().getConnection();
        return redisConnection.eval(COMPARE_AND_DELETE.getBytes(), ReturnType.BOOLEAN, 1, key.getBytes(), uuid.getBytes());
    }

    private Boolean setAtomicLockKey(String lockKey, String uuid) {
        RedisConnection redisConnection = redisTemplate.getConnectionFactory().getConnection();
        return redisConnection.set(lockKey.getBytes(),
                uuid.getBytes(),
                Expiration.seconds(60),
                RedisStringCommands.SetOption.SET_IF_ABSENT);
    }

    private Boolean setAtomicLockKey(String lockKey, String uuid, int second) {
        RedisConnection redisConnection = redisTemplate.getConnectionFactory().getConnection();
        return redisConnection.set(lockKey.getBytes(),
                uuid.getBytes(),
                Expiration.seconds(second),
                RedisStringCommands.SetOption.SET_IF_ABSENT);
    }


    /**
     * @return
     * @Author kavin
     * @Description 获取某个key 的过期时间
     * @Param [key]
     **/
    public long getExpireTime(String key) {
        return redisTemplate.opsForValue().getOperations().getExpire(key);
    }

    /**
     * 多服务器集群，使用下面的方法，代替System.currentTimeMillis()，获取redis时间，避免多服务的时间不一致问题！！！
     * @return
     */
    public long currtTimeForRedis(){
        return redisTemplate.execute((RedisCallback<Long>) redisConnection -> redisConnection.time());
    }

    /**
     * @Author kavin
     * @Description key是否存在
     * @Param [key]
     * @return
     **/
    public boolean exists(String key){
        return redisTemplate.hasKey(key);
    }

    /**
     * @Author kavin
     * @Description 原子性自增
     * @Param [key]
     * @return
     **/
    public Long incr(String key){
        return redisTemplate.opsForValue().increment(key);
    }


}
