<?php
/**
 * AJAX handlers for RAG knowledge sync.
 *
 * Two-phase sync to avoid hosting WAF 503 errors:
 * Phase 1 (prepare): Extract + chunk content, delete old data, return chunk texts.
 * Phase 2 (embed_chunk): Embed ONE chunk via middleware + save to DB per request.
 *
 * @since 1.7.0
 */
trait SiteStaffr_Admin_Knowledge_Ajax_Trait {

    /**
     * Get list of published pages and posts with sync status.
     */
    public function ajax_knowledge_get_content_list() {
        if ( ! check_ajax_referer( 'sitestaffr_ajax_nonce', 'nonce', false ) ) {
            wp_send_json_error( array( 'message' => __( 'Security check failed.', 'sitestaffr' ) ) );
            return;
        }

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'sitestaffr' ) ) );
            return;
        }

        $public_types    = get_post_types( array( 'public' => true ), 'names' );
        $queryable_types = get_post_types( array( 'publicly_queryable' => true ), 'names' );
        $ui_types        = get_post_types( array( 'show_ui' => true ), 'names' );
        $all_types       = array_unique( array_merge( array_values( $public_types ), array_values( $queryable_types ), array_values( $ui_types ) ) );
        $all_types       = array_diff( $all_types, array( 'attachment', 'revision', 'nav_menu_item', 'custom_css', 'customize_changeset', 'oembed_cache', 'user_request', 'wp_block', 'wp_template', 'wp_template_part', 'wp_global_styles', 'wp_navigation', 'wp_font_family', 'wp_font_face' ) );

        $posts = get_posts( array(
            'post_type'      => array_values( $all_types ),
            'post_status'    => 'publish',
            'posts_per_page' => -1,
            'orderby'        => 'title',
            'order'          => 'ASC',
        ) );

        // Dates display in Eastern Time (site-wide convention). Stored values
        // are site-local wall-clock strings (post_modified / current_time),
        // so parse in the site timezone before converting.
        $display_tz = new DateTimeZone( 'America/New_York' );

        $items = array();
        foreach ( $posts as $post ) {
            $synced_at    = get_post_meta( $post->ID, '_sitestaffr_knowledge_synced', true );
            $post_modified = $post->post_modified;

            if ( empty( $synced_at ) ) {
                $status = 'unsynced';
            } elseif ( strtotime( $post_modified ) > strtotime( $synced_at ) ) {
                $status = 'stale';
            } else {
                $status = 'synced';
            }

            $items[] = array(
                'id'            => $post->ID,
                'title'         => $post->post_title,
                'post_type'     => $post->post_type,
                'modified'      => $this->format_knowledge_date( $post_modified, $display_tz ),
                'synced_at'     => $synced_at ? $synced_at : null,
                'status'        => $status,
            );
        }

        $stats = SiteStaffr_Knowledge_Manager::get_stats();
        if ( ! empty( $stats['last_synced'] ) ) {
            $stats['last_synced'] = $this->format_knowledge_date( (string) $stats['last_synced'], $display_tz );
        }

        wp_send_json_success( array(
            'items'      => $items,
            'stats'      => $stats,
            'top_chunks' => SiteStaffr_Knowledge_Manager::get_top_chunks( 5 ),
        ) );
    }

    /**
     * Format a site-local MySQL datetime as an Eastern Time date for display.
     *
     * @param string       $datetime   Site-local MySQL datetime string.
     * @param DateTimeZone $display_tz Target display timezone.
     * @return string
     */
    private function format_knowledge_date( $datetime, $display_tz ) {
        try {
            $date = new DateTime( $datetime, wp_timezone() );
        } catch ( Exception $e ) {
            return '';
        }

        return wp_date( 'M j, Y', $date->getTimestamp(), $display_tz );
    }

    /**
     * Phase 1: Extract content, chunk it, delete old data, return chunk texts.
     * Lightweight — no outbound HTTP calls.
     */
    public function ajax_knowledge_prepare_sync() {
        if ( ! check_ajax_referer( 'sitestaffr_ajax_nonce', 'nonce', false ) ) {
            wp_send_json_error( array( 'message' => __( 'Security check failed.', 'sitestaffr' ) ) );
            return;
        }

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'sitestaffr' ) ) );
            return;
        }

        $post_id = isset( $_POST['post_id'] ) ? absint( wp_unslash( $_POST['post_id'] ) ) : 0;
        if ( $post_id <= 0 ) {
            wp_send_json_error( array( 'message' => __( 'Invalid post ID.', 'sitestaffr' ) ) );
            return;
        }

        $chunks = SiteStaffr_Content_Chunker::process_post( $post_id );
        if ( empty( $chunks ) ) {
            wp_send_json_error( array( 'message' => __( 'No content to index.', 'sitestaffr' ) ) );
            return;
        }

        // Delete old chunks before re-syncing.
        SiteStaffr_Knowledge_Manager::delete_post_chunks( $post_id );

        $post = get_post( $post_id );

        // Return chunk texts for JS to feed back one at a time.
        $chunk_data = array();
        foreach ( $chunks as $c ) {
            $chunk_data[] = array(
                'chunk_index' => $c['chunk_index'],
                'text'        => $c['text'],
            );
        }

        wp_send_json_success( array(
            'post_id'    => $post_id,
            'post_title' => $post ? $post->post_title : '',
            'post_type'  => $post ? $post->post_type : 'page',
            'chunks'     => $chunk_data,
            'total'      => count( $chunk_data ),
        ) );
    }

    /**
     * Phase 2: Embed a single chunk via middleware and save to DB.
     * One outbound HTTP call per request — stays under WAF thresholds.
     */
    public function ajax_knowledge_embed_chunk() {
        if ( ! check_ajax_referer( 'sitestaffr_ajax_nonce', 'nonce', false ) ) {
            wp_send_json_error( array( 'message' => __( 'Security check failed.', 'sitestaffr' ) ) );
            return;
        }

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'sitestaffr' ) ) );
            return;
        }

        $post_id     = isset( $_POST['post_id'] ) ? absint( wp_unslash( $_POST['post_id'] ) ) : 0;
        $chunk_index = isset( $_POST['chunk_index'] ) ? absint( wp_unslash( $_POST['chunk_index'] ) ) : 0;
        $chunk_text  = isset( $_POST['chunk_text'] ) ? sanitize_textarea_field( wp_unslash( $_POST['chunk_text'] ) ) : '';
        $post_title  = isset( $_POST['post_title'] ) ? sanitize_text_field( wp_unslash( $_POST['post_title'] ) ) : '';
        $post_type   = isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( $_POST['post_type'] ) ) : 'page';
        $is_last     = ! empty( wp_unslash( $_POST['is_last'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- boolean check only.

        if ( $post_id <= 0 || empty( $chunk_text ) ) {
            wp_send_json_error( array( 'message' => __( 'Missing chunk data.', 'sitestaffr' ) ) );
            return;
        }

        // Send single chunk to middleware for embedding.
        $payload = array(
            'chunks' => array(
                array(
                    'text'        => $chunk_text,
                    'post_id'     => $post_id,
                    'chunk_index' => $chunk_index,
                ),
            ),
        );

        $response = $this->call_middleware_embed( $payload );
        if ( is_wp_error( $response ) ) {
            wp_send_json_error( array( 'message' => $response->get_error_message() ) );
            return;
        }

        // Extract embedding vector (required) and title (optional) from response.
        if ( empty( $response['embeddings'][0]['vector'] ) ) {
            wp_send_json_error( array( 'message' => __( 'No embedding returned.', 'sitestaffr' ) ) );
            return;
        }

        $embedding   = $response['embeddings'][0]['vector'];
        $chunk_title = isset( $response['embeddings'][0]['title'] ) ? $response['embeddings'][0]['title'] : null;

        // Save single chunk to DB.
        $saved = SiteStaffr_Knowledge_Manager::insert_chunk( $post_id, array(
            'post_title'  => $post_title,
            'post_type'   => $post_type,
            'chunk_index' => $chunk_index,
            'text'        => $chunk_text,
            'embedding'   => $embedding,
            'chunk_title' => $chunk_title,
        ) );

        if ( ! $saved ) {
            wp_send_json_error( array( 'message' => __( 'Failed to save chunk.', 'sitestaffr' ) ) );
            return;
        }

        // Update sync timestamp on last chunk.
        if ( $is_last ) {
            update_post_meta( $post_id, '_sitestaffr_knowledge_synced', current_time( 'mysql' ) );
        }

        wp_send_json_success( array( 'chunk_index' => $chunk_index ) );
    }

    /**
     * Remove synced knowledge for a post.
     */
    public function ajax_knowledge_remove_post() {
        if ( ! check_ajax_referer( 'sitestaffr_ajax_nonce', 'nonce', false ) ) {
            wp_send_json_error( array( 'message' => __( 'Security check failed.', 'sitestaffr' ) ) );
            return;
        }

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'sitestaffr' ) ) );
            return;
        }

        $post_id = isset( $_POST['post_id'] ) ? absint( wp_unslash( $_POST['post_id'] ) ) : 0;
        if ( $post_id <= 0 ) {
            wp_send_json_error( array( 'message' => __( 'Invalid post ID.', 'sitestaffr' ) ) );
            return;
        }

        SiteStaffr_Knowledge_Manager::delete_post_chunks( $post_id );
        wp_send_json_success( array( 'message' => __( 'Knowledge removed.', 'sitestaffr' ) ) );
    }

    /**
     * Diagnostic: confirm chunk_title schema + fill-rate from browser console.
     * Frontend-only visibility when DB/CLI access is unavailable.
     */
    public function ajax_knowledge_schema_check() {
        if ( ! check_ajax_referer( 'sitestaffr_ajax_nonce', 'nonce', false ) ) {
            wp_send_json_error( array( 'message' => __( 'Security check failed.', 'sitestaffr' ) ) );
            return;
        }
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'sitestaffr' ) ) );
            return;
        }

        global $wpdb;
        $table = SiteStaffr_Database::get_knowledge_table();

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $column_row = $wpdb->get_results(
            $wpdb->prepare( 'SHOW COLUMNS FROM %i WHERE Field = %s', $table, 'chunk_title' )
        );
        $column_exists = ! empty( $column_row );

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $total = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i', $table ) );

        $with_title = 0;
        $samples    = array();
        if ( $column_exists ) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $with_title = (int) $wpdb->get_var(
                $wpdb->prepare( "SELECT COUNT(*) FROM %i WHERE chunk_title IS NOT NULL AND chunk_title <> ''", $table )
            );
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $rows = $wpdb->get_results(
                $wpdb->prepare(
                    'SELECT id, post_id, post_title, chunk_title, LEFT(chunk_text, 80) AS preview FROM %i ORDER BY id DESC LIMIT 10',
                    $table
                ),
                ARRAY_A
            );
            if ( is_array( $rows ) ) {
                $samples = $rows;
            }
        }

        wp_send_json_success( array(
            'plugin_version'         => SITESTAFFR_VERSION,
            'table'                  => $table,
            'chunk_title_column'     => $column_exists,
            'total_chunks'           => $total,
            'chunks_with_title'      => $with_title,
            'sample_recent_chunks'   => $samples,
        ) );
    }

    /**
     * Diagnostic: send a test chunk to the middleware /api/knowledge/embed
     * and return the raw response so we can verify whether the deployed
     * middleware is actually returning a `title` field.
     */
    public function ajax_knowledge_probe_embed() {
        if ( ! check_ajax_referer( 'sitestaffr_ajax_nonce', 'nonce', false ) ) {
            wp_send_json_error( array( 'message' => __( 'Security check failed.', 'sitestaffr' ) ) );
            return;
        }
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'sitestaffr' ) ) );
            return;
        }

        $test_text = "Page: Fees | Section: Option #1 — Sleeve + One Girth\nOption #1: Sleeve Flaccid Lengthening combined with one Penile Shaft and Glans Girth Treatment. Regular Fee $19,950.";

        $payload = array(
            'chunks' => array(
                array(
                    'text'        => $test_text,
                    'post_id'     => 0,
                    'chunk_index' => 0,
                ),
            ),
        );

        $response = $this->call_middleware_embed( $payload );
        if ( is_wp_error( $response ) ) {
            wp_send_json_error( array(
                'message'    => $response->get_error_message(),
                'error_code' => $response->get_error_code(),
                'mw_url'     => defined( 'SITESTAFFR_MIDDLEWARE_URL' ) ? SITESTAFFR_MIDDLEWARE_URL : 'undefined',
            ) );
            return;
        }

        $first = isset( $response['embeddings'][0] ) ? $response['embeddings'][0] : array();
        wp_send_json_success( array(
            'mw_url'            => defined( 'SITESTAFFR_MIDDLEWARE_URL' ) ? SITESTAFFR_MIDDLEWARE_URL : 'undefined',
            'response_keys'     => is_array( $response ) ? array_keys( $response ) : array(),
            'embedding_keys'    => is_array( $first ) ? array_keys( $first ) : array(),
            'title_present'     => array_key_exists( 'title', (array) $first ),
            'title_value'       => isset( $first['title'] ) ? $first['title'] : null,
            'title_type'        => isset( $first['title'] ) ? gettype( $first['title'] ) : 'missing',
            'vector_present'    => isset( $first['vector'] ) && is_array( $first['vector'] ),
            'vector_length'     => isset( $first['vector'] ) && is_array( $first['vector'] ) ? count( $first['vector'] ) : 0,
            'post_id_echoed'    => isset( $first['post_id'] ) ? $first['post_id'] : null,
            'chunk_index_echo'  => isset( $first['chunk_index'] ) ? $first['chunk_index'] : null,
        ) );
    }

    /**
     * Call middleware embed endpoint.
     *
     * @param array $payload Request body.
     * @return array|WP_Error Response data or error.
     */
    private function call_middleware_embed( $payload ) {
        if ( ! defined( 'SITESTAFFR_MIDDLEWARE_URL' ) || empty( SITESTAFFR_MIDDLEWARE_URL ) ) {
            return new \WP_Error( 'no_middleware', __( 'Middleware not configured.', 'sitestaffr' ) );
        }

        // Per-site secret first (registered sites), then global fallback.
        $api_secret = get_option( 'sitestaffr_site_api_secret', '' );
        if ( empty( $api_secret ) ) {
            $api_secret = get_option( 'sitestaffr_api_secret', '' );
        }
        if ( empty( $api_secret ) ) {
            return new \WP_Error( 'no_secret', __( 'API secret not configured.', 'sitestaffr' ) );
        }

        $body      = wp_json_encode( $payload );
        $timestamp = (string) time();
        $signature = base64_encode( hash_hmac( 'sha256', $timestamp . '.' . $body, $api_secret, true ) );

        $response = wp_remote_post(
            rtrim( SITESTAFFR_MIDDLEWARE_URL, '/' ) . '/api/knowledge/embed',
            array(
                'timeout' => 60,
                'headers' => array(
                    'Content-Type'           => 'application/json',
                    'X-SiteStaffr-Signature' => $signature,
                    'X-SiteStaffr-Timestamp' => $timestamp,
                    'X-SiteStaffr-Site-URL'  => esc_url_raw( get_site_url() ),
                ),
                'body'    => $body,
            )
        );

        if ( is_wp_error( $response ) ) {
            return $response;
        }

        $code = wp_remote_retrieve_response_code( $response );
        $data = json_decode( wp_remote_retrieve_body( $response ), true );

        if ( 200 !== $code || empty( $data['success'] ) ) {
            $msg      = isset( $data['message'] ) ? $data['message'] : __( 'Embedding request failed.', 'sitestaffr' );
            $err_code = isset( $data['error'] ) ? $data['error'] : 'embed_failed';
            return new \WP_Error( $err_code, $msg, array( 'status' => $code ) );
        }

        return $data;
    }

    /**
     * AJAX handler: add a manual knowledge chunk.
     *
     * @since 2.0.0
     */
    public function ajax_add_manual_knowledge() {
        check_ajax_referer( 'sitestaffr_knowledge_admin', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'sitestaffr' ) ), 403 );
            return;
        }

        $post_id = isset( $_POST['post_id'] ) ? intval( wp_unslash( $_POST['post_id'] ) ) : 0;
        $title   = isset( $_POST['title'] ) ? sanitize_text_field( wp_unslash( $_POST['title'] ) ) : '';
        $text    = isset( $_POST['text'] ) ? sanitize_textarea_field( wp_unslash( $_POST['text'] ) ) : '';

        if ( empty( $text ) ) {
            wp_send_json_error( array( 'message' => __( 'Knowledge text is required.', 'sitestaffr' ) ) );
            return;
        }

        if ( empty( $title ) ) {
            $title = $post_id > 0 ? get_the_title( $post_id ) : __( 'Manual Knowledge', 'sitestaffr' );
        }

        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-knowledge-manager.php';
        $chunk_id = SiteStaffr_Knowledge_Manager::insert_manual_chunk( $post_id, $title, $text );

        if ( false === $chunk_id ) {
            wp_send_json_error( array( 'message' => __( 'Failed to save knowledge entry.', 'sitestaffr' ) ) );
            return;
        }

        // Embed the manual chunk so it's searchable.
        $embed_payload = array(
            'chunks' => array(
                array(
                    'text'        => $text,
                    'post_id'     => $post_id,
                    'chunk_index' => 0,
                ),
            ),
        );

        $embed_response = $this->call_middleware_embed( $embed_payload );
        if ( ! is_wp_error( $embed_response ) && ! empty( $embed_response['embeddings'][0]['vector'] ) ) {
            global $wpdb;
            $table       = SiteStaffr_Database::get_knowledge_table();
            $embedding   = SiteStaffr_Knowledge_Manager::vector_to_binary( $embed_response['embeddings'][0]['vector'] );
            $chunk_title = isset( $embed_response['embeddings'][0]['title'] ) ? sanitize_text_field( $embed_response['embeddings'][0]['title'] ) : '';

            $update_data    = array( 'embedding' => $embedding );
            $update_formats = array( '%s' );
            if ( '' !== $chunk_title ) {
                $update_data['chunk_title'] = $chunk_title;
                $update_formats[]           = '%s';
            }
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->update(
                $table,
                $update_data,
                array( 'id' => $chunk_id ),
                $update_formats,
                array( '%d' )
            );
        }

        wp_send_json_success( array(
            'message'  => __( 'Knowledge entry added.', 'sitestaffr' ),
            'chunk_id' => $chunk_id,
        ) );
    }

    /**
     * AJAX handler: delete a manual knowledge chunk.
     *
     * @since 2.0.0
     */
    public function ajax_delete_manual_knowledge() {
        check_ajax_referer( 'sitestaffr_knowledge_admin', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'sitestaffr' ) ), 403 );
            return;
        }

        $chunk_id = isset( $_POST['chunk_id'] ) ? intval( wp_unslash( $_POST['chunk_id'] ) ) : 0;
        if ( $chunk_id <= 0 ) {
            wp_send_json_error( array( 'message' => __( 'Invalid chunk ID.', 'sitestaffr' ) ) );
            return;
        }

        require_once SITESTAFFR_PLUGIN_DIR . 'includes/class-sitestaffr-knowledge-manager.php';
        $deleted = SiteStaffr_Knowledge_Manager::delete_manual_chunk( $chunk_id );

        if ( ! $deleted ) {
            wp_send_json_error( array( 'message' => __( 'Could not delete this entry. It may have already been removed.', 'sitestaffr' ) ) );
            return;
        }

        wp_send_json_success( array( 'message' => __( 'Knowledge entry removed.', 'sitestaffr' ) ) );
    }

    /**
     * Save the knowledge_mode setting via AJAX.
     *
     * @since 1.9.0
     */
    public function ajax_save_knowledge_mode() {
        check_ajax_referer( 'sitestaffr_save_knowledge_mode', 'nonce' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => 'Unauthorized' ), 403 );
        }

        $mode = isset( $_POST['knowledge_mode'] ) ? sanitize_key( wp_unslash( $_POST['knowledge_mode'] ) ) : 'search';
        if ( ! in_array( $mode, array( 'search', 'page_scoped' ), true ) ) {
            $mode = 'search';
        }

        $settings = get_option( 'sitestaffr_settings', array() );
        $settings['knowledge_mode'] = $mode;
        update_option( 'sitestaffr_settings', $settings );

        wp_send_json_success();
    }
}
