<?php
/**
 * LolaCore — Hub Memory Sync 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 LolaCore\Memory\MemoryManager;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

/**
 * Class SyncEndpoint
 *
 * HMAC-SHA256 authenticated REST endpoint for memory synchronization.
 * Returns active memory facts for vector indexing on the Hub side.
 *
 * Route: GET /wp-json/lolacore/v1/memory/sync
 * Auth:  X-LolaCore-Signature header with HMAC-SHA256
 */
class SyncEndpoint
{
    /**
     * Custom header name for the HMAC signature.
     */
    private const SIGNATURE_HEADER = 'X-LolaCore-Signature';

    /**
     * Maximum number of facts to return per sync request.
     */
    private const MAX_SYNC_ENTRIES = 100;

    private MemoryManager $memoryManager;

    public function __construct(?MemoryManager $memoryManager = null)
    {
        $this->memoryManager = $memoryManager ?? new MemoryManager();
    }

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

    /**
     * Register the /memory/sync route.
     */
    public function registerRoute(): void
    {
        \register_rest_route('lolacore/v1', '/memory/sync', [
            'methods'             => \WP_REST_Server::READABLE,
            'callback'            => [$this, 'handle'],
            'permission_callback' => [$this, 'validateSignature'],
        ]);
    }

    /**
     * Validate the HMAC-SHA256 signature from the request header.
     *
     * @param WP_REST_Request $request The incoming request.
     * @return bool|WP_Error True if valid, WP_Error otherwise.
     */
    public function validateSignature(WP_REST_Request $request): bool|WP_Error
    {
        // Ensure the node is connected to a hub
        $connectionStatus = $this->getConfigValue('hub_connection_status', 'disconnected');
        if ($connectionStatus !== 'connected') {
            return new WP_Error(
                'lolacore_not_connected',
                'This node is not connected to a Hub.',
                ['status' => 403]
            );
        }

        $signature = $request->get_header('X_LolaCore_Signature');

        if (empty($signature)) {
            return new WP_Error(
                'lolacore_missing_signature',
                'Missing HMAC signature header.',
                ['status' => 401]
            );
        }

        // Retrieve the shared secret
        $hubSiteToken = $this->getConfigValue('hub_site_token', '');
        if (empty($hubSiteToken)) {
            return new WP_Error(
                'lolacore_no_token',
                'Hub site token not configured.',
                ['status' => 500]
            );
        }

        // Build the payload to sign: the full request URL (canonical form)
        $requestUrl = $request->get_route();
        $params     = $request->get_query_params();
        ksort($params);
        $payload = $requestUrl . '?' . http_build_query($params);

        // Calculate expected signature
        $expectedSignature = hash_hmac('sha256', $payload, $hubSiteToken);

        // Timing-safe comparison
        if (!hash_equals($expectedSignature, $signature)) {
            return new WP_Error(
                'lolacore_invalid_signature',
                'Invalid HMAC signature.',
                ['status' => 403]
            );
        }

        return true;
    }

    /**
     * Handle the sync request.
     *
     * Returns active memory facts for Hub vector indexing.
     * No sensitive data (API keys, passwords, PII) is ever included.
     *
     * @param WP_REST_Request $request The incoming request.
     * @return WP_REST_Response
     */
    public function handle(WP_REST_Request $request): WP_REST_Response
    {
        $nodeId = $this->getConfigValue('hub_node_id', '');
        $facts  = $this->memoryManager->getRelevantFacts(self::MAX_SYNC_ENTRIES);

        // Shape the entries for Hub consumption (strip internal fields)
        $entries = array_map(function (array $fact): array {
            return [
                'id'              => (int) $fact['id'],
                'fact_type'       => $fact['fact_type'],
                'content'         => $fact['content'],
                'structured_data' => $fact['structured_data'] ?? null,
                'importance'      => (int) $fact['importance'],
                'decay_score'     => (float) $fact['decay_score'],
                'source'          => $fact['source'],
                'created_at'      => $fact['created_at'],
                'updated_at'      => $fact['updated_at'],
            ];
        }, $facts);

        // Update last sync timestamp
        $this->setConfigValue('hub_last_sync', \current_time('mysql'));

        return new WP_REST_Response([
            'node_id'   => $nodeId,
            'entries'   => $entries,
            'count'     => count($entries),
            'last_sync' => \current_time('mysql'),
        ], 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,
            )
        );
    }
}