<?php

namespace WPSN\Core\Queue;

use WPSN\Core\Logger;
use WPSN\Core\RateLimiter;

defined('ABSPATH') || exit;

/**
 * Token-protected REST endpoint to run a small amount of queue work.
 *
 * Why:
 * - WP-Cron can be unreliable on some hosts
 * - admin-ajax workers can trigger 503 storms
 * - external cron pings a lightweight endpoint: it advances the queue safely
 */
final class QueueRemoteRunner
{
    private QueueRunner $runner;
    private QueueInterface $queue;
    private Logger $logger;
    private RateLimiter $limiter;

    public function __construct(QueueRunner $runner, QueueInterface $queue, Logger $logger, RateLimiter $limiter)
    {
        $this->runner = $runner;
        $this->queue = $queue;
        $this->logger = $logger;
        $this->limiter = $limiter;
    }

    public function register(): void
    {
        add_action('rest_api_init', [$this, 'registerRoutes']);
    }

    public function registerRoutes(): void
    {
        register_rest_route('wpsn/v1', '/queue/tick', [
            'methods'             => ['GET', 'POST'],
            'permission_callback' => [$this, 'permission'],
            'callback'            => [$this, 'handle'],
            'args'                => [
                'token' => [
                    'type' => 'string',
                    'required' => true,
                ],
                'max' => [
                    'type' => 'integer',
                    'required' => false,
                ],
            ],
        ]);
    }

    /**
     * @return bool|\WP_Error
     */
    public function permission(\WP_REST_Request $request)
    {
        if (!QueueRemoteRunnerConfig::enabled()) {
            return new \WP_Error('wpsn_remote_queue_disabled', 'Remote runner disabled.', ['status' => 404]);
        }

        $token = (string) $request->get_param('token');
        $token = trim($token);
        $expected = QueueRemoteRunnerConfig::token();

        if ($token === '' || $expected === '' || !hash_equals($expected, $token)) {
            return new \WP_Error('wpsn_remote_queue_forbidden', 'Forbidden.', ['status' => 403]);
        }

        return true;
    }

    /**
     * @return \WP_REST_Response|\WP_Error
     */
    public function handle(\WP_REST_Request $request)
    {
        $ip = isset($_SERVER['REMOTE_ADDR']) ? (string) $_SERVER['REMOTE_ADDR'] : '';
        $key = 'wpsn_remote_queue_' . md5($ip . '|' . QueueRemoteRunnerConfig::token());

        $hit = $this->limiter->hit($key, QueueRemoteRunnerConfig::rateLimit(), QueueRemoteRunnerConfig::rateWindowSeconds());
        if (empty($hit['allowed'])) {
            return new \WP_Error(
                'wpsn_remote_queue_rate_limited',
                'Rate limited.',
                [
                    'status' => 429,
                    'retry_after' => (int) ($hit['retry_after'] ?? 1),
                    'limit' => (int) ($hit['limit'] ?? 0),
                ]
            );
        }

        // Respect operational pause.
        $paused = QueuePause::isPaused();

        $reqMax = (int) $request->get_param('max');
        $max = $reqMax > 0 ? $reqMax : QueueRemoteRunnerConfig::maxJobs();
        $max = max(1, min(20, $max));
        // Hard cap by config.
        $max = min($max, QueueRemoteRunnerConfig::maxJobs());

        $processed = 0;
        $err = '';

        if (!$paused) {
            try {
                $processed = (int) $this->runner->run($max);
            } catch (\Throwable $e) {
                $err = $e->getMessage();
                $processed = 0;
                try {
                    $this->logger->warning('Remote queue tick failed: ' . $err);
                } catch (\Throwable $e2) {
                    // ignore
                }
            }
        }

        $counts = [];
        try {
            $counts = $this->queue->counts();
        } catch (\Throwable $e) {
            $counts = [];
        }

        $data = [
            'ok' => true,
            'paused' => $paused,
            'processed' => $processed,
            'max' => $max,
            'counts' => $counts,
            'ts' => time(),
        ];
        if ($err !== '') {
            $data['error'] = $err;
        }

        return rest_ensure_response($data);
    }
}
