package com.brilliance.isc.automatic.service.impl;

import com.brilliance.isc.automatic.annotation.FlowLimit;
import com.brilliance.isc.automatic.mapper.SemphoreMapper;
import com.brilliance.isc.bo.auto.AutoFlow;
import com.brilliance.isc.mda.dao.AutoFlowMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

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

/**
 * redis信号量切面限流
 */
@Slf4j
@Aspect
@Component
public class SemaphoreServiceImpl {
    private static final String SEMAPHORE_PREFIX = "SEMAPHORE_";
    private static final String SEMAPHORE_LOCK_PREFIX = "SEMAPHORE_LOCK_";
    private static final String SEMAPHORE_NUM_PREFIX = "SEMAPHORE_NUM_";
    private static final String SEMAPHORE_CUR_NUM_PREFIX = "SEMAPHORE_CUR_NUM_";
    private static final long EXPIRETIMES = 360l;
    private static final TimeUnit EXPIRETYPE = TimeUnit.SECONDS;
    private static final Long LEASE_TIME = 30L;
    private static final Long ACQUIRE_TIME = 3L;
    private static final Object LOCK = new Object();
    private static final ExpressionParser expressionParser = new SpelExpressionParser();

    @Resource
    RedissonClient redissonClient;
    @Resource
    SemphoreMapper semphoreMapper;
    @Resource
    private AutoFlowMapper autoFlowMapper;
    @Autowired
    @Qualifier("jacksonRedisTemplate")
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 信号量并发控制
     */
    @Around("@annotation(flowLimit)")
    public Object limitInterface(ProceedingJoinPoint point, FlowLimit flowLimit) throws Throwable {
        RSemaphore semaphore = null;
        String key = null;
        boolean flag = false;
        try {
            key = flowLimit.value();
            if (!StringUtils.hasLength(key) || isNotAuto(point.getArgs())) {
                //添加类注解，但是没指定name属性，直接忽略，不进行限流
                return point.proceed();
            }
            Integer semaphoreNum = getSemaphoreNum(key);
            semaphore = redissonClient.getSemaphore(SEMAPHORE_PREFIX + key);
            updateSemaphore(key, semaphore, semaphoreNum);
            if (semaphore != null) {
                flag = semaphore.tryAcquire(ACQUIRE_TIME, TimeUnit.MINUTES);
                log.info("接口{}获取信号成功标志:{}", key, flag);
            }
            return point.proceed();
        } catch (Throwable e) {
            log.error("对{}接口进行信号量限流异常", key, e);
            throw e;
        } finally {
            if (semaphore != null && flag) {
                semaphore.release();
                log.info("接口{}释放信号", key);
            }
        }
    }

    /**
     * 根据参数获取trninr，判断是否为非自动化业务
     *
     * @param args 可能是SendVo类型对象
     */
    private boolean isNotAuto(Object[] args) {
        if (ObjectUtils.isEmpty(args)) {
            return true;
        }
        try {
            Expression expression = expressionParser.parseExpression("trn.inr");
            String trnInr = expression.getValue(args[0], String.class);
            if (!StringUtils.hasText(trnInr)) {
                return true;
            }
            AutoFlow autoFlow = autoFlowMapper.selectByTrnInr(trnInr);
            if (autoFlow == null) {
                return true;
            }
            if ("1".equals(autoFlow.getSta())) {
                return false;
            }
        } catch (Exception e) {
            //静默处理
        }
        return true;
    }

    /**
     * 初始化或者更新指定接口的信号数(只能有一个pod可以执行)
     */
    private boolean updateSemaphore(String key, RSemaphore semaphore, Integer semaphoreNum) {
        try {
            RLock lock = redissonClient.getLock(SEMAPHORE_LOCK_PREFIX + key);
            boolean locked = lock.tryLock(1, LEASE_TIME, TimeUnit.SECONDS);
            if (!locked) {
                return true;
            }
            if (log.isInfoEnabled()) {
                log.info("接口{}准备调整信号数", key);
            }
            String curKey = SEMAPHORE_CUR_NUM_PREFIX + key;
            Integer oldNum = (Integer) redisTemplate.opsForValue().get(curKey);
            if (oldNum == null || oldNum != semaphoreNum) {
                oldNum = oldNum == null ? 0 : oldNum;
                //此处先判断原信号数和此次获取的是否一致，不一致需要更新信号数
                Integer diff = semaphoreNum - oldNum;
                if (diff > 0) {
                    //提升信号数
                    log.info("{}接口原信号数{}个,现在增加{}个", key, oldNum, diff);
                    semaphore.addPermits(diff);
                    redisTemplate.opsForValue().set(curKey, semaphoreNum);
                } else if (diff < 0) {
                    int absDiff = Math.abs(diff);
                    //减少信号数，需要考虑目前可用的信号数是否够，如果不够直接忽略
                    int permits = semaphore.availablePermits();
                    if (permits >= absDiff) {
                        log.info("{}接口原信号数{}个,现在减少{}个", key, oldNum, absDiff);
                        semaphore.addPermits(diff);
                        redisTemplate.opsForValue().set(curKey, semaphoreNum);
                    } else {
                        log.warn("更新{}接口的信号数失败，当前可用信号数={}小于待缩减信号数={}", key, permits, absDiff);
                    }
                }
            }
        } catch (Exception e) {
            log.error("更新{}接口的信号数异常", key, e);
            return false;
        }
        return true;
    }

    /**
     * 获取指定的信号数
     */
    private Integer getSemaphoreNum(String key) {
        key = SEMAPHORE_NUM_PREFIX + key;
        Integer num = (Integer) redisTemplate.opsForValue().get(key);
        if (num == null || num < 0) {
            synchronized (LOCK) {
                num = (Integer) redisTemplate.opsForValue().get(key);
                if (num == null || num < 0) {
                    String numStr = semphoreMapper.selectSemphoreNum(key);
                    if (StringUtils.hasLength(numStr)) {
                        num = Integer.parseInt(numStr);
                    }
                    if (num == null || num < 0) {
                        if (log.isInfoEnabled()) {
                            log.info("接口{}没有配置指定的信号数，默认分配一个信号数", key);
                        }
                        num = 1;
                    }
                    redisTemplate.opsForValue().set(key, num, EXPIRETIMES, EXPIRETYPE);
                }
            }
        }
        return num;
    }
}
