<?php
/**
 * BookiFlex Background Jobs Extension
 *
 * @package    BookiFlex
 * @subpackage BackgroundJobs
 * @author     BookiFlex
 * @copyright  Copyright (c) 2025 BookiFlex
 * @license    GPL-2.0-or-later
 *
 * @link       https://bookiflex.com
 */

declare(strict_types=1);

namespace BookiFlex\Extensions\BackgroundJobs\Api\Controller;

use BookiFlex\Core\Api\Controller\AbstractController;
use BookiFlex\Core\Infrastructure\Storage\CacheRepositoryInterface;
use BookiFlex\Extensions\BackgroundJobs\Service\HealthChecker;
use BookiFlex\Extensions\BackgroundJobs\Service\QueueRunner;
use Psr\Log\LoggerInterface;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;

defined( 'ABSPATH' ) || exit;

/**
 * Cron Controller
 *
 * Provides a public REST API endpoint for triggering ActionScheduler queue.
 */
class CronController extends AbstractController
{
    public const API_ENDPOINT_NAME = 'cron';

    private const TRANSIENT_RATE_LIMIT = 'bflex_bgj_api_rate_limit';
    private const RATE_LIMIT_INTERVAL = 60; // 1 minute

    public function __construct(
        private readonly QueueRunner $queueRunner,
        private readonly HealthChecker $healthChecker,
        private readonly CacheRepositoryInterface $cache,
        private readonly ?LoggerInterface $logger = null,
        string $namespace = 'bflex/v1'
    ) {
        parent::__construct($namespace);
    }

    /**
     * Register REST API routes
     */
    public function registerRoutes(): void
    {
        // Public endpoint: allows external cron services to trigger background job queue (includes rate limiting)
        register_rest_route(
            $this->namespace,
            '/' . self::API_ENDPOINT_NAME,
            [
                'methods' => 'GET',
                'callback' => [$this, 'triggerCron'],
                'permission_callback' => '__return_true',
            ]
        );
    }

    /**
     * Trigger cron endpoint handler
     *
     * @param WP_REST_Request $request REST request object
     * @return WP_REST_Response|WP_Error Response or error
     */
    public function triggerCron(WP_REST_Request $request): WP_REST_Response|WP_Error
    {
        // Rate limiting
        if (!$this->checkRateLimit()) {
            return $this->error(
                'rate_limit_exceeded',
                __('Too many requests. Please wait before triggering again.', 'bookiflex'),
                429
            );
        }

        try {
            // Run queue
            $result = $this->queueRunner->run();

            // Record successful run
            $this->healthChecker->recordRun();

            // Get updated health status
            $healthStatus = $this->healthChecker->getHealthStatus();

            return $this->success([
                'status' => 'success',
                'timestamp' => $result['timestamp'],
                'health' => $healthStatus->toArray(),
            ]);
        } catch (\Throwable $e) {
            return $this->handleError(
                $e,
                'cron_trigger_failed',
                __('Failed to trigger background jobs', 'bookiflex')
            );
        }
    }

    /**
     * Check rate limit using transient
     *
     * @return bool True if request allowed, false if rate limited
     */
    private function checkRateLimit(): bool
    {
        if ($this->cache->has(self::TRANSIENT_RATE_LIMIT)) {
            return false;
        }

        $this->cache->set(
            self::TRANSIENT_RATE_LIMIT,
            time(),
            self::RATE_LIMIT_INTERVAL
        );

        return true;
    }

    /**
     * Handle errors with proper logging
     *
     * @param \Throwable $throwable Exception thrown
     * @param string $code Error code
     * @param string $message Error message
     * @return WP_Error WordPress error object
     */
    protected function handleError(\Throwable $throwable, string $code, string $message): WP_Error
    {
        $this->logger?->error('BackgroundJobs CronController error', [
            'code' => $code,
            'error' => $throwable->getMessage()
        ]);

        return $this->error($code, $message, 500);
    }
}
