package com.bonus.core.common.redis;

import cn.hutool.core.collection.CollUtil;
import com.bonus.common.core.exception.ServiceException;
import com.bonus.core.common.utils.SpringContextHolder;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.support.atomic.RedisAtomicInteger;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

public class RedisUtil {
   private static final Logger log = LoggerFactory.getLogger(RedisUtil.class);

   public static void setString(String key, String value) {
      setString(key, value, (Long)null);
   }

   public static void setString(String key, String value, Long timeOut) {
      timeOut = timeoutFilter(timeOut);
      if (value != null) {
         if (timeOut != null) {
            stringRedisTemplate().opsForValue().set(key, value, timeOut, TimeUnit.SECONDS);
         } else {
            stringRedisTemplate().opsForValue().set(key, value);
         }
      }

   }

   public static Boolean setIfPresentString(String key, String value, Long timeOut) {
      timeOut = timeoutFilter(timeOut);
      if (value != null) {
         return timeOut != null ? stringRedisTemplate().opsForValue().setIfPresent(key, value, timeOut, TimeUnit.SECONDS) : stringRedisTemplate().opsForValue().setIfPresent(key, value);
      } else {
         return false;
      }
   }

   public static boolean setNx(String key, Object value, int expireTime) {
      Boolean ifSucc = redisTemplate().opsForValue().setIfAbsent(key, value, (long)expireTime, TimeUnit.SECONDS);
      return ifSucc == null ? false : ifSucc;
   }

   public static String getString(String key) {
      return (String)stringRedisTemplate().opsForValue().get(key);
   }

   public static void setObj(String key, Object value) {
      setObj(key, value, (Long)null);
   }

   public static void setObj(String key, Object value, Long timeOut) {
      timeOut = timeoutFilter(timeOut);
      if (value != null) {
         if (timeOut != null) {
            redisTemplate().opsForValue().set(key, value, timeOut, TimeUnit.SECONDS);
         } else {
            redisTemplate().opsForValue().set(key, value);
         }
      }

   }

   public static Object getObj(String key) {
      return redisTemplate().opsForValue().get(key);
   }

   public static void delete(String key) {
      redisTemplate().delete(key);
   }

   public static void deleteByPattern(String keyPattern) {
      Set<Object> keys = redisTemplate().keys(keyPattern);
      if (CollUtil.isNotEmpty(keys)) {
         redisTemplate().delete(keys);
      }

   }


   private static Long timeoutFilter(Long timeout) {
      Long maxExpSecond = (Long)environment().getProperty("spring.redis.custom-max-expiration-second", Long.class);
      if (maxExpSecond != null && (timeout == null || timeout > maxExpSecond)) {
         timeout = maxExpSecond;
      }

      return timeout;
   }

   public static List<String> keysByPattern(String pattern) {
      Set<Object> keys = redisTemplate().keys(pattern);
      return (List)(CollUtil.isEmpty(keys) ? new ArrayList() : (List)keys.stream().map(Object::toString).collect(Collectors.toList()));
   }

   public static void multiSet(Map map) {
      redisTemplate().opsForValue().multiSet(map);
   }

   public static List multiGet(List keys) {
      return redisTemplate().opsForValue().multiGet(keys);
   }

   public static void zAdd(String key, Object value, double score) {
      redisTemplate().opsForZSet().add(key, value, score);
   }

   public static Set<ZSetOperations.TypedTuple<Object>> zGetList(String key, long start, long end) {
      return redisTemplate().opsForZSet().rangeWithScores(key, start, end);
   }

   private static StringRedisTemplate stringRedisTemplate() {
      return (StringRedisTemplate) SpringContextHolder.getBean(StringRedisTemplate.class);
   }

   private static RedisTemplate<Object, Object> redisTemplate() {
      return (RedisTemplate)SpringContextHolder.getBean("redisTemplate");
   }



   private static Environment environment() {
      return (Environment)SpringContextHolder.getBean(Environment.class);
   }

   public static Integer incr(String key, Long liveTime) {
      return getRedisAtomicInteger(key, liveTime, true);
   }
   public static Integer getRedisAtomicInteger(String key, Long liveTime, Boolean incrDecrFlag) {
      RedisConnectionFactory connectionFactory = redisTemplate().getConnectionFactory();
      if (connectionFactory == null) {
         throw new ServiceException("Redis连接异常");
      } else {
         RedisAtomicInteger entityIdCounter = new RedisAtomicInteger(key, connectionFactory);
         if (incrDecrFlag == null) {
            return entityIdCounter.get();
         } else {
            int increment;
            if (incrDecrFlag) {
               increment = entityIdCounter.getAndIncrement();
               if (liveTime > 0L) {
                  entityIdCounter.expire(liveTime, TimeUnit.SECONDS);
               }

               return increment + 1;
            } else {
               increment = entityIdCounter.getAndDecrement();
               if (liveTime > 0L) {
                  entityIdCounter.expire(liveTime, TimeUnit.SECONDS);
               }

               return increment - 1;
            }
         }
      }
   }

   public static RLock getLock(String key) {
      return redissonClient().getLock(key);
   }

   private static RedissonClient redissonClient() {
      return (RedissonClient)SpringContextHolder.getBean(RedissonClient.class);
   }

   @Deprecated
   public static boolean tryLock(String key) {
      return tryLock(key, (Integer)null, (Integer)null);
   }

   /** @deprecated */
   @Deprecated
   public static boolean tryLock(String key, Integer waitSecond) {
      return tryLock(key, waitSecond, (Integer)null);
   }

   /** @deprecated */
   @Deprecated
   public static boolean tryLock(String key, Integer waitSecond, Integer expireSecond) {
      if (waitSecond == null && expireSecond != null) {
         throw new ServiceException("不能这样使用");
      } else {
         RLock lock = redissonClient().getLock(key);

         try {
            if (waitSecond != null && expireSecond != null) {
               return lock.tryLock((long)waitSecond, (long)expireSecond, TimeUnit.SECONDS);
            } else {
               return waitSecond != null ? lock.tryLock((long)waitSecond, TimeUnit.SECONDS) : lock.tryLock();
            }
         } catch (InterruptedException var5) {
            log.error("【分布式锁】tryLock失败：{}", var5.getMessage());
            return false;
         }
      }
   }
   @Deprecated
   public static void safeUnLock(String key) {
      RLock lock = redissonClient().getLock(key);
      if (lock.isHeldByCurrentThread() && lock.isLocked()) {
         lock.unlock();
      }

   }

   public static Long getIncr(RedisTemplate<Object, Object> redisTemplate, String key) {
      RedisAtomicLong counter = new RedisAtomicLong(key, (RedisConnectionFactory) Objects.requireNonNull(redisTemplate.getConnectionFactory()));
      long increment = counter.getAndIncrement();
      if (increment <= 0L) {
         setIncr(redisTemplate, key, 2);
         return 1L;
      } else {
         LocalDateTime now = LocalDateTime.now();
         LocalDateTime max = LocalDate.now().atTime(LocalTime.MAX);
         long millis = Duration.between(now, max).toMillis();
         counter.expire(millis, TimeUnit.MILLISECONDS);
         return increment;
      }
   }

   public static void setIncr(RedisTemplate<Object, Object> redisTemplate, String key, int value) {
      RedisAtomicLong counter = new RedisAtomicLong(key, (RedisConnectionFactory)Objects.requireNonNull(redisTemplate.getConnectionFactory()));
      counter.set((long)value);
      long millis = Duration.between(LocalDateTime.now(), LocalDate.now().atTime(LocalTime.MAX)).toMillis();
      counter.expire(millis, TimeUnit.MILLISECONDS);
   }

}
