<?php
/**
 * API Manager for BoostBot REST API.
 *
 * @package BoostBot
 */

// Prevent direct access to the file.
if (!defined('ABSPATH')) {
    exit;
}

/**
 * BoostBot_API_Manager class.
 *
 * Manages the registration and initialization of all REST API endpoints.
 *
 * @since 1.0.0
 */
class BoostBot_API_Manager
{
    /**
     * The API namespace.
     *
     * @since 1.0.0
     * @access private
     * @var string $namespace
     */
    private $namespace = 'boostbot/v1';

    /**
     * Registered endpoint instances.
     *
     * @since 1.0.0
     * @access private
     * @var array $endpoints
     */
    private $endpoints = [];

    /**
     * Singleton instance.
     *
     * @since 1.0.0
     * @access private static
     * @var BoostBot_API_Manager|null
     */
    private static $instance = null;

    /**
     * Get the singleton instance.
     *
     * @since 1.0.0
     * @return BoostBot_API_Manager The singleton instance.
     */
    public static function get_instance()
    {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Constructor.
     *
     * @since 1.0.0
     */
    private function __construct()
    {
        // Private constructor for singleton pattern
    }

    /**
     * Initialize the API Manager.
     *
     * @since 1.0.0
     */
    public function init()
    {
        add_action('rest_api_init', [$this, 'register_routes']);

        // Strip empty or stale nonces from BoostBot REST requests before WordPress's
        // rest_cookie_check_errors() (priority 100) can reject them with 403.
        add_filter( 'rest_authentication_errors', array( $this, 'fix_empty_nonce_for_public_endpoints' ), 99 );

        // Log API initialization
        if (class_exists('BoostBot_Logger')) {
            BoostBot_Logger::debug('API Manager initialized');
        }
    }

    /**
     * Strip invalid/stale nonces from BoostBot REST API requests.
     *
     * Page caching plugins (WP Rocket, etc.) bake a wp_rest nonce into cached HTML.
     * After the nonce expires (12-24h), WordPress's rest_cookie_check_errors() rejects
     * every request with 403 "Cookie check failed" — BEFORE the endpoint's permission
     * callback runs. No public BoostBot endpoint requires nonce authentication.
     *
     * This filter runs at priority 99 (just before the cookie check at 100) and:
     * - Empty nonce → stripped (anonymous request, permission callback decides)
     * - Stale/invalid nonce → stripped (prevents 403 from rest_cookie_check_errors)
     * - Valid nonce → kept (admin endpoints authenticate via cookie+nonce normally)
     *
     * @since 1.19.0
     * @param WP_Error|null|true $result Current auth result.
     * @return WP_Error|null|true Unchanged result.
     */
    public function fix_empty_nonce_for_public_endpoints( $result ) {
        if ( ! empty( $result ) ) {
            return $result;
        }

        $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
        if ( strpos( $request_uri, '/wp-json/boostbot/' ) === false
            && strpos( $request_uri, 'rest_route=/boostbot/' ) === false ) {
            return $result;
        }

        $nonce = null;
        if ( isset( $_REQUEST['_wpnonce'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            $nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
            $nonce = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_WP_NONCE'] ) );
        }

        if ( null === $nonce ) {
            return $result;
        }

        if ( '' === $nonce || ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
            unset( $_SERVER['HTTP_X_WP_NONCE'] );
            unset( $_REQUEST['_wpnonce'] );
            unset( $_GET['_wpnonce'] );
            unset( $_POST['_wpnonce'] );
        }

        return $result;
    }

    /**
     * Register all API routes.
     *
     * @since 1.0.0
     */
    public function register_routes()
    {
        // Get all endpoint classes
        $endpoint_classes = $this->get_endpoint_classes();

        foreach ($endpoint_classes as $class_name) {
            if (class_exists($class_name)) {
                $endpoint = new $class_name();
                $endpoint->register();
                $this->endpoints[] = $endpoint;

                if (class_exists('BoostBot_Logger')) {
                    BoostBot_Logger::debug('Registered endpoint', ['class' => $class_name]);
                }
            } else {
                if (class_exists('BoostBot_Logger')) {
                    BoostBot_Logger::warning('Endpoint class not found', ['class' => $class_name]);
                }
            }
        }

        // Register nonce refresh endpoint
        $this->register_nonce_refresh_endpoint();

        if (class_exists('BoostBot_Logger')) {
            BoostBot_Logger::info('All API routes registered', [
                'count' => count($this->endpoints),
            ]);
        }
    }

    /**
     * Get all endpoint classes to register.
     *
     * @since 1.0.0
     * @return array Array of endpoint class names.
     */
    private function get_endpoint_classes()
    {
        $classes = [
            // Public endpoints
            'BoostBot_Chat_Endpoint',
            'BoostBot_Config_Endpoint',
            'BoostBot_Widget_Endpoint',

            // Admin endpoints
            'BoostBot_Leads_Endpoint',
            'BoostBot_Conversations_Endpoint',
            'BoostBot_Credits_Endpoint',
            'BoostBot_Indexing_Credits_Endpoint',
            'BoostBot_Pricing_Endpoint',
            'BoostBot_Purchases_Endpoint',
            'BoostBot_Migration_Endpoint',
            'BoostBot_Settings_Endpoint',
            'BoostBot_Analytics_Endpoint',
            'BoostBot_Export_Endpoint',
            'BoostBot_Index_Endpoint',

            // License endpoints
            'BoostBot_License_Endpoint',

            // WooCommerce endpoints (Week 8)
            'BoostBot_WooCommerce_Endpoint',

            // Feedback endpoint (Week 9)
            'BoostBot_Feedback_Endpoint',

            // CRM Export endpoint (Week 10.5)
            'BoostBot_CRM_Export_Endpoint',

            // Bots endpoint (Week 10.6)
            'BoostBot_Bots_Endpoint',

            // Personalities endpoint (Week 10.8)
            'BoostBot_Personalities_Endpoint',

            // Contact Lists endpoint (Week 10.8)
            'BoostBot_Contact_Lists_Endpoint',

            // Custom Widgets endpoint (Week 10.9)
            'BoostBot_Widgets_Endpoint',

            // Tones endpoint (Personality tones with System Prompts)
            'BoostBot_Tones_Endpoint',

            // Appearance Presets endpoint (Week 14)
            'BoostBot_Appearance_Presets_Endpoint',

            // Usage
            'BoostBot_Usage_Endpoint',

            // Google Calendar & Meetings (Week 17)
            'BoostBot_Google_Endpoint',
            'BoostBot_Meetings_Endpoint',

            // Opening Screen Widget (Week 18)
            'BoostBot_Opening_Screen_Endpoint',

            // Opening Screen Presets (Week 18)
            'BoostBot_Opening_Screen_Presets_Endpoint',

            // Files endpoint archived — see boostbot/_archive/files-endpoint/

            // Week 26 subscriptions (backend-owned)
            'BoostBot_Subscriptions_Endpoint',

            // Week 27.6: Brand Card (admin-only)
            'BoostBot_Brand_Card_Endpoint',

            // Direct SSE streaming architecture (Week 28+)
            'BoostBot_Session_Endpoint',
            'BoostBot_Retrieval_Endpoint',
            'BoostBot_Widget_Analyzer_Endpoint',

            // Backup (Export/Import) endpoint
            'BoostBot_Backup_Endpoint',

            // Multilingual language management
            'BoostBot_Languages_Endpoint',

            // Remote support session (reverse tunnel)
            'BoostBot_Support_Endpoint',

            // Credit alert emails (backend → WP wp_mail)
            'BoostBot_Alerts_Endpoint',

            // External KB access for authorized consumers (BoostCalendar etc.)
            'BoostBot_External_KB_Endpoint',

        ];

        // WP.org Guideline 8: only register the self-hosted update channel
        // when running a self-hosted build. WordPress.org must be the sole
        // update source for .org-hosted plugins.
        if ( defined( 'BOOSTBOT_SELF_HOSTED' ) && BOOSTBOT_SELF_HOSTED ) {
            $classes[] = 'BoostBot_Updates_Endpoint';
        }

        return $classes;
    }

    /**
     * Register nonce refresh endpoint.
     *
     * @since 1.0.0
     */
    private function register_nonce_refresh_endpoint()
    {
        register_rest_route(
            $this->namespace,
            '/nonce/refresh',
            [
                'methods'             => 'GET',
                'callback'            => [$this, 'refresh_nonce'],
                'permission_callback' => '__return_true', // Nonce refresh must be unrestricted
            ]
        );
    }

    /**
     * Refresh the nonce.
     *
     * @since 1.0.0
     * @return WP_REST_Response The response with new nonce.
     */
    public function refresh_nonce()
    {
        return new WP_REST_Response(
            [
                'success' => true,
                'nonce'   => wp_create_nonce('wp_rest'),
            ],
            200
        );
    }

    /**
     * Get the API namespace.
     *
     * @since 1.0.0
     * @return string The API namespace.
     */
    public function get_namespace()
    {
        return $this->namespace;
    }

    /**
     * Get the full API URL.
     *
     * @since 1.0.0
     * @param string $endpoint Optional. Specific endpoint path.
     * @return string The full API URL.
     */
    public function get_api_url($endpoint = '')
    {
        return rest_url($this->namespace . '/' . ltrim($endpoint, '/'));
    }

    /**
     * Get registered endpoints.
     *
     * @since 1.0.0
     * @return array Array of registered endpoint instances.
     */
    public function get_endpoints()
    {
        return $this->endpoints;
    }
}



