<?php

namespace XZD\AI\V5\AI;

use XZD\AI\V5\Core\Config;
use XZD\AI\V5\Core\UserHelper;

/**
 * API 速率限制器 — 基于角色/频率/预算的三层限制
 *
 * 使用 Transient 持久化计数，无需额外数据库表
 */
class RateLimiter
{
    private string $role;
    private int $userId;
    private array $limits;

    public function __construct(string $role = 'guest', int $userId = 0)
    {
        $this->role = $role;
        $this->userId = $userId;
        $this->limits = $this->loadLimits();
    }

    /**
     * 检查是否可以发起请求
     *
     * @return array ['allowed'=>bool, 'message'=>string]
     */
    public function check(): array
    {
        // 管理员无限制
        if ($this->limits['unlimited'] ?? false) {
            return ['allowed' => true, 'message' => ''];
        }

        // 检查间隔
        $lastRequest = get_transient($this->key('last'));
        if ($lastRequest) {
            $elapsed = time() - $lastRequest;
            if ($elapsed < $this->limits['interval']) {
                $wait = $this->limits['interval'] - $elapsed;
                return ['allowed' => false, 'message' => "请等待 {$wait} 秒后再试"];
            }
        }

        // 检查每分钟限制
        $perMinute = (int) get_transient($this->key('minute'));
        if ($perMinute >= $this->limits['per_minute']) {
            return ['allowed' => false, 'message' => '请求过于频繁，请稍后再试'];
        }

        // 检查每日限制
        $daily = (int) get_transient($this->key('daily'));
        if ($daily >= $this->limits['daily']) {
            return ['allowed' => false, 'message' => '已达到每日请求上限'];
        }

        // 检查月度预算
        $budget = $this->checkBudget();
        if (!$budget['allowed']) {
            return $budget;
        }

        return ['allowed' => true, 'message' => ''];
    }

    /**
     * 记录一次请求
     * V3.1.7: 每日计数使用 MySQL 原子自增，防止并发竞态导致计数不准
     */
    public function record(): void
    {
        $now = time();

        // 更新最后请求时间
        set_transient($this->key('last'), $now, 60);

        // 更新每分钟计数（短暂窗口内竞态影响可接受）
        $minuteCount = (int) get_transient($this->key('minute'));
        set_transient($this->key('minute'), $minuteCount + 1, 60);

        // V3.1.7: 每日计数使用 MySQL 原子自增
        // 高并发下 get_transient + set_transient 存在竞态导致计数偏少
        global $wpdb;
        $optName = '_transient_' . $this->key('daily');
        $endOfDay = strtotime('tomorrow') - $now;
        $ttl = max(60, $endOfDay);

        // 使用 INSERT ... ON DUPLICATE KEY UPDATE 实现原子自增
        $wpdb->query($wpdb->prepare(
            "INSERT INTO {$wpdb->options} (option_name, option_value, autoload)
             VALUES (%s, %d, 'no')
             ON DUPLICATE KEY UPDATE option_value = option_value + 1",
            $optName, 1
        ));

        // 确保过期时间正确（仅在首次创建时设置）
        $timeoutName = '_transient_timeout_' . $this->key('daily');
        $existingTimeout = get_option($timeoutName);
        if (!$existingTimeout || (int)$existingTimeout < $now) {
            update_option($timeoutName, $now + $ttl, false);
        }
    }

    /**
     * 记录费用
     */
    public function recordCost(float $rmb): void
    {
        $key = $this->key('cost_monthly');
        $current = (float) get_transient($key);
        set_transient($key, $current + $rmb, MONTH_IN_SECONDS);
    }

    /**
     * 获取当前使用统计
     */
    public function getStats(): array
    {
        $dailyUsed = (int) get_transient($this->key('daily'));
        $remaining = $this->getRemaining();
        return [
            'unlimited'   => $this->limits['unlimited'] ?? false,
            'remaining'   => $remaining,
            'limit'       => $this->limits['daily'],
            'daily_used'  => $dailyUsed,
            'daily_limit' => $this->limits['daily'],
            'minute_used' => (int) get_transient($this->key('minute')),
            'minute_limit' => $this->limits['per_minute'],
            'monthly_cost' => round((float) get_transient($this->key('cost_monthly')), 4),
            'monthly_budget' => $this->limits['monthly_budget'] ?? 0,
        ];
    }

    /**
     * 获取剩余配额（用于前端展示）
     */
    public function getRemaining(): int
    {
        if ($this->limits['unlimited'] ?? false) {
            return PHP_INT_MAX;
        }
        $daily = (int) get_transient($this->key('daily'));
        return max(0, $this->limits['daily'] - $daily);
    }

    // ===== 内部 =====

    private function loadLimits(): array
    {
        $config = Config::instance();
        $prefix = "rate_limit.{$this->role}";

        return [
            'daily'      => $config->int("{$prefix}.daily", 20),
            'per_minute' => $config->int("{$prefix}.per_minute", 5),
            'interval'   => $config->int("{$prefix}.interval", 3),
            'unlimited'  => $this->role === 'admin' ? $config->bool("{$prefix}.unlimited", true) : false,
            'monthly_budget' => $config->get('cost.monthly_budget', 0),
        ];
    }

    private function checkBudget(): array
    {
        $budget = $this->limits['monthly_budget'] ?? 0;
        if ($budget <= 0) {
            return ['allowed' => true, 'message' => ''];
        }

        $spent = (float) get_transient($this->key('cost_monthly'));
        if ($spent >= $budget) {
            $action = Config::instance()->get('cost.over_budget_action', 'warn');
            if ($action === 'block') {
                return ['allowed' => false, 'message' => '月度预算已用完'];
            }
        }

        return ['allowed' => true, 'message' => ''];
    }

    private function key(string $type): string
    {
        return "xzd_rate_{$type}_{$this->role}_{$this->userId}";
    }

    /**
     * 按当前用户创建限速器实例
     */
    public static function forCurrentUser(): self
    {
        $userId = get_current_user_id();
        $role = UserHelper::getRole($userId);

        return new self($role, $userId);
    }
}
