<?php
/**
 * Rate Limiter for Maple Local Fonts.
 *
 * @package Maple_Local_Fonts
 */

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Class MLF_Rate_Limiter
 *
 * Provides rate limiting functionality to prevent abuse of AJAX endpoints.
 */
class MLF_Rate_Limiter {

    /**
     * Default rate limit (requests per window).
     *
     * @var int
     */
    private $limit = 10;

    /**
     * Default time window in seconds.
     *
     * @var int
     */
    private $window = 60;

    /**
     * Constructor.
     *
     * @param int $limit  Maximum requests allowed per window.
     * @param int $window Time window in seconds.
     */
    public function __construct($limit = 10, $window = 60) {
        $this->limit = absint($limit);
        $this->window = absint($window);
    }

    /**
     * Check if the current user/IP is rate limited.
     *
     * @param string $action The action being rate limited.
     * @return bool True if rate limited (should block), false if allowed.
     */
    public function is_limited($action) {
        $key = $this->get_rate_limit_key($action);
        $data = get_transient($key);

        if ($data === false) {
            // First request, not limited
            return false;
        }

        return $data['count'] >= $this->limit;
    }

    /**
     * Record a request for rate limiting purposes.
     *
     * @param string $action The action being recorded.
     * @return void
     */
    public function record_request($action) {
        $key = $this->get_rate_limit_key($action);
        $data = get_transient($key);

        if ($data === false) {
            // First request in this window
            $data = [
                'count' => 1,
                'start' => time(),
            ];
        } else {
            $data['count']++;
        }

        // Set/update transient with remaining window time
        $elapsed = time() - $data['start'];
        $remaining = max(1, $this->window - $elapsed);

        set_transient($key, $data, $remaining);
    }

    /**
     * Check rate limit and record request in one call.
     *
     * @param string $action The action being checked.
     * @return bool True if request is allowed, false if rate limited.
     */
    public function check_and_record($action) {
        if ($this->is_limited($action)) {
            return false;
        }

        $this->record_request($action);
        return true;
    }

    /**
     * Get the number of remaining requests in the current window.
     *
     * @param string $action The action to check.
     * @return int Number of remaining requests.
     */
    public function get_remaining($action) {
        $key = $this->get_rate_limit_key($action);
        $data = get_transient($key);

        if ($data === false) {
            return $this->limit;
        }

        return max(0, $this->limit - $data['count']);
    }

    /**
     * Get the rate limit key for the current user/IP.
     *
     * @param string $action The action being rate limited.
     * @return string Transient key.
     */
    private function get_rate_limit_key($action) {
        // Use user ID if logged in, otherwise IP
        $user_id = get_current_user_id();

        if ($user_id > 0) {
            $identifier = 'user_' . $user_id;
        } else {
            // Sanitize and hash IP for privacy
            $ip = $this->get_client_ip();
            $identifier = 'ip_' . md5($ip);
        }

        return 'mlf_rate_' . sanitize_key($action) . '_' . $identifier;
    }

    /**
     * Get the client IP address.
     *
     * @return string Client IP address.
     */
    private function get_client_ip() {
        $ip = '';

        // Check for various headers (in order of reliability)
        $headers = [
            'HTTP_CLIENT_IP',
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_FORWARDED',
            'HTTP_X_CLUSTER_CLIENT_IP',
            'HTTP_FORWARDED_FOR',
            'HTTP_FORWARDED',
            'REMOTE_ADDR',
        ];

        foreach ($headers as $header) {
            if (!empty($_SERVER[$header])) {
                // X-Forwarded-For can contain multiple IPs, get the first one
                $ips = explode(',', sanitize_text_field(wp_unslash($_SERVER[$header])));
                $ip = trim($ips[0]);

                // Validate IP
                if (filter_var($ip, FILTER_VALIDATE_IP)) {
                    break;
                }
            }
        }

        // Fallback to localhost if no valid IP found
        return $ip ?: '127.0.0.1';
    }

    /**
     * Clear rate limit for a specific action and user/IP.
     *
     * @param string $action The action to clear.
     * @return void
     */
    public function clear($action) {
        $key = $this->get_rate_limit_key($action);
        delete_transient($key);
    }
}
