<?php

/**
 * Endpoint REST API pour la memoire apprenants
 *
 * POST /wp-json/slidemind/v2/memory/get
 * POST /wp-json/slidemind/v2/memory/set
 * POST /wp-json/slidemind/v2/memory/delete
 *
 * @package SlideMind\API
 * @since 2.1.0
 */
namespace SlideMind\API;

use SlideMind\Core\ConfigRepository;
use SlideMind\Core\FeatureGate;
use SlideMind\Core\LearnerMemory;
use SlideMind\Core\PromptEngine;
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
class MemoryEndpoint {
    /**
     * Namespace de l'API
     * @var string
     */
    private $namespace = 'slidemind/v2';

    /**
     * Repository des configurations
     * @var ConfigRepository
     */
    private $config_repository;

    /**
     * Gestionnaire de memoire apprenants
     * @var LearnerMemory
     */
    private $learner_memory;

    /**
     * Limite de requetes par minute
     */
    private const RATE_LIMIT_MAX = 60;

    /**
     * TTL du rate limit en secondes
     */
    private const RATE_LIMIT_TTL = 60;

    /**
     * Constructeur
     *
     * @param ConfigRepository $config_repository
     * @param LearnerMemory    $learner_memory
     */
    public function __construct( ConfigRepository $config_repository, LearnerMemory $learner_memory ) {
        $this->config_repository = $config_repository;
        $this->learner_memory = $learner_memory;
    }

    /**
     * Enregistre les routes REST
     */
    public function register_routes() {
        // Public: called by e-learning modules in iframes; identity validated internally.
        register_rest_route( $this->namespace, '/memory/get', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_get'],
            'permission_callback' => [$this, 'check_nonce_permission'],
            'args'                => $this->get_common_args( [
                'keys' => [
                    'required'          => false,
                    'type'              => 'array',
                    'description'       => 'Cles a recuperer (toutes si omis)',
                    'validate_callback' => function ( $value ) {
                        if ( !is_array( $value ) ) {
                            return new \WP_Error('invalid_keys', __( 'The keys parameter must be an array', 'slidemind' ), [
                                'status' => 400,
                            ]);
                        }
                        foreach ( $value as $item ) {
                            if ( !is_string( $item ) ) {
                                return new \WP_Error('invalid_keys', __( 'Each key must be a string', 'slidemind' ), [
                                    'status' => 400,
                                ]);
                            }
                        }
                        return true;
                    },
                ],
            ] ),
        ] );
        // Public: called by e-learning modules in iframes; identity validated internally.
        register_rest_route( $this->namespace, '/memory/set', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_set'],
            'permission_callback' => [$this, 'check_nonce_permission'],
            'args'                => $this->get_common_args( [
                'variables' => [
                    'required'          => true,
                    'type'              => 'object',
                    'description'       => 'Variables a sauvegarder (nom => valeur)',
                    'validate_callback' => function ( $value ) {
                        if ( !is_array( $value ) || empty( $value ) ) {
                            return new \WP_Error('invalid_variables', __( 'The variables parameter must be a non-empty object', 'slidemind' ), [
                                'status' => 400,
                            ]);
                        }
                        return true;
                    },
                ],
            ] ),
        ] );
        // Public: called by e-learning modules in iframes; identity validated internally.
        register_rest_route( $this->namespace, '/memory/delete', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_delete'],
            'permission_callback' => [$this, 'check_nonce_permission'],
            'args'                => $this->get_common_args( [
                'keys' => [
                    'required'          => true,
                    'type'              => 'array',
                    'description'       => 'Cles a supprimer',
                    'validate_callback' => function ( $value ) {
                        if ( !is_array( $value ) || empty( $value ) ) {
                            return new \WP_Error('invalid_keys', __( 'The keys parameter must be a non-empty array', 'slidemind' ), [
                                'status' => 400,
                            ]);
                        }
                        foreach ( $value as $item ) {
                            if ( !is_string( $item ) ) {
                                return new \WP_Error('invalid_keys', __( 'Each key must be a string', 'slidemind' ), [
                                    'status' => 400,
                                ]);
                            }
                        }
                        return true;
                    },
                ],
            ] ),
        ] );
        // Public: called by e-learning modules in iframes; identity validated internally.
        // Runtime field filtering (2A.9) handles plan-based restrictions.
        register_rest_route( $this->namespace, '/profile/get', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_profile_get'],
            'permission_callback' => [$this, 'check_nonce_permission'],
            'args'                => [
                'configId'  => [
                    'required'          => true,
                    'type'              => 'string',
                    'description'       => 'Identifiant de la configuration',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
                'learnerId' => [
                    'required'          => false,
                    'type'              => 'integer',
                    'description'       => 'Ignored — identity is derived server-side (kept for client compatibility)',
                    'sanitize_callback' => 'absint',
                ],
                'keys'      => [
                    'required'          => false,
                    'type'              => 'array',
                    'description'       => 'Cles profil a recuperer (toutes les autorisees si omis)',
                    'validate_callback' => function ( $value ) {
                        if ( !is_array( $value ) ) {
                            return new \WP_Error('invalid_keys', __( 'The keys parameter must be an array', 'slidemind' ), [
                                'status' => 400,
                            ]);
                        }
                        foreach ( $value as $item ) {
                            if ( !is_string( $item ) ) {
                                return new \WP_Error('invalid_keys', __( 'Each key must be a string', 'slidemind' ), [
                                    'status' => 400,
                                ]);
                            }
                        }
                        return true;
                    },
                ],
            ],
        ] );
    }

    /**
     * Retourne les arguments communs aux 3 endpoints, fusionnes avec les args specifiques
     *
     * @param array $specific_args Arguments specifiques a l'endpoint
     * @return array
     */
    private function get_common_args( array $specific_args = [] ) : array {
        $common = [
            'configId'     => [
                'required'          => true,
                'type'              => 'string',
                'description'       => 'Identifiant de la configuration',
                'sanitize_callback' => 'sanitize_text_field',
            ],
            'learnerId'    => [
                'required'          => false,
                'type'              => 'integer',
                'description'       => 'ID WordPress de l\'apprenant',
                'sanitize_callback' => 'absint',
            ],
            'sessionId'    => [
                'required'          => false,
                'type'              => 'string',
                'description'       => 'UUID v4 de session anonyme (si pas de learnerId)',
                'sanitize_callback' => 'sanitize_text_field',
                'validate_callback' => function ( $value ) {
                    if ( empty( $value ) ) {
                        return true;
                    }
                    if ( !\SlideMind\Core\LearnerMemory::is_valid_session_id( $value ) ) {
                        return new \WP_Error('invalid_session_id', __( 'The sessionId must be a valid UUID v4', 'slidemind' ), [
                            'status' => 400,
                        ]);
                    }
                    return true;
                },
            ],
            'sessionToken' => [
                'required'          => false,
                'type'              => 'string',
                'description'       => 'HMAC signature of the sessionId, issued by /bootstrap',
                'sanitize_callback' => 'sanitize_text_field',
            ],
            'scope'        => [
                'required'          => true,
                'type'              => 'string',
                'description'       => 'Portee de la variable (global, course, module)',
                'sanitize_callback' => 'sanitize_text_field',
                'validate_callback' => function ( $value ) {
                    // Accept 'formation' as backward-compat alias for 'course' (deployed e-learning modules)
                    if ( !in_array( $value, [
                        'global',
                        'course',
                        'formation',
                        'module'
                    ], true ) ) {
                        return new \WP_Error('invalid_scope', __( 'The scope must be global, course, or module', 'slidemind' ), [
                            'status' => 400,
                        ]);
                    }
                    return true;
                },
            ],
        ];
        return array_merge( $common, $specific_args );
    }

    /**
     * Valide config, publication, type, identite (user ou session) et rate limit.
     *
     * Logique commune partagee par resolve_request() et handle_profile_get().
     *
     * @param string $config_id  Identifiant de configuration
     * @param int    $learner_id ID WordPress de l'apprenant (0 si anonyme)
     * @param string $session_id UUID v4 de session anonyme (vide si authentifie)
     * @return array|\WP_Error ['config' => array, 'user_id' => int, 'session_id' => string] ou erreur
     */
    private function validate_config_and_identity( string $config_id, int $learner_id, string $session_id = '' ) {
        // 1. Config existe ?
        $config = $this->config_repository->get_by_config_uid( $config_id );
        if ( !$config ) {
            return new \WP_Error('config_not_found', __( 'Configuration not found for this identifier', 'slidemind' ), [
                'status' => 404,
            ]);
        }
        // 2. Config publiee ?
        if ( $config['status'] !== 'publish' ) {
            return new \WP_Error('config_not_published', __( 'This configuration is not active', 'slidemind' ), [
                'status' => 403,
            ]);
        }
        // 3. Config v2 ? (protection contre configs legacy v1 sans config_type)
        $config_type = get_post_meta( $config['id'], '_slidemind_config_type', true );
        if ( !in_array( $config_type, [
            'course',
            'module',
            'slide',
            'memory_bridge'
        ], true ) ) {
            return new \WP_Error('invalid_config_type', __( 'This configuration is not compatible with learner memory', 'slidemind' ), [
                'status' => 400,
            ]);
        }
        // 4. Valider identite : user authentifie OU session anonyme
        if ( !empty( $learner_id ) ) {
            $user = get_userdata( $learner_id );
            if ( !$user ) {
                return new \WP_Error('user_not_found', __( 'No WordPress user associated with this identifier', 'slidemind' ), [
                    'status' => 404,
                ]);
            }
            $session_id = '';
            // Authenticated users don't use session_id
        } elseif ( !empty( $session_id ) ) {
            // Anonymous session mode — learner_id stays 0
            $learner_id = 0;
        } else {
            return new \WP_Error('identity_required', __( 'A learnerId or sessionId is required', 'slidemind' ), [
                'status' => 400,
            ]);
        }
        // 5. Rate limit dual : 60 req/min authentifies, 20 req/min anonymes
        $rate_key = ( $learner_id > 0 ? $learner_id : $session_id );
        $rate_check = $this->check_rate_limit( $rate_key );
        if ( is_wp_error( $rate_check ) ) {
            return $rate_check;
        }
        return [
            'config'     => $config,
            'user_id'    => $learner_id,
            'session_id' => $session_id,
        ];
    }

    /**
     * Validation commune aux handlers memory (get/set/delete).
     *
     * Verifie config, publication, type, user, rate limit et scope.
     *
     * @param \WP_REST_Request $request
     * @return array|\WP_Error Contexte resolu ou erreur
     */
    private function resolve_request( $request ) {
        $config_id = $request->get_param( 'configId' );
        $scope = $request->get_param( 'scope' );
        // Identity is derived server-side, never from the request body.
        $identity = \SlideMind\Core\LearnerIdentity::resolve( $request );
        if ( is_wp_error( $identity ) ) {
            return $identity;
        }
        // Backward compat: accept 'formation' as alias for 'course' (deployed e-learning modules)
        if ( $scope === 'formation' ) {
            $scope = 'course';
        }
        $base = $this->validate_config_and_identity( $config_id, $identity['user_id'], $identity['session_id'] );
        if ( is_wp_error( $base ) ) {
            return $base;
        }
        // Resoudre scope_id
        $config_type = get_post_meta( $base['config']['id'], '_slidemind_config_type', true );
        $scope_id = $this->resolve_scope_id( $base['config']['id'], $config_type, $scope );
        if ( is_wp_error( $scope_id ) ) {
            return $scope_id;
        }
        return [
            'config'     => $base['config'],
            'user_id'    => $base['user_id'],
            'session_id' => $base['session_id'],
            'scope'      => $scope,
            'scope_id'   => $scope_id,
        ];
    }

    /**
     * Resout le scope_id a partir du post_id et du config_type
     *
     * @param int    $post_id     ID du post de configuration
     * @param string $config_type Type de configuration (course, module, slide)
     * @param string $scope       Portee demandee (global, course, module)
     * @return int|\WP_Error scope_id resolu ou erreur
     */
    private function resolve_scope_id( int $post_id, string $config_type, string $scope ) {
        if ( $scope === 'global' ) {
            return 0;
        }
        if ( $scope === 'module' ) {
            if ( $config_type === 'module' ) {
                return $post_id;
            }
            // Slide -> parent module
            $module_id = (int) get_post_meta( $post_id, '_slidemind_parent_module_id', true );
            if ( !$module_id ) {
                return new \WP_Error('no_parent_module', __( 'Cannot determine the parent module for this configuration', 'slidemind' ), [
                    'status' => 400,
                ]);
            }
            return $module_id;
        }
        if ( $scope === 'course' ) {
            if ( $config_type === 'course' ) {
                return $post_id;
            }
            // Module -> parent course
            if ( $config_type === 'module' ) {
                $course_id = (int) get_post_meta( $post_id, '_slidemind_parent_course_id', true );
            } else {
                // Slide: try direct course link first
                $course_id = (int) get_post_meta( $post_id, '_slidemind_parent_course_id', true );
                // Fall back: slide -> module -> course
                if ( !$course_id ) {
                    $module_id = (int) get_post_meta( $post_id, '_slidemind_parent_module_id', true );
                    if ( !$module_id ) {
                        return new \WP_Error('no_parent_course', __( 'Cannot determine the parent course for this configuration', 'slidemind' ), [
                            'status' => 400,
                        ]);
                    }
                    $course_id = (int) get_post_meta( $module_id, '_slidemind_parent_course_id', true );
                }
            }
            if ( !$course_id ) {
                return new \WP_Error('no_parent_course', __( 'Cannot determine the parent course for this configuration', 'slidemind' ), [
                    'status' => 400,
                ]);
            }
            return $course_id;
        }
        return new \WP_Error('invalid_scope', __( 'The scope must be global, course, or module', 'slidemind' ), [
            'status' => 400,
        ]);
    }

    /**
     * Permission callback shared by all nonce-protected endpoints.
     *
     * Verifies the X-WP-Nonce issued by /bootstrap, then re-validates the
     * configuration's guest-access policy for anonymous callers on every
     * request — a nonce or session obtained via /bootstrap for one
     * configuration cannot be replayed against a configuration that does
     * not allow guests.
     *
     * @param \WP_REST_Request $request
     * @return bool|\WP_Error
     */
    public function check_nonce_permission( $request ) {
        $nonce = $request->get_header( 'x_wp_nonce' );
        if ( empty( $nonce ) || !wp_verify_nonce( $nonce, 'wp_rest' ) ) {
            return new \WP_Error('rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid. Call /bootstrap first.', 'slidemind' ), [
                'status' => 401,
            ]);
        }
        if ( !is_user_logged_in() ) {
            $config_uid = (string) $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 ( !\SlideMind\Core\ConfigAccess::guest_allowed( (int) $config['id'] ) ) {
                return new \WP_Error('guest_access_denied', __( 'Guest access is not allowed for this configuration.', 'slidemind' ), [
                    'status' => 403,
                ]);
            }
        }
        return true;
    }

    /**
     * Check rate limit by identity + IP, split by authentication status.
     *
     * Authenticated: 60 req/min (class constant).
     * Anonymous: 20 req/min.
     *
     * @param int|string $identity Identity (user_id or session_id)
     * @return true|\WP_Error
     */
    private function check_rate_limit( $identity ) {
        $is_auth = is_user_logged_in();
        $max = ( $is_auth ? self::RATE_LIMIT_MAX : 20 );
        $ip = ( isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '0.0.0.0' );
        $bucket = ( $is_auth ? 'auth' : 'anon' );
        $key = 'slidemind_rl_mem_' . $bucket . '_' . md5( $identity . $ip );
        $current = (int) get_transient( $key );
        if ( $current >= $max ) {
            return new \WP_Error('rate_limit', __( 'Too many requests. Please wait before trying again.', 'slidemind' ), [
                'status' => 429,
            ]);
        }
        set_transient( $key, $current + 1, self::RATE_LIMIT_TTL );
        return true;
    }

    /**
     * Handler GET : recupere des variables de memoire
     *
     * @param \WP_REST_Request $request
     * @return \WP_REST_Response|\WP_Error
     */
    public function handle_get( $request ) {
        $ctx = $this->resolve_request( $request );
        if ( is_wp_error( $ctx ) ) {
            return $ctx;
        }
        $keys = $request->get_param( 'keys' );
        $data = $this->learner_memory->get(
            $ctx['user_id'],
            $ctx['scope'],
            $ctx['scope_id'],
            $keys,
            $ctx['session_id']
        );
        $response = new \WP_REST_Response([
            'success' => true,
            'data'    => $data,
        ], 200);
        $response->header( 'Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0' );
        $response->header( 'Pragma', 'no-cache' );
        return $response;
    }

    /**
     * Handler SET : sauvegarde des variables de memoire
     *
     * @param \WP_REST_Request $request
     * @return \WP_REST_Response|\WP_Error
     */
    public function handle_set( $request ) {
        $ctx = $this->resolve_request( $request );
        if ( is_wp_error( $ctx ) ) {
            return $ctx;
        }
        // Scope check — free: global only; Pro (premium zip): all scopes. The
        // premium block widens the scopes and supplies plan-aware messaging; in
        // free it is stripped, leaving the neutral default error.
        $allowed_scopes = ['global'];
        $scope_error_msg = __( 'This memory scope is not available.', 'slidemind' );
        if ( !in_array( $ctx['scope'], $allowed_scopes, true ) ) {
            return new \WP_Error('scope_restricted', $scope_error_msg, [
                'status' => 403,
            ]);
        }
        $variables = $request->get_param( 'variables' );
        $saved = $this->learner_memory->set(
            $ctx['user_id'],
            $ctx['scope'],
            $ctx['scope_id'],
            $variables,
            $ctx['session_id']
        );
        if ( $saved === 0 ) {
            return new \WP_Error('save_failed', __( 'No variable saved. Please check that the names are valid (lowercase, letters/digits/underscores, starts with a letter).', 'slidemind' ), [
                'status' => 400,
            ]);
        }
        $response = new \WP_REST_Response([
            'success' => true,
            'data'    => [
                'saved' => $saved,
            ],
        ], 200);
        $response->header( 'Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0' );
        $response->header( 'Pragma', 'no-cache' );
        return $response;
    }

    /**
     * Handler DELETE : supprime des variables de memoire
     *
     * @param \WP_REST_Request $request
     * @return \WP_REST_Response|\WP_Error
     */
    public function handle_delete( $request ) {
        $ctx = $this->resolve_request( $request );
        if ( is_wp_error( $ctx ) ) {
            return $ctx;
        }
        $keys = $request->get_param( 'keys' );
        $this->learner_memory->delete(
            $ctx['user_id'],
            $ctx['scope'],
            $ctx['scope_id'],
            $keys,
            $ctx['session_id']
        );
        // Toujours 200, idempotent
        $response = new \WP_REST_Response([
            'success' => true,
            'data'    => [
                'deleted' => count( $keys ),
            ],
        ], 200);
        $response->header( 'Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0' );
        $response->header( 'Pragma', 'no-cache' );
        return $response;
    }

    /**
     * Handler PROFILE GET : recupere les champs profil d'un apprenant
     *
     * Les variables profil sont resolues a la volee (pas stockees en DB).
     * Utilise PromptEngine::resolve_profile_field_value() pour la resolution.
     *
     * @param \WP_REST_Request $request
     * @return \WP_REST_Response|\WP_Error
     */
    public function handle_profile_get( $request ) {
        $config_id = $request->get_param( 'configId' );
        $keys = $request->get_param( 'keys' );
        // Identity is derived server-side. Profile fields are only available
        // to authenticated users (a guest session has no WP profile).
        $identity = \SlideMind\Core\LearnerIdentity::resolve( $request );
        if ( is_wp_error( $identity ) ) {
            return $identity;
        }
        $learner_id = $identity['user_id'];
        if ( $learner_id <= 0 ) {
            return new \WP_Error('profile_requires_auth', __( 'Profile fields require an authenticated learner.', 'slidemind' ), [
                'status' => 403,
            ]);
        }
        // Validate config, publication, type, user, rate limit
        $ctx = $this->validate_config_and_identity( $config_id, $learner_id );
        if ( is_wp_error( $ctx ) ) {
            return $ctx;
        }
        // Resolve allowed profile fields
        $settings = get_option( 'slidemind_settings', [] );
        $allowed = $settings['allowed_profile_fields'] ?? [];
        // Free: 5 WP native fields only. Premium with Memory axis: all configured fields.
        $is_native_only = true;
        if ( $is_native_only ) {
            $native_keys = [
                'display_name',
                'email',
                'first_name',
                'last_name',
                'role'
            ];
            $allowed = array_values( array_intersect( $allowed, $native_keys ) );
        }
        $aliases = $settings['profile_field_aliases'] ?? [];
        // Build reverse alias map: alias => original_key
        $alias_to_real = [];
        foreach ( $aliases as $original_key => $alias ) {
            if ( !empty( $alias ) ) {
                $alias_to_real[$alias] = $original_key;
            }
        }
        // Determine which fields to resolve
        $fields_to_resolve = $allowed;
        if ( !empty( $keys ) && is_array( $keys ) ) {
            $fields_to_resolve = [];
            foreach ( $keys as $key ) {
                // Resolve alias to original key if applicable
                $real_key = ( isset( $alias_to_real[$key] ) ? $alias_to_real[$key] : $key );
                if ( in_array( $real_key, $allowed, true ) ) {
                    $fields_to_resolve[] = $real_key;
                }
            }
        }
        // Build alias reverse: original_key => alias (for output keys)
        $real_to_alias = [];
        foreach ( $aliases as $original_key => $alias ) {
            if ( !empty( $alias ) ) {
                $real_to_alias[$original_key] = $alias;
            }
        }
        // Resolve field values
        $data = [];
        foreach ( $fields_to_resolve as $field_key ) {
            $value = PromptEngine::resolve_profile_field_value( $learner_id, $field_key );
            // Use alias as output key if defined, otherwise original key
            $output_key = ( isset( $real_to_alias[$field_key] ) ? $real_to_alias[$field_key] : $field_key );
            $data[$output_key] = ( $value !== null ? sanitize_textarea_field( $value ) : '' );
        }
        $response = new \WP_REST_Response([
            'success' => true,
            'data'    => $data,
        ], 200);
        $response->header( 'Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0' );
        $response->header( 'Pragma', 'no-cache' );
        return $response;
    }

}
