<?php

namespace Limb_Chatbot\Includes\Api\V1\Controllers;

use Exception;
use Limb_Chatbot\Includes\Data_Objects\Action;
use Limb_Chatbot\Includes\Data_Objects\Chat;
use Limb_Chatbot\Includes\Data_Objects\Parameter;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Factories\Parameter_Callback_Handler_Factory;
use Limb_Chatbot\Includes\Repositories\Parameter_Repository;
use Limb_Chatbot\Includes\Services\Actions\Attachment_Media_Upload_Service;
use Limb_Chatbot\Includes\Services\Actions\Parameter_Service;
use Limb_Chatbot\Includes\Services\Collection;
use Limb_Chatbot\Includes\Services\Helper;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

/**
 * Controller for managing Parameters via REST API.
 *
 * Provides endpoints for CRUD operations on parameters including:
 * - GET /parameters - List all parameters with filtering and pagination
 * - POST /parameters - Create a new parameter
 * - GET /parameters/{id} - Get a single parameter by ID
 * - PUT /parameters/{id} - Update an existing parameter
 * - DELETE /parameters/{id} - Delete an parameter
 * - DELETE /parameters - Batch delete multiple parameters
 * - POST /parameters/batch - Batch create or update parameters
 *
 * @package Limb_Chatbot\Includes\Api\V1\Controllers
 * @since 1.0.0
 */
class Parameters_Controller extends Rest_Controller {

	/**
	 * REST route base.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	protected $rest_base = 'actions/(?P<action_id>[\d]+)/parameters';

	/**
	 * Parameter repository instance.
	 *
	 * @var Parameter_Repository
	 * @since 1.0.0
	 */
	protected Parameter_Repository $repository;
	protected Parameter_Service $parameter_service;
	protected Attachment_Media_Upload_Service $attachment_media_upload_service;

	/**
	 * Handler factory instance.
	 *
	 * @var Parameter_Callback_Handler_Factory
	 * @since 1.0.0
	 */
	private Parameter_Callback_Handler_Factory $handler_factory;

	/**
	 * Parameters_Controller constructor.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {
		$this->repository                      = new Parameter_Repository();
		$this->parameter_service               = new Parameter_Service();
		$this->attachment_media_upload_service = new Attachment_Media_Upload_Service();
		$this->handler_factory                 = new Parameter_Callback_Handler_Factory();
	}

	/**
	 * Registers REST routes for parameters.
	 *
	 * Defines routes for CRUD operations and batch operations.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function register_routes() {
		// GET /parameters - List all parameters
		// POST /parameters - Create a new parameter
		// DELETE /parameters - Batch delete parameters
		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_items' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => $this->get_action_id_arg(),
			),
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'create_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array_merge($this->get_action_id_arg(), $this->get_endpoint_args_for_item_schema()),
			),
			'schema' => array( $this, 'get_item_schema' ),
		) );

		// GET /parameters/{id} - Get single parameter
		// PUT /parameters/{id} - Update parameter
		// DELETE /parameters/{id} - Delete single parameter
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array(
					'id' => array(
						'required'          => true,
						'validate_callback' => function ( $value ) {
							return ! empty( Parameter::find( $value ) );
						},
					),
				),
			),
			array(
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'update_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array_merge( $this->get_action_id_arg(), $this->get_endpoint_args_for_item_schema() ),
			),
			array(
				'methods'             => WP_REST_Server::DELETABLE,
				'callback'            => array( $this, 'delete_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array(
					'id' => array(
						'required'          => true,
						'validate_callback' => function ( $value ) {
							return ! empty( Parameter::find( $value ) );
						},
					),
				),
			),
			'schema' => array( $this, 'get_item_schema' ),
		) );

		register_rest_route( $this->namespace, '/parameters/(?P<id>\d+)/callback', array(
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'handle_callback' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => $this->get_parameters_callback_args(),
			),
		) );

		register_rest_route( $this->namespace, '/parameters/(?P<id>\d+)/upload', array(
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'upload_attachment' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array(
					'id' => array(
						'required'          => true,
						'validate_callback' => function ( $value ) {
							return ! empty( Parameter::find( $value ) );
						},
					),
				),
			),
		) );
	}

	/**
	 * Returns query parameters for the collection endpoint.
	 *
	 * These parameters allow filtering and sorting of parameter results.
	 *
	 * @return array Array of query parameters accepted by the collection route.
	 * @since 1.0.0
	 */
	public function get_action_id_arg() {
		return array(
			'action_id' => array(
				'description'       => __( 'Filter parameters by associated action ID.', 'limb-chatbot' ),
				'type'              => 'integer',
				'required'          => true,
				'sanitize_callback' => 'absint',
				'validate_callback' => function ( $value ) {
					return ! empty( Action::find( $value ) );
				}
			),
		);
	}

	/**
	 * Returns a collection of parameter items.
	 *
	 * @param  WP_REST_Request  $request  REST request object.
	 *
	 * @return WP_REST_Response|WP_Error
	 * @since 1.0.0
	 */
	public function get_items( $request ) {
		try {
			$parameters = $this->repository->get_items( $request->get_param( 'action_id' ),
				$request->get_query_params() );
			$items      = $this->prepare_collection( $parameters, $request );

			return rest_ensure_response( $items ?? [] );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

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

	/**
	 * Creates a new parameter.
	 *
	 * @param  WP_REST_Request  $request  REST request with parameter data.
	 *
	 * @return WP_REST_Response|WP_Error
	 * @since 1.0.0
	 */
	public function create_item( $request ) {
		try {
			$action_id = $request->get_param( 'action_id' );
			$parameter = $this->parameter_service->create( $action_id, $request->get_json_params() );

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

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

	/**
	 * Prepares a single parameter item for REST response.
	 *
	 * @param  mixed  $item  The item to prepare (Parameter instance).
	 * @param  WP_REST_Request  $request  The full REST request object.
	 *
	 * @return Parameter|WP_Error The prepared item or WP_Error if invalid.
	 * @throws \Exception
	 * @since 1.0.0
	 */
	public function prepare_item( $item, $request ) {
		if ( is_a( $item, WP_Error::class ) ) {
			return $item;
		}
		if ( ! is_a( $item, Parameter::class ) ) {
			throw new \Exception( __( 'Unknown item type.', 'limb-chatbot' ) );
		}

		// Handle included relations
		$include = $request->get_param( 'include' );
		if ( ! empty( $include ) && is_array( $include ) ) {
			foreach ( $include as $relation ) {
				if ( method_exists( $item, $relation ) ) {
					$item->included[ $relation ] = $item->{$relation}();
				}
			}
		}

		return $item;
	}

	/**
	 * Returns a single parameter item by ID.
	 *
	 * @param  WP_REST_Request  $request  REST request object.
	 *
	 * @return WP_REST_Response|WP_Error
	 * @since 1.0.0
	 */
	public function get_item( $request ) {
		try {
			$parameter = $this->repository->get_item( $request->get_param( 'id' ) );

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

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

	/**
	 * Updates an existing parameter by ID.
	 *
	 * @param  WP_REST_Request  $request  REST request with update data.
	 *
	 * @return WP_REST_Response|WP_Error
	 * @since 1.0.0
	 */
	public function update_item( $request ) {
		try {
			$action_id = $request->get_param( 'action_id' );
			$id = $request->get_param('id');
			$params = array_merge(['action_id' => $action_id], $request->get_json_params());
			$parameter = $this->parameter_service->update($id, $params);


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

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

	/**
	 * Deletes an parameter by ID.
	 *
	 * @param  WP_REST_Request  $request  REST request object.
	 *
	 * @return WP_REST_Response|WP_Error
	 * @since 1.0.0
	 */
	public function delete_item( $request ) {
		try {
			$deleted = $this->repository->delete( $request->get_param( 'id' ) );

			return rest_ensure_response( array( 'deleted' => $deleted ) );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

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

	/**
	 * Returns the JSON schema for an parameter item.
	 *
	 * Defines structure and validation rules for the parameter resource.
	 *
	 * @return array JSON schema array.
	 * @since 1.0.0
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->schema;
		}

		$this->schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'parameter',
			'type'       => 'object',
			'properties' => array(
				'id'               => array(
					'description' => __( 'Unique identifier for the parameter.', 'limb-chatbot' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'name'             => array(
					'description' => __( 'Parameter name.', 'limb-chatbot' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'required'    => false,
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_text_field',
						'validate_callback' => function ( $value, $request ) {
							return is_string( $value ) && ! empty( $value );
						},
					),
				),
				'order'             => array(
					'description' => __( 'Order.', 'limb-chatbot' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit' ),
					'required'    => false,
					'arg_options' => array(
						'sanitize_callback' => 'absint',
						'validate_callback' => function ( $value, $request ) {
							return !empty($value) && $value > 0;
						},
					),
				),
				'label'            => array(
					'description' => __( 'AI instructions for the parameter.', 'limb-chatbot' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'required'    => false,
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_textarea_field',
						'validate_callback' => function ( $value ) {
							return is_string( $value ) && ! empty( $value );
						},
					),
				),
				'type'             => array(
					'description' => __( 'Parameter type.', 'limb-chatbot' ),
					'type'        => 'string',
					'enum'        => Parameter::get_supported_type_inputs(),
					'context'     => array( 'view', 'edit' ),
					'required'    => false,
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_text_field',
						'validate_callback' => function ( $value ) {
							return in_array( Parameter::normalize_type( (string) $value ), Parameter::get_supported_types(), true );
						},
					),
				),
				'required'         => array(
					'description' => __( 'Whether the parameter is required.', 'limb-chatbot' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit' ),
					'default'     => false,
					'required'    => true,
					'arg_options' => array(
						'sanitize_callback' => 'absint',
						'validate_callback' => function ( $value ) {
							return in_array( $value, [ 1, 0 ] );
						},
					),
				),
				'question'         => array(
					'description' => __( 'Question to ask the user for this parameter.', 'limb-chatbot' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'required'    => false,
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_text_field',
						'validate_callback' => function ( $value ) {
							return is_string( $value );
						},
					),
				),
				'default_value'    => array(
					'description' => __( 'Default value for the parameter.', 'limb-chatbot' ),
					'type'        => [ 'string', 'number', 'integer', 'boolean', 'array', 'object', 'null' ],
					'context'     => array( 'view', 'edit' ),
					'required'    => false,
					'arg_options' => array(
						'validate_callback' => function ( $value ) {
							return true;// todo
						},
					),
				),
				'placeholder'      => array(
					'description' => __( 'Default value for the parameter.', 'limb-chatbot' ),
					'context'     => array( 'view', 'edit' ),
					'type'        => 'string',
					'required'    => false,
					'arg_options' => array(
						'validate_callback' => function ( $value ) {
							return is_string( $value );
						},
					),
				),
				'suggestions'      => array(
					'description' => __( 'Suggestions for the parameter value.', 'limb-chatbot' ),
					'type'        => 'array',
					'context'     => array( 'view', 'edit' ),
					'required'    => false,
					'arg_options' => array(
						'validate_callback' => function ( $value ) {
							return is_array( $value );
						},
					)
				),
				'validation_rules' => array(
					'description' => __( 'Validations for the parameter.', 'limb-chatbot' ),
					'type'        => ['object', 'array'],
					'context'     => array( 'view', 'edit' ),
					'required'    => false,
					'arg_options' => array(
						'validate_callback' => function ( $value ) {
							return is_array( $value );
						},
					)
				),
				'config'           => array(
					'description' => __( 'Validations for the parameter.', 'limb-chatbot' ),
					'type'        => ['object', 'array'],
					'context'     => array( 'view', 'edit' ),
					'required'    => false,
					'arg_options' => array(
						'validate_callback' => function ( $value ) {
							return is_array( $value );
						},
					)
				),
			)
		);

		return $this->schema;
	}


	/**
	 * Handles parameter callback request.
	 *
	 * Routes the request to the appropriate handler based on parameter type.
	 * Currently supports TYPE_VECTOR_SEARCH.
	 *
	 * @param  WP_REST_Request  $request  The request object.
	 *
	 * @return WP_REST_Response|WP_Error Response with results or error.
	 * @since 1.0.0
	 */
	public function handle_callback( WP_REST_Request $request ) {
		try {
			// Get and validate parameter ID
			$parameter_id = absint( $request->get_param( 'id' ) );
			if ( $parameter_id <= 0 ) {
				throw new \Limb_Chatbot\Includes\Exceptions\Exception( Error_Codes::VALIDATION_INVALID_VALUE, __( 'Invalid parameter(s): id', 'limb-chatbot' ) );
			}

			// Get parameter by ID
			$parameter = Parameter::find( $parameter_id );
			if ( ! $parameter ) {
				throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE, __( 'Invalid parameter(s): id', 'limb-chatbot' ));
			}

			// Validate parameter is TYPE_VECTOR_SEARCH
			if ( Parameter::TYPE_VECTOR_SEARCH !== $parameter->get_type() ) {
				throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE, sprintf( __( 'Parameter type "%s" is not supported for callbacks', 'limb-chatbot' ), $parameter->get_type() ), );
			}

			// Get and validate chat UUID
			$chat_uuid = sanitize_text_field( $request->get_param( 'chat_uuid' ) );
			if ( empty( $chat_uuid ) ) {
				throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE, __( 'Invalid parameter(s): chat_uuid', 'limb-chatbot' ), );
			}

			// Get chat by UUID
			$chat = Chat::where( array( 'uuid' => $chat_uuid ) )->first();
			if ( ! $chat ) {
				throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE, __( 'Invalid parameter(s): chat_uuid', 'limb-chatbot' ), );
			}

			// Inject parameter and chat into request for handler
			$request->set_param( '_parameter', $parameter );
			$request->set_param( '_chat', $chat );

			// Get handler from factory
			$handler = $this->handler_factory->make( $parameter );

			// Handle the callback
			return $handler->handle( $request );
		} catch ( Exception $e ) {
			Helper::log( $e, __METHOD__ );

			// Use Helper to convert exceptions to WP_Error with standard format
			return Helper::get_wp_error( $e );
		}
	}

	private function get_parameters_callback_args() {
		return array(
			'id'             => array(
				'description'       => __( 'Parameter ID', 'limb-chatbot' ),
				'type'              => 'integer',
				'required'          => true,
				'validate_callback' => function ( $param ) {
					return ! empty ( Parameter::find( $param ) );
				},
			),
			'chat_uuid'      => array(
				'description'       => __( 'Chat UUID identifier', 'limb-chatbot' ),
				'type'              => 'string',
				'required'          => true,
				'validate_callback' => function ( $param ) {
					return ! empty( Chat::find_by_uuid( $param ) );
				},
				'sanitize_callback' => 'sanitize_text_field',
			),
			'search_keyword' => array(
				'description'       => __( 'Search keyword (required for vector search)', 'limb-chatbot' ),
				'type'              => 'string',
				'required'          => false,
				'sanitize_callback' => 'sanitize_text_field',
			),
		);
	}

	/**
	 * Handle attachment parameter file upload.
	 *
	 * @param  WP_REST_Request  $request
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function upload_attachment( WP_REST_Request $request ) {
		try {
			$parameter = Parameter::find( (int) $request->get_param( 'id' ) );
			if ( ! $parameter instanceof Parameter ) {
				throw new Exception( Error_Codes::NOT_FOUND, __( 'Parameter not found.', 'limb-chatbot' ) );
			}
			if ( $parameter->get_type() !== Parameter::TYPE_ATTACHMENT ) {
				throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE,
					__( 'Upload is only supported for attachment parameters.', 'limb-chatbot' ) );
			}

			$attachments = $this->attachment_media_upload_service->upload_for_parameter( $parameter, $request->get_file_params() );

			return rest_ensure_response( [
				'items' => $attachments,
			] );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

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

}
