<?php

namespace Limb_Chatbot\Includes\Api\V1\Controllers;

use Limb_Chatbot\Includes\Data_Objects\Config;
use Limb_Chatbot\Includes\Data_Objects\AI_Model;
use Limb_Chatbot\Includes\Data_Objects\Vector;
use Limb_Chatbot\Includes\Data_Objects\Vector_Index;
use Limb_Chatbot\Includes\Repositories\Vector_Repository;
use Limb_Chatbot\Includes\Services\Helper;
use Limb_Chatbot\Includes\Services\Vector_Service;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;

/**
 * REST API Controller for Vectors.
 *
 * Provides endpoints to create, read, update, delete, upsert vectors.
 *
 * @since 1.0.0
 */
class Vectors_Controller extends Rest_Controller {

	/**
	 * REST API base route.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	protected $rest_base = 'vectors';

	/**
	 * Vector Service instance.
	 *
	 * @var Vector_Service
	 * @since 1.0.0
	 */
	protected Vector_Service $vector_service;

	/**
	 * Constructor.
	 *
	 * Initializes Vector_Service with Vector repository.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {
		$this->vector_service = new Vector_Service( new Vector_Repository() );
	}

	/**
	 * Register REST API routes for vectors.
	 *
	 * @since 1.0.0
	 */
	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'                => $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'                => $this->get_collection_params(),
			),
			'schema' => array( $this, 'get_item_schema' ),
		) );
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<vector_id>[\d]+)', array(
			array(
				'methods'             => \WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'update_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array_merge( array(
					'vector_id' => array(
						'description'       => __( 'Vector id', 'limb-chatbot' ),
						'type'              => 'int',
						'required'          => true,
						'context'           => array( 'view', 'edit' ),
						'validate_callback' => array( $this, 'validate_vector_id' ),
					)
				), $this->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ) ),
			),
			'schema' => array( $this, 'get_item_schema' ),
		) );
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<vector_id>[\d]+)', array(
			array(
				'methods'             => \WP_REST_Server::DELETABLE,
				'callback'            => array( $this, 'delete_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array_merge( array(
					'vector_id'    => array(
						'description'       => __( 'Vector id', 'limb-chatbot' ),
						'type'              => 'int',
						'required'          => true,
						'context'           => array( 'view', 'edit' ),
						'validate_callback' => array( $this, 'validate_vector_id' ),
					),
					'force_delete' => array(
						'description' => __( 'Vector id', 'limb-chatbot' ),
						'type'        => 'boolean',
						'context'     => array( 'edit' )
					)
				) ),
			),
			'schema' => array( $this, 'get_item_schema' ),
		) );
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<vector_id>[\d]+)/upsert', array(
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'upsert_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array(
					'vector_id'       => array(
						'description'       => __( 'Vector id', 'limb-chatbot' ),
						'type'              => 'int',
						'required'          => true,
						'context'           => array( 'view', 'edit' ),
						'validate_callback' => array( $this, 'validate_vector_id' ),
					),
					'vector_index_id' => array(
						'description'       => __( 'Index id', 'limb-chatbot' ),
						'type'              => 'int',
						'required'          => true,
						'context'           => array( 'view', 'edit' ),
						'validate_callback' => array( $this, 'validate_vector_index_id' ),
					)
				),
			)
		) );
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/upsert/batch', array(
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'upsert_batch' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array(
					'vector_index_id' => array(
						'description'       => __( 'Index id', 'limb-chatbot' ),
						'type'              => 'int',
						'required'          => true,
						'context'           => array( 'view', 'edit' ),
						'validate_callback' => array( $this, 'validate_vector_index_id' ),
					),
					'vector_ids'      => array(
						'description'       => __( 'Vector ids', 'limb-chatbot' ),
						'type'              => 'array',
						'required'          => true,
						'context'           => array( 'view', 'edit' ),
						'validate_callback' => array( $this, 'validate_vector_id' ),
					),
				),
			)
		) );
	}

	/**
	 * Permission callback to check if current user can manage options.
	 *
	 * @param  WP_REST_Request  $request  REST request.
	 *
	 * @return bool True if user has permission, false otherwise.
	 * @since 1.0.0
	 */
	public function permission_callback( $request ): bool {
		if ( ! parent::permission_callback( $request ) ) {
			return false;
		}

		return current_user_can( 'manage_options' );
	}


	/**
	 * Retrieve a collection of vectors.
	 *
	 * @param  WP_REST_Request  $request  REST request.
	 *
	 * @return WP_REST_Response|WP_Error Response or error.
	 * @since 1.0.0
	 */
	public function get_items( $request ) {
		try {
			$collection = ( new Vector_Repository() )->get_items( $request->get_query_params() );
			$collection = $this->prepare_collection( $collection, $request );

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

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Create a new vector.
	 *
	 * @param  WP_REST_Request  $request  REST request.
	 *
	 * @return WP_REST_Response|WP_Error Response or error.
	 * @since 1.0.0
	 */
	public function create_item( $request ) {
		try {
			return $this->prepare_item_for_response( $this->vector_service->create( $request->get_json_params() ), $request );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Update a vector by ID.
	 *
	 * @param  WP_REST_Request  $request  REST request.
	 *
	 * @return WP_REST_Response|WP_Error Response or error.
	 * @since 1.0.0
	 */
	public function update_item( $request ) {
		try {
			$res = $this->vector_service->update( Vector::find( $request->get_param( 'vector_id' ) ), $request->get_json_params() );

			return $this->prepare_item_for_response( $res, $request );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Delete a vector by ID.
	 *
	 * @param  WP_REST_Request  $request  REST request.
	 *
	 * @return WP_REST_Response|WP_Error Response or error.
	 * @since 1.0.0
	 */
	public function delete_item( $request ) {
		try {
			return $this->prepare_item_for_response( $this->vector_service->delete( Vector::find( $request->get_param( 'vector_id' ) ) ), $request );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Upsert (insert or update) a vector with an index.
	 *
	 * @param WP_REST_Request $request REST request.
	 *
	 * @return WP_REST_Response|WP_Error Response or error.
	 * @since 1.0.0
	 */
	public function upsert_item( $request ) {
		try {
			$result = $this->vector_service->upsert( Vector::find( $request->get_param( 'vector_id' ) ), Vector_Index::find( $request->get_json_params()['vector_index_id'] ) );

			return $this->prepare_item_for_response( $result, $request );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Batch upsert vectors for an index.
	 *
	 * @param WP_REST_Request $request REST request.
	 *
	 * @return WP_REST_Response|WP_Error Response or error.
	 * @since 1.0.0
	 */
	public function upsert_batch( $request ) {
		try {
			$body = $request->get_json_params();
			$res  = $this->vector_service->upsert_batch( Vector_Index::find( $body['vector_index_id'] ), Vector::where( [ 'id' => $body['vector_ids'] ] )->get() );

			return $this->prepare_item_for_response( $res, $request );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return Helper::get_wp_error( $e );
		}
	}

	/**
	 * Get JSON schema for vector resource.
	 *
	 * @return array JSON schema definition.
	 * @since 1.0.0
	 */
	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'      => 'vector',
			'type'       => 'object',
			'properties' => array(
				'id'          => array(
					'description' => __( 'Unique identifier for the resource.', 'limb-chatbot' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true
				),
				'input'       => array(
					'description' => __( 'Origin of the resource.', 'limb-chatbot' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'arg_options' => array(// 'sanitize_callback' => 'wp_filter_post_kses',
					),
				),
				'values'      => array(
					'description' => __( 'Vector representation of the resource', 'limb-chatbot' ),
					'type'        => 'array',
					'context'     => array( 'view', 'edit' ),
					'arg_options' => array(// 'sanitize_callback' => 'wp_filter_post_kses',
					),
				),
				'dimension'   => array(
					'description' => __( 'Vector dimension', 'limb-chatbot' ),
					'required'    => true,
					'type'        => 'integer',
					'context'     => array( 'view', 'edit' ),
				),
				'object_id'   => array(
					'description' => __( 'Identifier of the object which could be the origin of the vector', 'limb-chatbot' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit' ),
					'arg_options' => array(
						'validate_callback' => function ( $value, $request ) {
							if(!empty($type = $request->get_param('object_type'))){
								$function = $type === 'term' ? 'get_term' : 'get_post';
								if (function_exists($function)) {
									return !empty(call_user_func($function, $value));
								}
								return false;
							}
							return true;
						}
					),
				),
				'object_type' => array(
					'description' => __( 'Type of the object which could be origin of the vector', 'limb-chatbot' ),
					'type'        => 'string',
					'enum'        => array( 'post', 'term' ),
					'readonly'    => true,
					'context'     => array( 'view', 'edit' ),
					'arg_options' => array(
						'validate_callback' => function ( $value, $request ) {
							if ( ! empty( $request->get_param( 'object_id' ) ) ) {
								return ! empty( $value ) && post_type_exists( $value );
							}

							return true;
						}
					),
				),
				'object_fields'      => array(
					'description' => __( 'Fields of the object which which will be stringify', 'limb-chatbot' ),
					'type'        => 'array',
					'context'     => array( 'view', 'edit' ),
					'arg_options' => array(),
				),
				'ai_model_id'    => array(
					'description' => __( 'AI_Model id on basis of which the vector is generated', 'limb-chatbot' ),
					'required'    => true,
					'type'        => 'integer',
					'context'     => array( 'view', 'edit' ),
					'arg_options' => array(
						'validate_callback' => function ( $value ) {
							return ! empty( AI_Model::count( [ 'id' => $value ] ) );
						}
					)
				),
				'config_id'   => array(
					'description' => __( 'Keys configuration id', 'limb-chatbot' ),
					'required'    => true,
					'type'        => 'integer',
					'context'     => array( 'view', 'edit' ),
					'arg_options' => array(
						'validate_callback' => function ( $value ) {
							return ! empty( Config::count( [ 'id' => $value ] ) );
						}
					)
				)
			),
		);

		return $this->schema;
	}

	/**
	 * Get collection parameters for query.
	 *
	 * @return array Collection parameters.
	 * @since 1.0.0
	 */
	public function get_collection_params() {
		return array_merge( parent::get_collection_params(), array(
			'include' => array(
				'description' => __( 'Include vector relations.', 'limb-chatbot' ),
				'type'        => 'array'
			)
		) );
	}

	/**
	 * Validate vector index ID.
	 *
	 * @param  int  $value  Vector index ID.
	 *
	 * @return bool True if valid, false otherwise.
	 * @since 1.0.0
	 */
	public function validate_vector_index_id( $value ): bool {
		return (bool) Vector_Index::count( [ 'id' => $value ] );
	}

	/**
	 * Validate vector ID or array of IDs.
	 *
	 * @param  int|int[]  $value  Vector ID or array of vector IDs.
	 *
	 * @return bool True if valid, false otherwise.
	 * @since 1.0.0
	 */
	public function validate_vector_id( $value ): bool {
		return is_array( $value ) ? ( ! empty( $value ) && Vector::count( [ 'id' => $value ] ) == count( $value ) ) : ( bool ) Vector::count( [ 'id' => $value ] );
	}

	/**
	 * Prepare a vector item for REST response.
	 *
	 * @param  mixed  $item  Vector object or WP_Error.
	 * @param  WP_REST_Request  $request  REST request.
	 *
	 * @return WP_REST_Response|WP_Error Prepared response or error.
	 * @since 1.0.0
	 */
	public function prepare_item_for_response( $item, $request ) {
		if ( is_a( $item, WP_Error::class ) ) {
			return $item;
		}

		// Extra manipulations can be made here
		return rest_ensure_response( $item );
	}
}