<?php
/**
 * REST API endpoint for auth bootstrap
 *
 * GET /wp-json/slidemind/v2/bootstrap?configId=...
 *
 * Validates access to a configuration and returns a fresh `wp_rest` nonce
 * plus the resolved learner identity (learnerId for logged-in users,
 * sessionId for allowed guests). All subsequent write endpoints
 * (chat, memory/*, profile/get) require the nonce in the X-WP-Nonce header.
 *
 * @package SlideMind\API
 * @since   0.4.0
 */

namespace SlideMind\API;

use SlideMind\Core\ConfigRepository;

if (!defined('ABSPATH')) {
    exit;
}

class BootstrapEndpoint {

    /**
     * API namespace
     * @var string
     */
    private $namespace = 'slidemind/v2';

    /**
     * Configuration repository
     * @var ConfigRepository
     */
    private $config_repository;

    /**
     * Nonce expiry window in seconds (12 hours — matches WP default).
     */
    private const NONCE_EXPIRY_SECONDS = 12 * HOUR_IN_SECONDS;

    /**
     * Constructor
     *
     * @param ConfigRepository $config_repository
     */
    public function __construct(ConfigRepository $config_repository) {
        $this->config_repository = $config_repository;
    }

    /**
     * Register REST routes
     */
    public function register_routes() {
        register_rest_route($this->namespace, '/bootstrap', [
            'methods'             => 'GET',
            'callback'            => [$this, 'handle'],
            'permission_callback' => [$this, 'check_permission'],
            'args'                => [
                'configId' => [
                    'required'          => true,
                    'type'              => 'string',
                    'description'       => 'Configuration UID',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
            ],
        ]);
    }

    /**
     * Permission callback — validates access to the configuration.
     *
     * Logged-in users: always allowed.
     * Anonymous: per-slide `_sm_allow_guests` meta (inherit|allow|deny) with
     * global `slidemind_settings['allow_guests']` fallback (default false).
     *
     * @param \WP_REST_Request $request
     * @return bool|\WP_Error
     */
    public function check_permission($request) {
        $config_uid = $request->get_param('configId');
        $config     = $this->config_repository->get_by_config_uid($config_uid);

        if (!$config) {
            return new \WP_Error(
                'config_not_found',
                __('Configuration not found for this identifier', 'slidemind'),
                ['status' => 404]
            );
        }

        if ($this->resolve_logged_in_user_id() > 0) {
            return true;
        }

        // Guest access — per-slide meta overrides global setting.
        // Meta key uses the new `_sm_` prefix (see CLAUDE.md "Meta prefix") — do not
        // rename to the legacy `_slidemind_` convention used by pre-existing meta.
        $slide_meta = get_post_meta($config['id'], '_sm_allow_guests', true);
        if ($slide_meta === 'allow') {
            return true;
        }
        if ($slide_meta === 'deny') {
            return false;
        }

        // 'inherit' (or empty) → fall back to global.
        $settings = get_option('slidemind_settings', []);
        return !empty($settings['allow_guests']);
    }

    /**
     * Handler — returns nonce + identity for a validated bootstrap request.
     *
     * @param \WP_REST_Request $request
     * @return \WP_REST_Response
     */
    public function handle($request) {
        $config_uid = $request->get_param('configId');
        $user_id    = $this->resolve_logged_in_user_id();

        // Populate the REST-scoped current user so wp_create_nonce('wp_rest')
        // binds the nonce to this user. Without this, the nonce would be
        // tied to uid=0 (from WP's REST cookie callback that skips auth
        // when no X-WP-Nonce is present — our chicken-and-egg case).
        if ($user_id > 0) {
            wp_set_current_user($user_id);
        }

        $data = [
            'nonce'    => wp_create_nonce('wp_rest'),
            'configId' => $config_uid,
            'expiry'   => time() + self::NONCE_EXPIRY_SECONDS,
        ];

        if ($user_id > 0) {
            $data['learnerId']    = $user_id;
            $data['sessionId']    = null;
            $data['sessionToken'] = null;
        } else {
            $session_id           = $this->issue_session_id();
            $data['learnerId']    = null;
            $data['sessionId']    = $session_id;
            $data['sessionToken'] = \SlideMind\Core\LearnerIdentity::sign_session_id($session_id);
        }

        $response = new \WP_REST_Response($data, 200);
        $response->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
        $response->header('Pragma', 'no-cache');

        return $response;
    }

    /**
     * Resolve the current user ID from the WP auth cookie.
     *
     * REST requests without X-WP-Nonce don't set current_user via cookie
     * (WP's rest_cookie_callback bails). Bootstrap is the endpoint that
     * issues that nonce, so we must validate the cookie ourselves here.
     *
     * @return int User ID, or 0 if not authenticated.
     */
    private function resolve_logged_in_user_id(): int {
        $uid = get_current_user_id();
        if ($uid > 0) {
            return $uid;
        }

        // Look up the logged_in cookie directly (hash varies per site).
        foreach ($_COOKIE as $name => $value) {
            if (strpos($name, 'wordpress_logged_in_') === 0) {
                // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- wp_validate_auth_cookie handles validation.
                $cookie_uid = wp_validate_auth_cookie(wp_unslash($value), 'logged_in');
                if ($cookie_uid) {
                    return (int) $cookie_uid;
                }
            }
        }

        return 0;
    }

    /**
     * Generate a cryptographically random UUID v4 for anonymous sessions.
     *
     * @return string
     */
    private function issue_session_id(): string {
        $data    = random_bytes(16);
        $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // version 4
        $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // variant 10
        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
    }
}
