package link.at17.mid.tushare.data.crawler;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 线程安全的重试与延迟控制器
 */
@Accessors(chain = true)
@Slf4j
public class RetryAndDelay {

    @Getter
    @Setter
    private int retryTime = 5;

    private volatile long delayBase = 500L;

    // 用 AtomicReference<Double> 存储 delayFactor
    private final AtomicReference<Double> delayFactor = new AtomicReference<>(1d);

    @Getter
    @Setter
    private int threadNum = 1;

    private final AtomicInteger triggerFactorAdd = new AtomicInteger(0);

    // 最大因子限制，避免无限增长
    @Getter
    @Setter
    private double maxDelayFactor = 5.0;

    /**
     * 计算当前延迟
     */
    public long getDelay() {
        double factor = delayFactor.get();
        long delay = (long) Math.floor(delayBase * factor);

        // 衰减回 1
        if (factor > 1) {
            double newFactor = Math.max(1d, factor - 0.0005);
            delayFactor.compareAndSet(factor, newFactor);
        } else if (factor < 1) {
            delayFactor.set(1d);
        }

        return delay;
    }

    public RetryAndDelay setDelay(long delay) {
        this.delayBase = delay;
        this.delayFactor.set(1d);
        return this;
    }

    /**
     * 按每分钟请求限制更新
     */
    public RetryAndDelay setDelayByMinLimit(int minLimit) {
        return setDelayBySpanAndLimit(60000, minLimit);
    }

    /**
     * 按每小时请求限制更新
     */
    public RetryAndDelay setDelayByHourLimit(int hourLimit) {
        return setDelayBySpanAndLimit(3600000, hourLimit);
    }

    private RetryAndDelay setDelayBySpanAndLimit(int spanMs, int limit) {
        if (threadNum <= 0) {
            threadNum = 1;
        }

        long newDelay = (long) Math.floor(1.0d * spanMs / limit * threadNum);
        log.debug("Trigger crawler limit = {}/span, delay = {}ms/{} threads", limit, newDelay, threadNum);

        if (delayBase == newDelay) {
            int count = triggerFactorAdd.incrementAndGet();
            if (count >= threadNum) {
                // 增加因子，但不超过最大值
                delayFactor.updateAndGet(f -> Math.min(maxDelayFactor, f + 0.05));
                triggerFactorAdd.set(0);
            }
            log.debug("Delay is still too short, increase delayFactor = {}, new delay = {}", delayFactor.get(), getDelay());
        } else {
            delayBase = newDelay;
            triggerFactorAdd.set(0);
        }

        return this;
    }
}
