<?php

namespace XZD\AI\V5\Core;

/**
 * AI API 速率限制器 — 令牌桶算法
 *
 * 防止多Worker并发时同时发起大量AI API请求，触发提供商速率限制。
 * 基于 WordPress Transient 实现（兼容对象缓存，提供接近原子性的令牌操作）。
 *
 * 默认参数：
 *   - 桶容量: 5个令牌（最多5个并发AI请求）
 *   - 填充速率: 1个/秒
 *   - 获取超时: 30秒
 */
class ApiRateLimiter
{
    private const BUCKET_KEY = 'xzd_api_token_bucket';
    private const LOCK_KEY   = 'xzd_api_token_lock';

    private int $capacity;
    private float $fillRate;
    private int $timeout;

    private static ?self $instance = null;

    public static function instance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function __construct()
    {
        $this->capacity = Config::instance()->int('api_rate_limit.capacity', 5);
        $this->fillRate = (float) Config::instance()->get('api_rate_limit.fill_rate', 1.0);
        $this->timeout  = Config::instance()->int('api_rate_limit.timeout', 30);
    }

    /**
     * 获取令牌（阻塞直到获取成功或超时）
     *
     * @param int $timeoutSeconds 最长等待秒数（0=使用默认值）
     * @return bool 是否成功获取
     */
    public function acquire(int $timeoutSeconds = 0): bool
    {
        $timeout = $timeoutSeconds > 0 ? $timeoutSeconds : $this->timeout;
        $deadline = microtime(true) + $timeout;

        while (microtime(true) < $deadline) {
            // 原子操作锁（用 transient 模拟互斥）
            $locked = $this->lockBucket();
            if (!$locked) {
                usleep(100000); // 100ms 后重试
                continue;
            }

            try {
                $bucket = $this->readBucket();
                $now = microtime(true);

                // 根据流逝时间补充令牌
                $elapsed = $now - ($bucket['last_refill'] ?? $now);
                $newTokens = min(
                    $this->capacity,
                    ($bucket['tokens'] ?? $this->capacity) + $elapsed * $this->fillRate
                );

                if ($newTokens >= 1.0) {
                    $newTokens -= 1.0;
                    $this->writeBucket($newTokens, $now);
                    return true;
                }

                // 令牌不足，计算需要等待的时间
                $waitTime = (1.0 - $newTokens) / $this->fillRate;
                $this->writeBucket($newTokens, $now);
            } finally {
                $this->unlockBucket();
            }

            // 等待令牌恢复（最少100ms避免忙等）
            $sleep = max(0.1, min($waitTime ?? 0.5, ($deadline - microtime(true))));
            if ($sleep > 0) {
                usleep((int)($sleep * 1000000));
            }
        }

        Logger::warning('api_rate_limiter', "获取令牌超时（{$timeout}秒）");
        return false;
    }

    /**
     * 释放令牌（AI调用完成后归还）
     */
    public function release(): void
    {
        $locked = $this->lockBucket();
        if (!$locked) return;

        try {
            $bucket = $this->readBucket();
            $tokens = min($this->capacity, ($bucket['tokens'] ?? 0) + 1.0);
            $this->writeBucket($tokens, microtime(true));
        } finally {
            $this->unlockBucket();
        }
    }

    /**
     * 当前可用令牌数
     */
    public function available(): float
    {
        $bucket = $this->readBucket();
        $elapsed = microtime(true) - ($bucket['last_refill'] ?? microtime(true));
        return min(
            $this->capacity,
            ($bucket['tokens'] ?? $this->capacity) + $elapsed * $this->fillRate
        );
    }

    /**
     * 带速率限制的AI调用包装器
     *
     * @example ApiRateLimiter::withLimit(fn() => Client::chat(...))
     */
    public static function withLimit(callable $callback, int $timeout = 30): mixed
    {
        $limiter = self::instance();
        if (!$limiter->acquire($timeout)) {
            throw new \RuntimeException('AI API速率限制：获取令牌超时');
        }
        try {
            return $callback();
        } finally {
            $limiter->release();
        }
    }

    // ── 内部 ──

    private function readBucket(): array
    {
        $data = get_transient(self::BUCKET_KEY);
        if ($data === false || !is_array($data)) {
            return ['tokens' => $this->capacity, 'last_refill' => microtime(true)];
        }
        return $data;
    }

    private function writeBucket(float $tokens, float $refillTime): void
    {
        set_transient(self::BUCKET_KEY, [
            'tokens'      => max(0, $tokens),
            'last_refill' => $refillTime,
        ], 60); // 60秒TTL，足够长但不会永久占用
    }

    private function lockBucket(): bool
    {
        $token = md5(uniqid('rate_limit_', true));
        return set_transient(self::LOCK_KEY, $token, 3); // 3秒锁
    }

    private function unlockBucket(): void
    {
        delete_transient(self::LOCK_KEY);
    }
}
