<?php
/**
 * LolaCore — Hub Connect Endpoint
 *
 * @package LolaCore
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\Hub;

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Required for core functionality, dynamic queries, and performance. Validated inputs.

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

/**
 * Class ConnectEndpoint
 *
 * Authenticated REST endpoint that completes the Hub Pro connection
 * handshake. Validates a temporary connection token (15-min TTL),
 * saves the hub's public key, and sets the connection status.
 *
 * Route: POST /wp-json/lolacore/v1/connect
 */
class ConnectEndpoint
{
    /**
     * Token time-to-live in seconds (15 minutes).
     */
    private const TOKEN_TTL = 900;

    /**
     * Register the REST route on the rest_api_init action.
     */
    public function register(): void
    {
        \add_action('rest_api_init', [$this, 'registerRoute']);
    }

    /**
     * Register the /connect route.
     */
    public function registerRoute(): void
    {
        \register_rest_route('lolacore/v1', '/connect', [
            'methods'             => \WP_REST_Server::CREATABLE,
            'callback'            => [$this, 'handle'],
            'permission_callback' => [$this, 'validatePermission'],
            'args'                => [
                'connection_token' => [
                    'required'          => true,
                    'type'              => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
                'hub_public_key' => [
                    'required'          => true,
                    'type'              => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
            ],
        ]);
    }

    /**
     * Validate the connection token before allowing the request to proceed.
     *
     * @param WP_REST_Request $request The incoming request.
     * @return bool|WP_Error True if valid, WP_Error otherwise.
     */
    public function validatePermission(WP_REST_Request $request): bool|WP_Error
    {
        $token = $request->get_param('connection_token');

        if (empty($token)) {
            return new WP_Error(
                'lolacore_missing_token',
                'Connection token is required.',
                ['status' => 401]
            );
        }

        // Retrieve stored token data
        $storedToken    = $this->getConfigValue('hub_connection_token', '');
        $tokenCreatedAt = $this->getConfigValue('hub_connection_token_created', '');

        if (empty($storedToken) || empty($tokenCreatedAt)) {
            return new WP_Error(
                'lolacore_no_pending_connection',
                'No pending connection request found.',
                ['status' => 403]
            );
        }

        // Validate token match using timing-safe comparison
        if (!hash_equals($storedToken, $token)) {
            return new WP_Error(
                'lolacore_invalid_token',
                'Invalid connection token.',
                ['status' => 403]
            );
        }

        // Validate TTL
        $createdTimestamp = strtotime($tokenCreatedAt);
        if ($createdTimestamp === false || (time() - $createdTimestamp) > self::TOKEN_TTL) {
            // Clean up expired token
            $this->deleteConfigValue('hub_connection_token');
            $this->deleteConfigValue('hub_connection_token_created');

            return new WP_Error(
                'lolacore_token_expired',
                'Connection token has expired. Please initiate a new connection.',
                ['status' => 403]
            );
        }

        return true;
    }

    /**
     * Handle the connection request.
     *
     * @param WP_REST_Request $request The incoming request.
     * @return WP_REST_Response
     */
    public function handle(WP_REST_Request $request): WP_REST_Response
    {
        $hubPublicKey = $request->get_param('hub_public_key');

        // Save hub public key
        $this->setConfigValue('hub_public_key', $hubPublicKey);

        // Update connection status
        $this->setConfigValue('hub_connection_status', 'connected');

        // Record connection timestamp
        $this->setConfigValue('hub_connected_at', \current_time('mysql'));

        // Clean up temporary token
        $this->deleteConfigValue('hub_connection_token');
        $this->deleteConfigValue('hub_connection_token_created');

        $nodeId = $this->getConfigValue('hub_node_id', '');

        return new WP_REST_Response([
            'success' => true,
            'node_id' => $nodeId,
            'status'  => 'connected',
        ], 200);
    }

    /**
     * Retrieve a value from the lolacore_config table.
     *
     * @param string      $key     The config key.
     * @param string|null $default Default value if key not found.
     * @return string|null
     */
    private function getConfigValue(string $key, ?string $default = null): ?string
    {
        global $wpdb;
        $table = $wpdb->prefix . 'lolacore_config';

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- Custom LolaCore table query.
        $value = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT config_value FROM {$table} WHERE config_key = %s",
                $key,
            )
        );

        return $value ?? $default;
    }

    /**
     * Set a value in the lolacore_config table (upsert).
     *
     * @param string $key   The config key.
     * @param string $value The config value.
     */
    private function setConfigValue(string $key, string $value): void
    {
        global $wpdb;
        $table = $wpdb->prefix . 'lolacore_config';

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- Custom LolaCore table query.
        $wpdb->query(
            $wpdb->prepare(
                "INSERT INTO {$table} (config_key, config_value) VALUES (%s, %s)
                 ON DUPLICATE KEY UPDATE config_value = VALUES(config_value)",
                $key,
                $value,
            )
        );
    }

    /**
     * Delete a config entry by key.
     *
     * @param string $key The config key to delete.
     */
    private function deleteConfigValue(string $key): void
    {
        global $wpdb;
        $table = $wpdb->prefix . 'lolacore_config';

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- Custom LolaCore table query.
        $wpdb->delete($table, ['config_key' => $key], ['%s']);
    }
}