<?php
/**
 * AJAX handler for text chat streaming proxy.
 *
 * Receives chat messages from the browser via admin-ajax.php,
 * signs with HMAC, proxies to middleware, and streams NDJSON back.
 * Mirrors the WebRTC session handler pattern.
 *
 * @package SiteStaffr
 * @since   1.5.0
 */

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

class SiteStaffr_Chat_Message_Handler {

	/**
	 * Handle chat message AJAX request.
	 *
	 * Streams NDJSON response from middleware back to the browser.
	 * Uses wp_remote_post with http_api_curl hook for real-time streaming.
	 */
	public static function ajax_chat_message() {
		check_ajax_referer( 'sitestaffr_chat_message', 'nonce' );

		// Additional security guards: honeypot, ban, global/per-IP rate limits, burst, message length.
		$ip = self::get_client_ip();
		SiteStaffr_Public_Request_Guards::enforce_public_guards( array(
			'nonce_action'       => 'sitestaffr_chat_message',
			'ip'                 => $ip,
			'honeypot'           => true,
			'ban_check'          => true,
			'global_limit'       => 100,
			'rate_limit'         => 30,
			'burst_detect'       => true,
			'max_message_length' => 2000,
		) );

		// 3. Parse request fields
		$message              = SiteStaffr_Public_Request_Guards::get_post_raw_field( 'message' );
		$previous_response_id = SiteStaffr_Public_Request_Guards::get_post_text_field( 'previous_response_id' );
		$session_id           = SiteStaffr_Public_Request_Guards::get_post_text_field( 'session_id' );
		$persona              = SiteStaffr_Public_Request_Guards::get_post_key_field( 'persona' );

		if ( empty( $session_id ) ) {
			wp_send_json_error( array( 'message' => 'Missing session ID.' ), 400 );
			return;
		}

		if ( ! in_array( $persona, array( 'default', 'onboarding', 'informational' ), true ) ) {
			$persona = 'default';
		}

		// Include page-specific knowledge context if available.
		$current_post_id = isset( $_POST['current_post_id'] ) ? absint( wp_unslash( $_POST['current_post_id'] ) ) : 0;

		// 4. Get API secret and middleware URL (uses per-site secret with global fallback)
		$api_secret = SiteStaffr_Public_Middleware_Client::get_current_api_secret();
		$preflight  = SiteStaffr_Public_Middleware_Client::get_public_preflight_config( $api_secret );

		if ( $preflight['missing_api_secret'] || $preflight['missing_middleware_url'] ) {
			wp_send_json_error( array( 'message' => 'Service not configured.' ), 500 );
			return;
		}

		// 5. Assemble business_info from WordPress settings
		// MUST mirror the WebRTC handler pattern exactly.
		$settings      = get_option( 'sitestaffr_settings', array() );
		$business_info = array(
			'business_name'        => isset( $settings['business_name'] ) ? $settings['business_name'] : '',
			'business_description' => isset( $settings['business_context'] ) ? $settings['business_context'] : get_option( 'sitestaffr_business_description', '' ),
			'business_hours'       => isset( $settings['business_hours'] ) ? $settings['business_hours'] : get_option( 'sitestaffr_business_hours', '' ),
			'business_phone'       => isset( $settings['business_phone'] ) ? $settings['business_phone'] : get_option( 'sitestaffr_business_phone', '' ),
			'custom_greeting'      => isset( $settings['custom_greeting'] ) ? sanitize_text_field( $settings['custom_greeting'] ) : '',
			'custom_greeting_tone' => isset( $settings['custom_greeting_tone'] ) ? sanitize_key( $settings['custom_greeting_tone'] ) : 'warm_neutral',
			'custom_instructions'       => isset( $settings['custom_instructions'] ) ? $settings['custom_instructions'] : '',
			'custom_instructions_user'  => SiteStaffr_Business_Enrichment::get_user_custom_instructions(),
			'persona'                  => $persona,
			'collect_contact'          => isset( $settings['ai_collect_contact'] ) ? (bool) $settings['ai_collect_contact'] : true,
			'disregard_personal_info'  => isset( $settings['ai_disregard_personal_info'] ) ? (bool) $settings['ai_disregard_personal_info'] : false,
		);

		$business_info  = SiteStaffr_Business_Enrichment::attach_hot_chunks( $business_info, 'text' );
		$knowledge_mode = isset( $settings['knowledge_mode'] ) ? sanitize_key( $settings['knowledge_mode'] ) : 'search';
		$chunks         = ( $current_post_id > 0 ) ? SiteStaffr_Knowledge_Manager::get_chunks_for_post( $current_post_id ) : array();
		$overview_limit = ( 'page_scoped' === $knowledge_mode ) ? 3 : count( $chunks );
		if ( ! empty( $chunks ) ) {
			$business_info['knowledge_context'] = array(
				'current_page'    => get_the_title( $current_post_id ),
				'current_post_id' => $current_post_id,
				'chunks'          => array_slice( $chunks, 0, $overview_limit ),
				'mode'            => $knowledge_mode,
			);
		} elseif ( SiteStaffr_Knowledge_Manager::has_knowledge() ) {
			$business_info['knowledge_context'] = array(
				'current_page'    => ( $current_post_id > 0 ) ? get_the_title( $current_post_id ) : '',
				'current_post_id' => $current_post_id > 0 ? $current_post_id : null,
				'chunks'          => array(),
				'has_knowledge'   => true,
				'mode'            => $knowledge_mode,
			);
		}

		// Site pages directory for navigation links. All modes — search mode
		// needs it so the AI can answer "where can I find X?" with a real link
		// (was page_scoped-only until 1.38.0).
		if ( empty( $business_info['knowledge_context'] ) ) {
			if ( 'page_scoped' === $knowledge_mode ) {
				$business_info['knowledge_context'] = array(
					'mode'      => $knowledge_mode,
					'chunks'    => array(),
					'sitePages' => SiteStaffr_Knowledge_Manager::get_site_pages(),
				);
			}
		} else {
			$business_info['knowledge_context']['sitePages'] = SiteStaffr_Knowledge_Manager::get_site_pages();
		}

		// Parse contact_fields from client (JSON-encoded visitor contact state).
		$contact_fields_raw = SiteStaffr_Public_Request_Guards::get_post_raw_field( 'contact_fields' );
		$contact_fields     = ! empty( $contact_fields_raw ) ? json_decode( $contact_fields_raw, true ) : null;

		// Parse fields_to_collect from client (JSON-encoded array of field names).
		$fields_to_collect_raw = SiteStaffr_Public_Request_Guards::get_post_raw_field( 'fields_to_collect' );
		$fields_to_collect     = ! empty( $fields_to_collect_raw ) ? json_decode( $fields_to_collect_raw, true ) : null;
		$allowed_fields        = array( 'name', 'phone', 'email', 'reason' );
		if ( is_array( $fields_to_collect ) ) {
			$fields_to_collect = array_values( array_intersect( $fields_to_collect, $allowed_fields ) );
			if ( empty( $fields_to_collect ) ) {
				$fields_to_collect = null;
			}
		} else {
			$fields_to_collect = null;
		}

		// 6. Build JSON body
		$account_id      = sanitize_text_field( get_option( 'sitestaffr_account_id', '' ) );
		$installation_id = sanitize_text_field( get_option( 'sitestaffr_installation_id', '' ) );

		$body = wp_json_encode( array(
			'message'              => $message,
			'previous_response_id' => ! empty( $previous_response_id ) ? $previous_response_id : null,
			'session_id'           => $session_id,
			'persona'              => $persona,
			'business_info'        => $business_info,
			'site_url'             => site_url(),
			'account_id'           => $account_id,
			'installation_id'      => $installation_id,
			'contact_fields'       => is_array( $contact_fields ) ? $contact_fields : null,
			'fields_to_collect'    => $fields_to_collect,
		) );

		// 7. Sign with HMAC (same pattern as WebRTC handler)
		$timestamp = (string) time();
		$signature = base64_encode( hash_hmac( 'sha256', $timestamp . '.' . $body, $preflight['api_secret'], true ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode

		$url = rtrim( $preflight['middleware_url'], '/' ) . '/api/chat/message';

		// 8. Stream the response — disable output buffering and send NDJSON
		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- ob_end_clean may warn if no buffer exists
		while ( @ob_get_level() ) {
			@ob_end_clean(); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
		}
		ob_implicit_flush( true );

		header( 'Content-Type: application/x-ndjson' );
		header( 'Cache-Control: no-cache' );
		header( 'X-Accel-Buffering: no' );
		header( 'Content-Encoding: none' );

		// Use WP HTTP API with http_api_curl hook for streaming.
		// Each NDJSON line is validated as JSON and re-encoded before output.
		$ndjson_buffer  = '';
		$target_url     = $url;
		$stream_handler = function ( &$handle, $parsed_args, $request_url ) use ( $target_url, &$ndjson_buffer ) {
			if ( $request_url !== $target_url ) {
				return;
			}
			// phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
			curl_setopt( $handle, CURLOPT_WRITEFUNCTION, function ( $ch, $data ) use ( &$ndjson_buffer ) {
				$ndjson_buffer .= $data;
				while ( ( $pos = strpos( $ndjson_buffer, "\n" ) ) !== false ) {
					$line          = substr( $ndjson_buffer, 0, $pos );
					$ndjson_buffer = substr( $ndjson_buffer, $pos + 1 );
					$line          = trim( $line );
					if ( '' === $line ) {
						continue;
					}
					$decoded = json_decode( $line, true );
					if ( null !== $decoded ) {
						echo wp_json_encode( $decoded ) . "\n";
						if ( function_exists( 'flush' ) ) {
							flush();
						}
					}
				}
				return strlen( $data );
			} );
		};

		add_action( 'http_api_curl', $stream_handler, 10, 3 );

		$response = wp_remote_post( $url, array(
			'body'    => $body,
			'headers' => array(
				'Content-Type'                                         => 'application/json',
				SiteStaffr_Public_Middleware_Client::header_signature() => $signature,
				SiteStaffr_Public_Middleware_Client::header_timestamp() => $timestamp,
			),
			'timeout' => 120,
		) );

		remove_action( 'http_api_curl', $stream_handler, 10 );

		if ( is_wp_error( $response ) ) {
			echo wp_json_encode( array(
				'type'    => 'error',
				'message' => 'Connection to service failed.',
			) ) . "\n";
		}

		die(); // Standard WordPress AJAX termination
	}

	/**
	 * Get the client IP address, checking proxy headers.
	 *
	 * Mirrors the pattern used by SiteStaffr_Widget::get_client_ip().
	 *
	 * @return string
	 */
	private static function get_client_ip() {
		$headers = array(
			'HTTP_CF_CONNECTING_IP', // Cloudflare
			'HTTP_X_FORWARDED_FOR',  // Standard proxy header
			'HTTP_X_REAL_IP',        // Nginx proxy
			'REMOTE_ADDR',           // Direct connection
		);

		foreach ( $headers as $header ) {
			if ( ! empty( $_SERVER[ $header ] ) ) {
				// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized below
				$ip = sanitize_text_field( wp_unslash( $_SERVER[ $header ] ) );
				// If X-Forwarded-For contains multiple IPs, use the first one
				if ( strpos( $ip, ',' ) !== false ) {
					$ips = explode( ',', $ip );
					$ip  = trim( $ips[0] );
				}
				return $ip;
			}
		}

		return '0.0.0.0';
	}
}
