<?php

namespace XZD\AI\V5\AI;

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

/**
 * API rate limiter with role/frequency/budget tiers
 */
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();
    }

    /**
     * Check if a request is allowed
     *
     * @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' => ''];
    }

    /**
     * Record a request
     */
    public function record(): void
    {
        $now = time();

        set_transient($this->key('last'), $now, 60);

        // 分钟计数 — 使用 MySQL 原子递增（而非 transient 读-改-写），防止并发少计
        global $wpdb;
        $optNameMinute = '_transient_' . $this->key('minute');
        $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",
            $optNameMinute, 1
        ));
        $timeoutMinute = '_transient_timeout_' . $this->key('minute');
        $existingMinuteTimeout = get_option($timeoutMinute);
        if (!$existingMinuteTimeout || (int)$existingMinuteTimeout < $now) {
            update_option($timeoutMinute, $now + 60, false);
        }

        // 每日计数 — MySQL 原子递增
        $optName = '_transient_' . $this->key('daily');
        $endOfDay = strtotime('tomorrow') - $now;
        $ttl = max(60, $endOfDay);

        $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);
        }
    }

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

    /**
     * Get usage statistics
     *
     * @return array
     */
    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,
        ];
    }

    /**
     * Get remaining quota
     *
     * @return int
     */
    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}";
    }

    /**
     * Create rate limiter for current user
     *
     * @return self
     */
    public static function forCurrentUser(): self
    {
        $userId = get_current_user_id();
        $role = UserHelper::getRole($userId);

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