package com.ssm.util;

import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class RedisClient<V> {

//    private final long DEFAULT_EXPIRE_TIME = 1 * 24 * 60 * 60L;

    public final static long EXPIRE_TIME_ONE_HOUR = 60 * 60L;

    public final static long EXPIRE_TIME_ONE_MINUTE = 60L;

    public final static long EXPIRE_TIME_FIVE_MINUTES = 5 * 60L;

    public final static long EXPIRE_TIME_TEN_MINUTES = 10 * 60L;

    public final static long EXPIRE_TIME_TEN_MINUTESTOW = 3 * 60L;

    public static final long EXPIRE_TIME_HALF_HOUR = 30 * 60L;

    public static final long EXPIRE_TIME_ONE_DAY =  24 * 60 * 60L;

    public static final long EXPIRE_DAY_STATISTICS = 3 * 24 * 60 * 60L;

    public static final long EXPIRE_MONTHLY_STATISTICS = 33 * 24 *60 *60L;
    public static final String LOCK_RELEASE_SCRIPT = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";

    @Resource
    RedisTemplate<String,V> redisTemplate;

    public static final int LOCK_EXPIRE_TIME = 10; // 锁的过期时间，单位：秒
    public static final String LOCKED = "locked";   // 分布式锁时, 设置的值

    public RedisTemplate<String,V> getRedisTemplate(){
        return redisTemplate;
    }

    /**
     * 通过 SETNX 实现分布式锁
     * @param lockKey 当 {@code lockKey} 不存在时, 上锁并返回 {@code true} (通过设置 {@code lockKey} 的值为 "locked" 来上锁);
     *                当 {@code lockKey} 存在时, 不设置锁, 并且返回值为 {@code false}.
     */
    public boolean tryAcquireLock(String lockKey) {
        RedisTemplate<String, String> redisTemplate = (RedisTemplate<String, String>) this.redisTemplate;
        ValueOperations<String, String> stringStringValueOperations = redisTemplate.opsForValue();
        // 尝试获取锁，使用 SETNX 语义
        return Boolean.TRUE.equals(stringStringValueOperations.setIfAbsent(lockKey, LOCKED, Duration.ofSeconds(LOCK_EXPIRE_TIME)));
    }

    public void delete(String key){
        log.warn("delete cache ,keys in ({})",key);
        redisTemplate.delete(key);
    }

    public void delete(Collection<String> keys){
        log.warn("delete cache ,keys in ({})", StringUtils.join(keys.toArray(),","));
        redisTemplate.delete(keys);
    }

    public long incr(String k,long l){
        return redisTemplate.opsForValue().increment(k,l);
    }

    public long incr(String k){
        return this.incr(k,1L);
    }

    public Long decr(String k,long l){
        return this.incr(k,-l);
    }

    public long decr(String k){
        return this.incr(k, -1L);
    }

    public double incrD(String k,double d){
        return redisTemplate.opsForValue().increment(k,d);
    }

    public double incrD(String k){
        return this.incrD(k, 1D);
    }

    public double decrD(String k,double d){
        return this.incrD(k,-d);
    }

    public double decrD(String k){
        return this.incrD(k,-1D);
    }


    public void set(String k,V v,Long l){
        redisTemplate.opsForValue().set(k,v,l,TimeUnit.SECONDS);
    }

    public void set(String k,V v){
        redisTemplate.opsForValue().set(k,v);
    }

    public void set(String k,V v,Long l,TimeUnit timeUnit){
        redisTemplate.opsForValue().set(k,v,l,timeUnit);
    }

    public void waitingSet(String k, V v, Long time, TimeUnit timeUnit) {
         redisTemplate.execute(new SessionCallback<Boolean>() {
             @Override
             public <K, V> Boolean execute(RedisOperations<K, V> operations) throws DataAccessException {
                 operations.multi();
                 operations.opsForValue().set((K) k, (V) v);
                 operations.exec();
                 return true;
             }
         });
    }

    public void waitingSetValueWithExpirationAt(String key, V value, Date expiration) {
        redisTemplate.execute(new SessionCallback<Boolean>() {
            @Override
            public <K, V> Boolean execute(RedisOperations<K, V> operations) throws DataAccessException {
                operations.multi();
                operations.opsForValue().set((K) key, (V) value);
                operations.expireAt((K) key, expiration);
                operations.exec();
                return true;
            }
        });
    }

    public V get(String k){
        return redisTemplate.opsForValue().get(k);
    }

    public void expire(String k,int seconds){
        redisTemplate.expire(k, seconds, TimeUnit.SECONDS);
    }

    public void expire(String k,long seconds){
        redisTemplate.expire(k, seconds, TimeUnit.SECONDS);
    }

    public List<V> pipeLine(Map<String,V> map){
           List<Object> objects = redisTemplate.execute((RedisConnection redisConnection) -> {
               RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
               Gson gson = new Gson();
               redisConnection.openPipeline();
               for(String key:map.keySet()){
                   redisConnection.lPush(serializer.serialize(key),serializer.serialize(gson.toJson(map.get(key))));
               }
               return redisConnection.closePipeline();
           });
        return (List<V>) objects;
    }


    public boolean hasKey(String key){
        return redisTemplate.hasKey(key);
    }

    public boolean setnx(String key,V v){
        return redisTemplate.opsForValue().setIfAbsent(key,v);
    }


    public Long sAdd(String key, V... v){
        return redisTemplate.opsForSet().add(key,v);
    }

    public Long sRemove(String key, V... v){
        return redisTemplate.opsForSet().remove(key,v);
    }

    public Long leftPush(String key,V v){
        return redisTemplate.opsForList().leftPush(key,v);
    }
    public Long rightPush(String key,V v){
        return redisTemplate.opsForList().rightPush(key, v);
    }

    /*
    *  从列表头部开始删除值等于value的元素count次
    *  LREM key count value
    *  count>0:从表头开始向表尾搜索，移除与value相等的元素，数量为count
    *  count<0:从表尾开始向表头搜索，移除与value相等的元素，数量为count的决定值
    *  count=0:移除表中所有value相等的值
    * */
    public  Long lRem(String key, Long count, V v){return redisTemplate.opsForList().remove(key,count,v);}

    public Long lSize(String key){
        return redisTemplate.opsForList().size(key);
    }
    public List<V> lRange(String key,Long start,Long end){
        return redisTemplate.opsForList().range(key, start, end);
    }
    public V lIndex(String key,Long index){
        return redisTemplate.opsForList().index(key,index);
    }


    public  void lSet(String key,Long index, V v){
         redisTemplate.opsForList().set(key,index, v);
    }
    public V right(String key){
        return redisTemplate.opsForList().rightPop(key);
    }

    public Set<V> sMembers(String key){
        return redisTemplate.opsForSet().members(key);
    }

    public V sPop(String key){
        return redisTemplate.opsForSet().pop(key);
    }

    public Set<V> sRandMember(String key, long count) {
        return redisTemplate.opsForSet().distinctRandomMembers(key, count);
    }

    public Long sSize(String key){
        return redisTemplate.opsForSet().size(key);
    }


    public boolean isMembers(String key,V v){
        return redisTemplate.opsForSet().isMember(key,v);
    }




    public V getset(String key,V v){
        return redisTemplate.opsForValue().getAndSet(key,v);
    }


    public Set<String> keys(String pattern){
        return redisTemplate.keys(pattern);
    }


    public Long evalUnlock(String script, List<String> keys, Object...args) {
        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        return redisTemplate.execute(redisScript, keys, args);
    }
}
