package com.sqx.common.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.UUID;
import java.util.function.Supplier;


/**
 * 接口访问次数 月为单位
 */
@Component
@Slf4j
public class ApiAccessLimitUtil {

    private static final String ACCESS_COUNT_KEY_PREFIX = "sys:limit:";

    private static RedisUtils redisUtils;

    private static final int DEFAULT_ACCESS_COUNT = 5;
    private static final String DATE_TIME_FORMAT = "month";


    @Autowired
    public void setRedisUtils(RedisUtils redisUtils) {
        ApiAccessLimitUtil.redisUtils = redisUtils;
    }

    /**
     * 默认 当月5次
     * @param id         唯一值
     * @param key        接口名称  sys:limit:接口名称
     * @return
     */
    public static boolean isAccessAllowed(String id, String key) {
        String redisKey = generateRedisKey(key, id);
        Object countObj = redisUtils.get(redisKey);
        if (countObj == null) {
            // 根据不同时间周期设置过期时间并初始化访问次数为1
            long expireAt = calculateExpireAt(DATE_TIME_FORMAT);
            redisUtils.set(redisKey, 1, expireAt);
            return true;
        }
        if ((int) countObj < DEFAULT_ACCESS_COUNT) {
            // 访问次数未达上限，次数加1
            redisUtils.incr(redisKey);
            return true;
        }
        return false;
    }


    /**
     * 默认月 month/月/自然月
     * @param id         唯一值
     * @param key        接口名称  sys:limit:接口名称
     * @param count      次数限制
     * @return
     */
    public static boolean isAccessAllowed(String id, String key, Integer count) {
        String redisKey = generateRedisKey(key, id);
        Object countObj = redisUtils.get(redisKey);
        if (countObj == null) {
            // 根据不同时间周期设置过期时间并初始化访问次数为1
            long expireAt = calculateExpireAt(DATE_TIME_FORMAT);
            redisUtils.set(redisKey, 1, expireAt);
            return true;
        }
        if ((int) countObj < count) {
            // 访问次数未达上限，次数加1
            redisUtils.incr(redisKey);
            return true;
        }
        return false;
    }

    /**
     * 默认 5次
     * @param id         唯一值
     * @param key        接口名称  sys:limit:接口名称
     * @param timeFormat  day/天/自然天 week/周/本周日 month/月/自然月 year/年/自然年
     * @return
     */
    public static boolean isAccessAllowed(String id, String key, String timeFormat) {
        String redisKey = generateRedisKey(key, id);
        Object countObj = redisUtils.get(redisKey);
        if (countObj == null) {
            // 根据不同时间周期设置过期时间并初始化访问次数为1
            long expireAt = calculateExpireAt(timeFormat);
            redisUtils.set(redisKey, 1, expireAt);
            return true;
        }
        if ((int) countObj < DEFAULT_ACCESS_COUNT) {
            // 访问次数未达上限，次数加1
            redisUtils.incr(redisKey);
            return true;
        }
        return false;
    }

    /**
     * @param id         唯一值
     * @param key        接口名称  sys:limit:接口名称
     * @param count      次数限制
     * @param timeFormat  day/天/自然天 week/周/本周日 month/月/自然月 year/年/自然年
     * @return
     */
    public static boolean isAccessAllowed(String id, String key, Integer count, String timeFormat) {
        String redisKey = generateRedisKey(key, id);
        Object countObj = redisUtils.get(redisKey);
        if (countObj == null) {
            long expireAt;
            if (StrUtil.isBlank(timeFormat)) {
                expireAt = count;
            }else {
                // 根据不同时间周期设置过期时间并初始化访问次数为1
                expireAt = calculateExpireAt(timeFormat);
            }
            redisUtils.set(redisKey, 1, expireAt);
            return true;
        }
        if (Integer.parseInt(countObj.toString()) < count) {
            // 访问次数未达上限，次数加1
            redisUtils.incr(redisKey);
            return true;
        }
        return false;
    }

    public static void removeKey(String id, String key) {
        String redisKey = generateRedisKey(key, id);
        redisUtils.delete(redisKey);
    }


    public static<T> T runFunAndCheckKey(Supplier<T> supplier, String lockKey, Integer seconds) {
        try{
            // 创建线程id, 用作判断
            String clientId = UUID.randomUUID().toString();
            // 设置分布式锁
            boolean lock = Boolean.TRUE.equals(redisUtils.setIfAbsent(lockKey, clientId, seconds));
            int count = 0;
            while (!lock) {
                if (count++ > 100) {
                    throw new RuntimeException("系统繁忙, 稍后再试");
                }
                Thread.sleep(20);
                lock = Boolean.TRUE.equals(redisUtils.setIfAbsent(lockKey, clientId, seconds));
            }
            return supplier.get();
        } catch (RuntimeException e){
            log.error("执行出错", e);
            throw e;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally{
            redisUtils.delete(lockKey);
        }
    }

    public static boolean isAccessAllowed(String id, String key, Integer count, Integer seconds) {
        String redisKey = generateRedisKey(key, id);
        Object countObj = redisUtils.get(redisKey);
        if (countObj == null) {
            // 根据不同时间周期设置过期时间并初始化访问次数为1
            redisUtils.set(redisKey, 1, seconds);
            return true;
        }
        if (Integer.parseInt(countObj.toString()) < count) {
            // 访问次数未达上限，次数加1
            redisUtils.incr(redisKey);
            return true;
        }
        return false;
    }


    private static String generateRedisKey(String key, String id) {
        return ACCESS_COUNT_KEY_PREFIX + key + ":" + id;
    }

    private static long calculateExpireAt(String timePeriod) {
        Date now = DateUtil.beginOfDay(DateUtil.date());
        Date expireDate = null;
        if ("day".equals(timePeriod)) {
            expireDate =  DateUtil.endOfDay(now);
        } else if ("week".equals(timePeriod)) {
            expireDate = DateUtil.endOfWeek(now);
        } else if ("month".equals(timePeriod)) {
            expireDate = DateUtil.endOfMonth(now);
        } else if ("year".equals(timePeriod)) {
            expireDate = DateUtil.endOfYear(now);
        }
        long endTimeStamp = DateUtil.endOfDay(expireDate).getTime() / 1000L;
        long currentTimeStamp = DateUtil.currentSeconds();
        return endTimeStamp - currentTimeStamp;
    }
}
