<?php

/**
 * REST API endpoint for chat v2
 *
 * POST /wp-json/slidemind/v2/chat
 *
 * @package SlideMind
 * @since 2.0.0
 */
namespace SlideMind\API;

use SlideMind\Core\ConfigRepository;
use SlideMind\Core\LLMProxy;
use SlideMind\Core\LearnerMemory;
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
class ChatEndpoint {
    private const DEFAULT_RATE_LIMIT = 60;

    private const RATE_LIMIT_TTL = 60;

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

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

    /**
     * LLM proxy
     * @var LLMProxy
     */
    private $llm_proxy;

    /**
     * Learner memory (optional)
     * @var LearnerMemory|null
     */
    private $learner_memory;

    /**
     * Constructor
     *
     * @param ConfigRepository   $config_repository
     * @param LLMProxy           $llm_proxy
     * @param LearnerMemory|null $learner_memory
     */
    public function __construct( ConfigRepository $config_repository, LLMProxy $llm_proxy, ?LearnerMemory $learner_memory = null ) {
        $this->config_repository = $config_repository;
        $this->llm_proxy = $llm_proxy;
        $this->learner_memory = $learner_memory;
    }

    /**
     * Register REST routes
     */
    public function register_routes() {
        // Main chat endpoint
        register_rest_route( $this->namespace, '/chat', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_chat'],
            'permission_callback' => [$this, 'check_nonce_permission'],
            'args'                => $this->get_chat_args(),
        ] );
        // Public: called by e-learning modules embedded in iframes to check config availability.
        register_rest_route( $this->namespace, '/config/(?P<id>[a-zA-Z0-9-]+)/status', [
            'methods'             => 'GET',
            'callback'            => [$this, 'handle_status'],
            'permission_callback' => '__return_true',
        ] );
        // Public: deprecated v1 endpoint, returns 410 Gone for legacy modules.
        register_rest_route( 'slidemind/v1', '/config/(?P<id>[a-zA-Z0-9-]+)', [
            'methods'             => 'GET',
            'callback'            => [$this, 'handle_v1_deprecated'],
            'permission_callback' => '__return_true',
        ] );
    }

    /**
     * Define the chat endpoint arguments
     *
     * @return array
     */
    private function get_chat_args() {
        return [
            'configId'     => [
                'required'          => true,
                'type'              => 'string',
                'description'       => 'Configuration identifier',
                'sanitize_callback' => 'sanitize_text_field',
            ],
            'messages'     => [
                'required'          => true,
                'type'              => 'array',
                'description'       => 'Conversation message history',
                'validate_callback' => [$this, 'validate_messages'],
            ],
            'variables'    => [
                'required'    => false,
                'type'        => 'object',
                'description' => 'Variables to inject into the prompt',
                'default'     => [],
            ],
            'learnerId'    => [
                'required'          => false,
                'type'              => 'integer',
                'description'       => 'WordPress user ID of the learner',
                'sanitize_callback' => 'absint',
            ],
            'sessionId'    => [
                'required'          => false,
                'type'              => 'string',
                'description'       => 'Anonymous session UUID v4 (if no 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',
            ],
        ];
    }

    /**
     * Validate message format
     *
     * @param array $messages
     * @return bool|WP_Error
     */
    public function validate_messages( $messages ) {
        if ( !is_array( $messages ) || empty( $messages ) ) {
            return new \WP_Error('invalid_messages', __( 'The messages parameter must be a non-empty array', 'slidemind' ), [
                'status' => 400,
            ]);
        }
        foreach ( $messages as $index => $message ) {
            if ( !isset( $message['role'] ) || !isset( $message['content'] ) ) {
                return new \WP_Error(
                    'invalid_message_format',
                    /* translators: %d: message index in the array */
                    sprintf( __( 'The message at index %d must contain \'role\' and \'content\'', 'slidemind' ), $index ),
                    [
                        'status' => 400,
                    ]
                );
            }
            if ( !in_array( $message['role'], ['user', 'assistant'], true ) ) {
                return new \WP_Error(
                    'invalid_role',
                    /* translators: %s: the invalid role value */
                    sprintf( __( 'The role \'%s\' is not valid. Use \'user\' or \'assistant\'', 'slidemind' ), $message['role'] ),
                    [
                        'status' => 400,
                    ]
                );
            }
        }
        return true;
    }

    /**
     * Handle chat request
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function handle_chat( $request ) {
        $config_id = $request->get_param( 'configId' );
        $messages = $request->get_param( 'messages' );
        $variables = ( $request->get_param( 'variables' ) ?: [] );
        // Retrieve the configuration
        $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,
            ]);
        }
        // Check that the configuration is published
        if ( $config['status'] !== 'publish' ) {
            return new \WP_Error('config_not_published', __( 'This configuration is not active', 'slidemind' ), [
                'status' => 403,
            ]);
        }
        // Rate limit by IP + configId
        $rate_check = $this->check_rate_limit( $config_id );
        if ( is_wp_error( $rate_check ) ) {
            return $rate_check;
        }
        // Resolve learner context from trusted sources only (never from body
        // identity). Soft mode: chat works anonymously with no memory binding.
        $learner_info = null;
        $settings = get_option( 'slidemind_settings', [] );
        if ( $this->learner_memory ) {
            $identity = \SlideMind\Core\LearnerIdentity::resolve( $request, false );
            $resolved_uid = $identity['user_id'];
            $resolved_sid = $identity['session_id'];
            if ( $resolved_uid > 0 ) {
                $scope = $this->resolve_config_scope( $config['id'] );
                $learner_info = [
                    'user_id'                => $resolved_uid,
                    'session_id'             => '',
                    'module_id'              => $scope['module_id'],
                    'course_id'              => $scope['course_id'],
                    'allowed_profile_fields' => $settings['allowed_profile_fields'] ?? [],
                ];
            } elseif ( $resolved_sid !== '' ) {
                $scope = $this->resolve_config_scope( $config['id'] );
                $learner_info = [
                    'user_id'                => 0,
                    'session_id'             => $resolved_sid,
                    'module_id'              => $scope['module_id'],
                    'course_id'              => $scope['course_id'],
                    'allowed_profile_fields' => [],
                ];
            }
        }
        // Resolve inherited course/module variables (course_theme, course_objectives, etc.)
        $inherited = $this->config_repository->get_inherited_content( $config['id'] );
        $inherited_vars = [];
        if ( $inherited['course'] ) {
            if ( !empty( $inherited['course']['theme'] ) ) {
                $inherited_vars['course_theme'] = $inherited['course']['theme'];
            }
            if ( !empty( $inherited['course']['content'] ) ) {
                $inherited_vars['course_content'] = $inherited['course']['content'];
            }
            if ( !empty( $inherited['course']['objectives'] ) ) {
                $inherited_vars['course_objectives'] = $inherited['course']['objectives'];
            }
        }
        if ( $inherited['module'] ) {
            if ( !empty( $inherited['module']['theme'] ) ) {
                $inherited_vars['module_theme'] = $inherited['module']['theme'];
            }
            if ( !empty( $inherited['module']['objectives'] ) ) {
                $inherited_vars['module_objectives'] = $inherited['module']['objectives'];
            }
            if ( !empty( $inherited['module']['content'] ) ) {
                $inherited_vars['module_content'] = $inherited['module']['content'];
            }
        }
        // Client variables override inherited ones
        $variables = array_merge( $inherited_vars, $variables );
        // Graceful close: inject closing instruction at last turn
        $graceful_close = !empty( $config['graceful_close'] ) && $config['graceful_close'] === '1';
        $max_turns = ( isset( $config['max_turns'] ) ? (int) $config['max_turns'] : 0 );
        if ( $graceful_close && $max_turns > 0 ) {
            // Count user messages to determine current turn
            $user_message_count = 0;
            foreach ( $messages as $msg ) {
                if ( isset( $msg['role'] ) && $msg['role'] === 'user' ) {
                    $user_message_count++;
                }
            }
            if ( $user_message_count >= $max_turns ) {
                $close_prompt = ( !empty( $config['graceful_close_prompt'] ) ? $config['graceful_close_prompt'] : __( 'This is the last exchange. Please conclude the conversation naturally while staying in character. Do not mention technical limitations or turn counter.', 'slidemind' ) );
                $config['system_prompt'] .= "\n\n--- INSTRUCTION IMPORTANTE ---\n" . $close_prompt;
            }
        }
        // Preventive markdown strip: instruct LLM to avoid markdown
        $widget_mode = $config['widget_mode'] ?? 'qa';
        $strip_active = ( $widget_mode === 'chat' ? !empty( $settings['strip_markdown_chat'] ) : !empty( $settings['strip_markdown_qa'] ) );
        if ( $strip_active ) {
            $config['system_prompt'] .= "\n\n--- FORMATTING INSTRUCTION ---\n" . 'IMPORTANT: Do NOT use any markdown formatting in your response. ' . 'No bold (**), no italic (*), no headings (#), no bullet points (- or *), ' . 'no code blocks (```), no links ([]()), no blockquotes (>). ' . 'Write in plain text only, using natural sentence structure.';
        }
        // Call the LLM proxy
        $result = $this->llm_proxy->chat(
            $config,
            $messages,
            $variables,
            $learner_info
        );
        if ( !$result['success'] ) {
            return new \WP_Error('llm_error', $result['error'], [
                'status' => 502,
            ]);
        }
        // Curative markdown strip: remove markdown from LLM response
        if ( $strip_active ) {
            // Simple response
            if ( isset( $result['data']['message']['content'] ) ) {
                $result['data']['message']['content'] = $this->strip_markdown( $result['data']['message']['content'] );
            }
            // Structured response: strip only text fields
            if ( !empty( $result['data']['structured_data'] ) ) {
                $output_vars = $config['output_variables'] ?? [];
                foreach ( $output_vars as $var ) {
                    $var_name = $var['name'] ?? '';
                    $var_type = $var['type'] ?? 'texte';
                    if ( $var_type === 'texte' && isset( $result['data']['structured_data'][$var_name] ) ) {
                        $result['data']['structured_data'][$var_name] = $this->strip_markdown( $result['data']['structured_data'][$var_name] );
                    }
                }
            }
        }
        // Auto-save interaction to learner memory
        if ( $learner_info && $this->learner_memory ) {
            $save_vars = [];
            if ( !empty( $config['save_user_input'] ) && $config['save_user_input'] === '1' && !empty( $config['save_input_key'] ) ) {
                $last_user_msg = end( $messages );
                if ( $last_user_msg && isset( $last_user_msg['role'] ) && $last_user_msg['role'] === 'user' ) {
                    $save_vars[$config['save_input_key']] = $last_user_msg['content'];
                }
            }
            if ( !empty( $config['save_ai_response'] ) && $config['save_ai_response'] === '1' && !empty( $config['save_response_key'] ) ) {
                $ai_content = $result['data']['message']['content'] ?? '';
                if ( !empty( $ai_content ) ) {
                    $save_vars[$config['save_response_key']] = $ai_content;
                }
            }
            if ( !empty( $save_vars ) ) {
                $scope = ( !empty( $config['save_interaction_scope'] ) ? $config['save_interaction_scope'] : 'global' );
                // Silent scope check — free: global only; Pro (premium zip): all scopes. Clear vars if scope not allowed (must not break chat).
                $allowed_scopes = ['global'];
                if ( !in_array( $scope, $allowed_scopes, true ) ) {
                    $save_vars = [];
                }
                if ( !empty( $save_vars ) ) {
                    $scope_id = $this->resolve_auto_save_scope_id( $config, $scope );
                    if ( !is_wp_error( $scope_id ) ) {
                        $this->learner_memory->set(
                            $learner_info['user_id'],
                            $scope,
                            $scope_id,
                            $save_vars,
                            $learner_info['session_id'] ?? ''
                        );
                    }
                }
            }
        }
        // Build the response
        $response_data = [
            'success' => true,
        ];
        // Structured mode (output variables defined) vs simple mode
        if ( isset( $result['data']['structured_data'] ) && !empty( $result['data']['structured_data'] ) ) {
            $response_data['structured'] = !empty( $result['data']['structured'] );
            $response_data['data'] = $result['data']['structured_data'];
            // Include original message for debug if parsing failed
            if ( empty( $result['data']['structured'] ) ) {
                $response_data['message'] = $result['data']['message'];
            }
        } else {
            $response_data['message'] = $result['data']['message'];
        }
        $response_data['usage'] = $result['data']['usage'];
        $response_data['meta'] = [
            'model'         => $result['data']['model'],
            'response_time' => $result['data']['response_time'],
        ];
        $response = new \WP_REST_Response($response_data, 200);
        // Anti-cache headers
        $response->header( 'Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0' );
        $response->header( 'Pragma', 'no-cache' );
        return $response;
    }

    /**
     * Handle configuration status check
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function handle_status( $request ) {
        $config_id = $request->get_param( 'id' );
        $config = $this->config_repository->get_by_config_uid( $config_id );
        if ( !$config ) {
            return new \WP_REST_Response([
                'exists' => false,
                'active' => false,
            ], 200);
        }
        return new \WP_REST_Response([
            'exists' => true,
            'active' => $config['status'] === 'publish',
            'model'  => $config['model_id'],
            'title'  => $config['title'],
        ], 200);
    }

    /**
     * Check rate limit by IP + configId, split by authentication status.
     *
     * Authenticated: 60 req/min (configurable via chat_rate_limit).
     * Anonymous: 20 req/min (hardcoded stricter cap).
     *
     * @param string $config_id Configuration identifier
     * @return true|\WP_Error
     */
    private function check_rate_limit( string $config_id ) {
        $settings = get_option( 'slidemind_settings', [] );
        $is_auth = is_user_logged_in();
        $max = ( $is_auth ? ( isset( $settings['chat_rate_limit'] ) ? absint( $settings['chat_rate_limit'] ) : self::DEFAULT_RATE_LIMIT ) : 20 );
        // 0 = disabled (authenticated only — anon is always capped)
        if ( $is_auth && $max === 0 ) {
            return true;
        }
        $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_chat_' . $bucket . '_' . md5( $ip . $config_id );
        $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;
    }

    /**
     * Resolve scope_id for interaction auto-save
     *
     * @param array $config The full configuration
     * @param string $scope The requested scope (global, course, module)
     * @return int|\WP_Error
     */
    private function resolve_auto_save_scope_id( array $config, string $scope ) {
        if ( $scope === 'global' ) {
            return 0;
        }
        $post_id = $config['id'];
        if ( $scope === 'module' ) {
            $module_id = (int) get_post_meta( $post_id, '_slidemind_parent_module_id', true );
            return ( $module_id ?: 0 );
            // Fallback to global if no parent
        }
        // Accept both 'course' and 'formation' (backward compat for deployed e-learning modules)
        if ( $scope === 'course' || $scope === 'formation' ) {
            // Try direct course link first (course-level slide)
            $course_id = (int) get_post_meta( $post_id, '_slidemind_parent_course_id', true );
            if ( $course_id ) {
                return $course_id;
            }
            // Fall back: module -> course
            $module_id = (int) get_post_meta( $post_id, '_slidemind_parent_module_id', true );
            if ( $module_id ) {
                $course_id = (int) get_post_meta( $module_id, '_slidemind_parent_course_id', true );
                return ( $course_id ?: 0 );
            }
            return 0;
        }
        return 0;
    }

    /**
     * Resolve module_id and course_id from a configuration
     *
     * NOTE: Duplicated in ChatWidget::resolve_config_scope().
     * Keep in sync until a third caller justifies extracting to a shared helper.
     *
     * @param int $post_id Configuration post ID
     * @return array ['module_id' => ?int, 'course_id' => ?int]
     */
    private function resolve_config_scope( int $post_id ) : array {
        $module_id = ( (int) get_post_meta( $post_id, '_slidemind_parent_module_id', true ) ?: null );
        if ( $module_id && get_post_status( $module_id ) === false ) {
            $module_id = null;
        }
        $course_id = ( (int) get_post_meta( $post_id, '_slidemind_parent_course_id', true ) ?: null );
        if ( !$course_id && $module_id ) {
            $course_id = ( (int) get_post_meta( $module_id, '_slidemind_parent_course_id', true ) ?: null );
        }
        if ( $course_id && get_post_status( $course_id ) === false ) {
            $course_id = null;
        }
        return [
            'module_id' => $module_id,
            'course_id' => $course_id,
        ];
    }

    /**
     * Handle deprecated v1 requests
     *
     * @param WP_REST_Request $request
     * @return WP_Error
     */
    public function handle_v1_deprecated( $request ) {
        return new \WP_Error('endpoint_deprecated', __( 'This endpoint is deprecated. Please use POST /wp-json/slidemind/v2/chat. Consult the documentation to migrate to the new secure API.', 'slidemind' ), [
            'status' => 410,
        ]);
    }

    /**
     * Strips markdown formatting from text, returning plain text.
     *
     * @param string $text Text potentially containing markdown
     * @return string Plain text without markdown formatting
     */
    private function strip_markdown( string $text ) : string {
        // 1. Fenced code blocks: keep content
        $text = preg_replace( '/```[a-z]*\\n([\\s\\S]*?)\\n?```/m', '$1', $text ) ?? $text;
        $text = preg_replace( '/```[a-z]*\\n?/m', '', $text ) ?? $text;
        // 2. Inline code: keep content
        $text = preg_replace( '/`([^`]+)`/', '$1', $text ) ?? $text;
        // 3. Images ![alt](url) → alt
        $text = preg_replace( '/!\\[([^\\]]*)\\]\\([^)]+\\)/', '$1', $text ) ?? $text;
        // 4. Links [text](url) → text
        $text = preg_replace( '/\\[([^\\]]+)\\]\\([^)]+\\)/', '$1', $text ) ?? $text;
        // 5. Horizontal rules (---, ***, ___) — must run before emphasis rules
        $text = preg_replace( '/^[-*_]{3,}\\s*$/m', '', $text ) ?? $text;
        // 6. Bold+italic ***text*** or ___text___
        $text = preg_replace( '/\\*{3}(.+?)\\*{3}/', '$1', $text ) ?? $text;
        $text = preg_replace( '/_{3}(.+?)_{3}/', '$1', $text ) ?? $text;
        // 7. Bold **text** or __text__
        $text = preg_replace( '/\\*{2}(.+?)\\*{2}/', '$1', $text ) ?? $text;
        $text = preg_replace( '/_{2}(.+?)_{2}/', '$1', $text ) ?? $text;
        // 8. Italic *text* (not bullet * item) and _text_ (not snake_case)
        $text = preg_replace( '/(?<!\\*)\\*(?!\\s)(.+?)(?<!\\s)\\*(?!\\*)/', '$1', $text ) ?? $text;
        $text = preg_replace( '/(?<!\\w)_(?!\\s)(.+?)(?<!\\s)_(?!\\w)/', '$1', $text ) ?? $text;
        // 9. Strikethrough ~~text~~
        $text = preg_replace( '/~~(.+?)~~/', '$1', $text ) ?? $text;
        // 10. Headings: remove leading #
        $text = preg_replace( '/^#{1,6}\\s+/m', '', $text ) ?? $text;
        // 11. Blockquotes: remove leading >
        $text = preg_replace( '/^>\\s?/m', '', $text ) ?? $text;
        // 12. List markers (-, *, +, 1.) at start of line
        $text = preg_replace( '/^[\\t ]*[-*+]\\s+/m', '', $text ) ?? $text;
        $text = preg_replace( '/^[\\t ]*\\d+\\.\\s+/m', '', $text ) ?? $text;
        // 13. Collapse multiple blank lines
        $text = preg_replace( '/\\n{3,}/', "\n\n", $text ) ?? $text;
        return trim( $text );
    }

    /**
     * Permission callback shared by all nonce-protected endpoints.
     *
     * Accepts X-WP-Nonce from the request header. The nonce is issued by
     * /bootstrap, which already validated configuration access + guest policy,
     * so if the nonce matches 'wp_rest' it proves the caller was authorized.
     *
     * @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,
            ]);
        }
        return true;
    }

}
