<?php

namespace ZenoChatbot\Services;

use ZenoChatbot\Framework\Support\Arr;
use ZenoChatbot\Helper\Helper;
use ZenoChatbot\Helper\Security;

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

class RateLimiter
{
    /**
     * Create a limiter instance.
     *
     * @return self
     */
    public static function create(): self
    {
        return new self();
    }

    /**
     * Resolve default policy by scope.
     *
     * @param string $scope
     * @return array{ip_limit:int,ip_window:int,session_limit:int,session_window:int}
     */
    public function defaultPolicy(string $scope): array
    {
        $defaults = [
            'chat' => [
                'ip_limit' => 20,
                'ip_window' => 60,
                'session_limit' => 60,
                'session_window' => 600,
            ],
            'history' => [
                'ip_limit' => 60,
                'ip_window' => 60,
                'session_limit' => 120,
                'session_window' => 600,
            ],
        ];

        return $defaults[$scope] ?? [
            'ip_limit' => 60,
            'ip_window' => 60,
            'session_limit' => 120,
            'session_window' => 600,
        ];
    }

    /**
     * Check whether the request is allowed for the given scope.
     *
     * @param string $scope
     * @param array<string,int> $policy
     * @return bool
     */
    public function allow(string $scope, array $policy = []): bool
    {
        $resolved = wp_parse_args($policy, $this->defaultPolicy($scope));

        $ipAddress = Helper::getClientIp();
        if (!Security::checkRateLimit(
            "{$scope}:ip:{$ipAddress}",
            (int) Arr::get($resolved, 'ip_limit', 60),
            (int) Arr::get($resolved, 'ip_window', 60)
        )) {
            return false;
        }

        $sessionId = (string) Arr::get(Helper::getSessionData(), 'session_id', '');
        if ($sessionId !== '' && !Security::checkRateLimit(
            "{$scope}:session:{$sessionId}",
            (int) Arr::get($resolved, 'session_limit', 120),
            (int) Arr::get($resolved, 'session_window', 600)
        )) {
            return false;
        }

        return true;
    }

    /**
     * Assert that a request is not rate limited.
     *
     * @param string $scope
     * @param array<string,int> $policy
     * @return void
     */
    public function assertOrFail(string $scope, array $policy = []): void
    {
        if (!$this->allow($scope, $policy)) {
            throw new \RuntimeException(
                // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
                sprintf('Rate limit exceeded for scope "%s"', $scope)
            );
        }
    }
}
