<?php

namespace Limb_Chatbot\Includes\Api\V1\Controllers;

use Exception;
use Limb_Chatbot\Includes\Data_Objects\Chat;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Factories\Message_Service_Factory;
use Limb_Chatbot\Includes\Repositories\Chat_Repository;
use Limb_Chatbot\Includes\Services\Chat_Service;
use Limb_Chatbot\Includes\Services\Helper;
use Limb_Chatbot\Includes\Services\Stream_Event_Service;
use Limb_Chatbot\Includes\Services\User_Manager;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;

/**
 * Controller for managing chat messages via REST API.
 *
 * Handles creating and retrieving chat messages for a specific chat.
 *
 * @package Limb_Chatbot\Includes\Api\V1\Controllers
 * @since 1.0.0
 */
class Chat_Messages_Controller extends Rest_Controller {

	/**
	 * REST route base including chat UUID parameter.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	protected $rest_base = 'chats/(?P<chat_uuid>[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/messages';

	/**
	 * Chat repository instance.
	 *
	 * @var Chat_Repository
	 * @since 1.0.0
	 */
	public Chat_Repository $chat_repository;

	/**
	 * Message service factory instance.
	 *
	 * @var Message_Service_Factory
	 * @since 1.0.0
	 */
	public Message_Service_Factory $message_service_factory;

	/**
	 * Constructor initializes dependencies.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {
		$this->message_service_factory = new Message_Service_Factory();
		$this->chat_repository         = new Chat_Repository();
	}

	/**
	 * Registers REST API routes for chat messages.
	 *
	 * @since 1.0.0
	 * @return void
	 */
	public function register_routes() {
		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'create_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array_merge( $this->get_chat_uuid_arg(), $this->get_endpoint_args_for_item_schema() ),
			),
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_items' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array_merge( $this->get_chat_uuid_arg(), $this->get_collection_params() ),
			),
			'schema' => array( $this, 'get_item_schema' ),
		) );
	}

	/**
	 * Creates a new chat message.
	 *
	 * @param WP_REST_Request $request Incoming REST request.
	 *
	 * @return WP_REST_Response|WP_Error Response or error.
	 * @since 1.0.0
	 */
	public function create_item( $request ) {
		try {
			$chat    = $this->chat_repository->get_by_uuid( $request['chat_uuid'] );
			$preview = (bool) $request->get_param( 'preview' );
			$params  = $request->get_json_params();
			$role    = $request->get_param( 'role' ) ?? 'user';
			$message = $this->message_service_factory->make( $chat, $role )->create( $params, $preview );
			if ( $message->get_role() === Message::ROLE_ASSISTANT ) {
				return rest_ensure_response( $message );
			}
			if ( $this->is_stream_waiting( $request ) ) {
				Stream_Event_Service::user_message_saved( $message );
			}
			if ( $message instanceof Message || !$message->role == Message::ROLE_ASSISTANT ) {
				$res = ( new Chat_Service( $chat->get_chatbot() ) )->run( $chat, (bool) $request->get_param( 'preview' ) );
				if ( $this->is_stream_waiting( $request ) ) {
					Stream_Event_Service::message( $res );
				}
				$res->inject_previous_message( $message );
			}

			return rest_ensure_response( $res ?? null );
		} catch ( Exception $e ) {
			Helper::log( $e, __METHOD__ );
			if ( ! $request->get_param( 'preview' ) && $e instanceof \Limb_Chatbot\Includes\Exceptions\Exception ) {
				// Any other error that should be shown as it is for the user, could be excepted below
				if ( ! $e->is_limitation_error() ) {
					$e = new \Limb_Chatbot\Includes\Exceptions\Exception( $this->get_error_code( $e, $message ?? null ), __( 'Failed to reply', 'limb-chatbot' ) );
				}
			}
			$error = Helper::get_wp_error( $e, __( 'Failed to reply', 'limb-chatbot' ) );
			if ( $this->is_stream_waiting( $request ) ) {
				Stream_Event_Service::error( $e->getMessage(), [ 'error_details' => $error ] );
			}

			return $error;
		}
	}

	/**
	 * Retrieves chat messages for a given chat UUID.
	 *
	 * @param WP_REST_Request $request Incoming REST request.
	 *
	 * @return WP_REST_Response|WP_Error Response or error.
	 * @since 1.0.0
	 */
	public function get_items( $request ) {
		try {
			$chat     = $this->chat_repository->get_by_uuid( $request['chat_uuid'] );
			$messages = $this->message_service_factory->make( $chat )->get_items( $request->get_query_params() );
			$messages = $this->prepare_collection( $messages, $request );

			return rest_ensure_response( $messages );
		} catch ( Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}

	}

	/**
	 * Returns the schema for chat messages.
	 *
	 * Defines validation and structure of chat message data.
	 *
	 * @since 1.0.0
	 * @return array JSON Schema array.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			// Since WordPress 5.3, the schema can be cached in the $schema property.
			return $this->schema;
		}
		$this->schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'chat',
			'type'       => 'object',
			'properties' => array(
				'id'      => array(
					'description' => esc_html__( 'Unique identifier for the object.', 'limb-chatbot' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'content' => array(
					'description' => __( 'Message content', 'limb-chatbot' ),
					'type'        => 'array',
					'context'     => array( 'view', 'edit' ),
					'required'    => true,
					'arg_options' => array(
						'validate_callback' => array($this, 'validate_content'),
						'sanitize_callback' => array($this, 'sanitize_content')
					)
				),
				'user_id' => array(
					'description'       => __( 'User id', 'limb-chatbot' ),
					'type'              => [ 'integer', 'null' ],
					'context'           => array( 'view', 'edit' ),
					'sanitize_callback' => function ( $value ) {
						return $value === null ? null : absint( $value );
					}
				),
				'role'    => array(
					'description'       => __( 'Sender role', 'limb-chatbot' ),
					'type'              => 'string',
					'context'           => array( 'view', 'edit' ),
					'required'          => true,
					'arg_options'       => array(
						'validate_callback' => function ( $value ) {
							return in_array( $value, Message::get_allowed_roles() );
						},
						'sanitize_callback' => function ( $value ) {
							return sanitize_text_field( $value );
						}
					)
				)
			),
		);

		return $this->schema;
	}

	public function validate_content( $content ) {
		if ( is_array( $content ) ) {
			foreach ( $content as $item ) {
				if ( ! isset( $item['type'] ) ) {
					return false;
				}
				switch ( $item['type'] ) {
					case 'text':
						if ( empty( $item['text'] ) || ! is_array( $item['text'] ) || empty( $item['text']['value'] ) ) {
							// Can be empty
						}
						break;
					case 'attachment':
						if ( empty( $item['attachment'] ) || ! is_array( $item['attachment'] ) ) {
							return false;
						}
						$mime_type = isset( $item['attachment']['mime_type'] ) ? sanitize_mime_type( (string) $item['attachment']['mime_type'] ) : '';
						$value     = isset( $item['attachment']['value'] ) ? (string) $item['attachment']['value'] : '';
						$media_id  = isset( $item['attachment']['media_id'] ) ? absint( $item['attachment']['media_id'] ) : 0;
						if ( empty( $mime_type ) || empty( $value ) ) {
							return false;
						}

						// Backward compatibility: old base64 image attachments.
						if ( str_starts_with( $value, 'data:image/' ) ) {
							if ( ! in_array( $mime_type, Message::get_allowed_mime_types(), true ) ) {
								return false;
							}
							if ( ! Helper::is_valid_base64_image( $value ) ) {
								return false;
							}
							$decoded_data = base64_decode( $value );
							$file_size    = strlen( $decoded_data );
							if ( $file_size > 5 * 1024 * 1024 ) {
								return false;
							}

							break;
						}

						// Attachment-parameter flow: URL pointing to previously uploaded file.
						if ( ! filter_var( $value, FILTER_VALIDATE_URL ) ) {
							return false;
						}

						if ( $media_id <= 0 ) {
							return false;
						}
						$media_post = get_post( $media_id );
						if ( ! $media_post || $media_post->post_type !== 'attachment' ) {
							return false;
						}
						$media_url  = wp_get_attachment_url( $media_id );
						$media_mime = get_post_mime_type( $media_id );
						if ( empty( $media_url ) || empty( $media_mime ) ) {
							return false;
						}
						if ( esc_url_raw( $media_url ) !== esc_url_raw( $value ) ) {
							return false;
						}
						if ( sanitize_mime_type( $media_mime ) !== $mime_type ) {
							return false;
						}
						break;
					case 'action_cancellation':
					case 'undo_parameter':
					case 'parameter_value':
						break;
					case 'wc_browse_more':
						if ( empty( $item['wc_browse_more'] ) || ! is_array( $item['wc_browse_more'] ) ) {
							return false;
						}
						if ( ! isset( $item['wc_browse_more']['identifier'] ) || '' === trim( (string) $item['wc_browse_more']['identifier'] ) ) {
							return false;
						}
						break;
					case 'wc_load_more':
						if ( isset( $item['wc_load_more'] ) && ! is_array( $item['wc_load_more'] ) ) {
							return false;
						}
						break;
					case 'wc_product_confirmation':
						if ( empty( $item['wc_product_confirmation'] ) || ! is_array( $item['wc_product_confirmation'] ) ) {
							return false;
						}
						if ( ! isset( $item['wc_product_confirmation']['product_id'] ) || absint( $item['wc_product_confirmation']['product_id'] ) <= 0 ) {
							return false;
						}
						break;
					case 'wc_add_to_cart':
						if ( empty( $item['wc_add_to_cart'] ) || ! is_array( $item['wc_add_to_cart'] ) ) {
							return false;
						}
						$intent = isset( $item['wc_add_to_cart']['intent'] ) ? sanitize_key( (string) $item['wc_add_to_cart']['intent'] ) : '';
						if ( ! in_array( $intent, [ 'confirm_true', 'confirm_false' ], true ) ) {
							return false;
						}
						if ( 'confirm_true' === $intent ) {
							$product_type = isset( $item['wc_add_to_cart']['product_type'] ) ? sanitize_key( (string) $item['wc_add_to_cart']['product_type'] ) : '';
							if ( 'simple' === $product_type ) {
								if ( ! isset( $item['wc_add_to_cart']['product_id'] ) || absint( $item['wc_add_to_cart']['product_id'] ) <= 0 ) {
									return false;
								}
							} elseif ( 'variable' === $product_type ) {
								if ( ! isset( $item['wc_add_to_cart']['variation'] ) || absint( $item['wc_add_to_cart']['variation'] ) <= 0 ) {
									return false;
								}
							} else {
								return false;
							}
						}
						break;
					default:
						return false;
				}
			}

			return true;
		}

		return false;
	}

	/**
	 * Sanitize message content array.
	 *
	 * Sanitizes all text fields in the content array while preserving structure.
	 * Base64 attachment data is not sanitized (binary data).
	 *
	 * @param array|mixed $content The content array to sanitize.
	 * @return array|mixed Sanitized content array.
	 * @since 1.0.0
	 */
	public function sanitize_content( $content ) {
		if ( ! is_array( $content ) ) {
			return $content;
		}

		$sanitized = array();
		foreach ( $content as $item ) {
			if ( ! is_array( $item ) || ! isset( $item['type'] ) ) {
				continue;
			}

			$sanitized_item = array( 'type' => sanitize_text_field( $item['type'] ) );

			switch ( $item['type'] ) {
				case 'text':
					if ( isset( $item['text'] ) && is_array( $item['text'] ) ) {
						$sanitized_item['text'] = array(
							'value' => isset( $item['text']['value'] ) ? wp_kses_post( $item['text']['value'] ) : ''
						);
						// Preserve other text properties if they exist
						if ( isset( $item['text']['data'] ) ) {
							$sanitized_item['text']['data'] = sanitize_text_field( $item['text']['data'] );
						}
					}
					break;

				case 'attachment':
					if ( isset( $item['attachment'] ) && is_array( $item['attachment'] ) ) {
						$sanitized_item['attachment'] = array();
						if ( isset( $item['attachment']['title'] ) ) {
							$sanitized_item['attachment']['title'] = sanitize_text_field( $item['attachment']['title'] );
						}
						// MIME type should be sanitized
						if ( isset( $item['attachment']['mime_type'] ) ) {
							$sanitized_item['attachment']['mime_type'] = sanitize_mime_type( $item['attachment']['mime_type'] );
						}
						// Keep attachment value as-is (base64 or URL), validation handles integrity.
						if ( isset( $item['attachment']['value'] ) ) {
							$sanitized_item['attachment']['value'] = $item['attachment']['value'];
						}
						if ( isset( $item['attachment']['media_id'] ) ) {
							$sanitized_item['attachment']['media_id'] = absint( $item['attachment']['media_id'] );
						}
					}
					break;

				case 'parameter_value':
					if ( isset( $item['parameter_value'] ) && is_array( $item['parameter_value'] ) ) {
						$sanitized_item['parameter_value'] = array();
						// Sanitize the value
						if ( isset( $item['parameter_value']['value'] ) ) {
							$sanitized_item['parameter_value']['value'] = wp_kses_post( $item['parameter_value']['value'] );
						}
						// Preserve other properties if they exist
						if ( isset( $item['parameter_value']['parameter'] ) ) {
							$sanitized_item['parameter_value']['parameter'] = $item['parameter_value']['parameter'];
						}
					}
					break;

				case 'action_cancellation':
					if ( isset( $item['action_cancellation'] ) && is_array( $item['action_cancellation'] ) ) {
						$sanitized_item['action_cancellation'] = array();
						if ( isset( $item['action_cancellation']['text'] ) ) {
							$sanitized_item['action_cancellation']['text'] = wp_kses_post( $item['action_cancellation']['text'] );
						}
						// Preserve other properties
						foreach ( $item['action_cancellation'] as $key => $value ) {
							if ( $key !== 'text' && ! isset( $sanitized_item['action_cancellation'][ $key ] ) ) {
								$sanitized_item['action_cancellation'][ $key ] = is_string( $value ) ? sanitize_text_field( $value ) : $value;
							}
						}
					}
					break;
				case 'undo_parameter':
					if ( isset( $item['undo_parameter'] ) && is_array( $item['undo_parameter'] ) ) {
						$sanitized_item['undo_parameter'] = array();
						if ( isset( $item['undo_parameter']['parameter_name'] ) ) {
							$sanitized_item['undo_parameter']['parameter_name'] = wp_kses_post( $item['undo_parameter']['parameter_name'] );
						}
					}
					break;
				case 'wc_browse_more':
					if ( isset( $item['wc_browse_more'] ) && is_array( $item['wc_browse_more'] ) ) {
						$sanitized_item['wc_browse_more'] = array();
						if ( isset( $item['wc_browse_more']['identifier'] ) ) {
							$sanitized_item['wc_browse_more']['identifier'] = wp_kses_post( $item['wc_browse_more']['identifier'] );
						}
					}
					break;
				case 'wc_load_more':
					$sanitized_item['wc_load_more'] = array();
					break;
				case 'wc_product_confirmation':
					if ( isset( $item['wc_product_confirmation'] ) && is_array( $item['wc_product_confirmation'] ) ) {
						$sanitized_item['wc_product_confirmation'] = array();
						if ( isset( $item['wc_product_confirmation']['product_id'] ) ) {
							$sanitized_item['wc_product_confirmation']['product_id'] = absint( $item['wc_product_confirmation']['product_id'] );
						}
					}
					break;
				case 'wc_add_to_cart':
					if ( isset( $item['wc_add_to_cart'] ) && is_array( $item['wc_add_to_cart'] ) ) {
						$sanitized_item['wc_add_to_cart'] = array();
						if ( isset( $item['wc_add_to_cart']['intent'] ) ) {
							$sanitized_item['wc_add_to_cart']['intent'] = sanitize_key( (string) $item['wc_add_to_cart']['intent'] );
						}
						if ( isset( $item['wc_add_to_cart']['product_type'] ) ) {
							$sanitized_item['wc_add_to_cart']['product_type'] = sanitize_key( (string) $item['wc_add_to_cart']['product_type'] );
						}
						if ( isset( $item['wc_add_to_cart']['product_id'] ) ) {
							$sanitized_item['wc_add_to_cart']['product_id'] = absint( $item['wc_add_to_cart']['product_id'] );
						}
						if ( isset( $item['wc_add_to_cart']['variation'] ) ) {
							$sanitized_item['wc_add_to_cart']['variation'] = absint( $item['wc_add_to_cart']['variation'] );
						}
					}
					break;
				case 'action_submission':
					if ( isset( $item['action_submission'] ) && is_array( $item['action_submission'] ) ) {
						$sanitized_item['action_submission'] = array();
						if ( isset( $item['action_submission']['message'] ) ) {
							$sanitized_item['action_submission']['message'] = wp_kses_post( $item['action_submission']['message'] );
						}
						// Preserve other properties
						foreach ( $item['action_submission'] as $key => $value ) {
							if ( $key !== 'message' && ! isset( $sanitized_item['action_submission'][ $key ] ) ) {
								$sanitized_item['action_submission'][ $key ] = is_string( $value ) ? sanitize_text_field( $value ) : $value;
							}
						}
					}
					break;

				case 'parameter':
					// Parameter content type - preserve structure but sanitize string fields
					$sanitized_item = $item;
					if ( isset( $item['parameter'] ) && is_object( $item['parameter'] ) ) {
						// Parameter object should be preserved as-is (it's a data object)
						$sanitized_item['parameter'] = $item['parameter'];
					}
					// Sanitize other string fields
					foreach ( $item as $key => $value ) {
						if ( $key !== 'type' && $key !== 'parameter' && is_string( $value ) ) {
							$sanitized_item[ $key ] = sanitize_text_field( $value );
						}
					}
					break;

				case 'audio':
					if ( isset( $item['audio'] ) && is_array( $item['audio'] ) ) {
						$sanitized_item['audio'] = array();
						if ( isset( $item['audio']['mime_type'] ) ) {
							$sanitized_item['audio']['mime_type'] = sanitize_mime_type( $item['audio']['mime_type'] );
						}
						// Base64 value should not be sanitized (it's binary data)
						if ( isset( $item['audio']['value'] ) ) {
							$sanitized_item['audio']['value'] = $item['audio']['value'];
						}
					}
					break;

				default:
					// For unknown types, sanitize all string values
					$sanitized_item = $item;
					foreach ( $sanitized_item as $key => $value ) {
						if ( is_string( $value ) && $key !== 'type' ) {
							$sanitized_item[ $key ] = sanitize_text_field( $value );
						}
					}
					break;
			}

			$sanitized[] = $sanitized_item;
		}

		return $sanitized;
	}

	/**
	 * Returns argument definition for validating chat UUID.
	 *
	 * Checks that the chat exists and belongs to the current user.
	 *
	 * @since 1.0.0
	 * @return array Argument schema for chat_uuid.
	 */
	public function get_chat_uuid_arg() {
		return array(
			'chat_uuid' => array(
				'type'              => 'string',
				'format'            => 'uuid',
				'validate_callback' => function ( $value, $request, $param ) {
					$chat = $this->chat_repository->get_by_uuid( $request->get_url_params()['chat_uuid'] );
					if ( ! ( $chat instanceof Chat ) ) {
						return false;
					}

					return $chat->has_participant( User_Manager::instance()->get_current_user() ) || $request->get_param('_current_tab') == 'transcripts' || $request->get_param('role') === Message::ROLE_ASSISTANT;
				}
			),
			'_current_tab' => array(
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			)
		);
	}

	private function get_error_code(  $e, $message ) {
		if ( $e instanceof \Limb_Chatbot\Includes\Exceptions\Exception ) {
			return $e->get_error_code();
		}
		if ( ! empty( $message ) && empty( $code ) ) {
			return Error_Codes::USER_MESSAGE_FAILED;
		} elseif ( empty( $message ) ) {
			return Error_Codes::AI_RESPONSE_FAILED;
		}

		return $code ?? Error_Codes::AI_RESPONSE_FAILED;
	}
}