<?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\Live_Agent_Service;
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'] );
			$message = $this->message_service_factory->make( $chat )->create( $request->get_json_params(), (bool) $request->get_param( 'preview' ) );
			if ( $this->is_stream_waiting( $request ) ) {
				Stream_Event_Service::user_message_saved( $message );
			}
			if ( $chat->is_live_agent_active() ) {
				if ( $message->is_live_agent_disconnection_request() ) {
					$res = ( new Live_Agent_Service() )->disconnect_live_agent( $chat );
					$res->inject_previous_message( $message );
				} else {
					$res = $message;
				}

				return rest_ensure_response( $res );
			}
			if ( $message instanceof Message ) {
				$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() );
			}

			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 ( ! isset( $item['attachment']['mime_type'] ) || ! in_array( $item['attachment']['mime_type'],
								Message::get_allowed_mime_types() ) ) {
							return false;
						}
						if ( ! isset( $item['attachment']['value'] ) || ! Helper::is_valid_base64_image( $item['attachment']['value'] ) ) {
							return false;
						}
						$base_64_data = $item['attachment']['value'];
						$decoded_data = base64_decode( $base_64_data );
						$file_size = strlen( $decoded_data );
						$max_file_size = 5 * 1024 * 1024; // read from settings
						if ( $file_size > $max_file_size ) {
							return false;
						}
						break;
					case 'live_agent_disconnection':
					case 'action_cancellation':
					case 'parameter_value':
						return true;
					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();
						// MIME type should be sanitized
						if ( isset( $item['attachment']['mime_type'] ) ) {
							$sanitized_item['attachment']['mime_type'] = sanitize_mime_type( $item['attachment']['mime_type'] );
						}
						// Base64 value should not be sanitized (it's binary data), but we validate it
						if ( isset( $item['attachment']['value'] ) ) {
							$sanitized_item['attachment']['value'] = $item['attachment']['value'];
						}
					}
					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 'live_agent_disconnection':
					if ( isset( $item['live_agent_disconnection'] ) && is_array( $item['live_agent_disconnection'] ) ) {
						$sanitized_item['live_agent_disconnection'] = array();
						if ( isset( $item['live_agent_disconnection']['text'] ) ) {
							$sanitized_item['live_agent_disconnection']['text'] = wp_kses_post( $item['live_agent_disconnection']['text'] );
						}
						// Preserve other properties
						foreach ( $item['live_agent_disconnection'] as $key => $value ) {
							if ( $key !== 'text' && ! isset( $sanitized_item['live_agent_disconnection'][ $key ] ) ) {
								$sanitized_item['live_agent_disconnection'][ $key ] = is_string( $value ) ? sanitize_text_field( $value ) : $value;
							}
						}
					}
					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') == 'chats';
				}
			)
		);
	}

	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;
	}
}