<?php

namespace Limb_Chatbot\Includes\Api\V1\Controllers;

use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Config_Dependent_Interface;
use Limb_Chatbot\Includes\Data_Objects\Config;
use Limb_Chatbot\Includes\Data_Objects\Vector;
use Limb_Chatbot\Includes\Data_Objects\Vector_Index;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Repositories\Config_Repository;
use Limb_Chatbot\Includes\Services\Collection;
use Limb_Chatbot\Includes\Services\Config_Service;
use Limb_Chatbot\Includes\Services\Helper;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

/**
 * Class Configs_Controller
 *
 * REST controller for managing configuration items.
 *
 * @package Limb_Chatbot\Includes\Api\V1\Controllers
 * @since 1.0.0
 */
class Configs_Controller extends Rest_Controller {

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

	/**
	 * Service instance to handle config business logic.
	 *
	 * @var Config_Service
	 * @since 1.0.0
	 */
	protected $service;

	/**
	 * Configs_Controller constructor.
	 *
	 * Initializes the controller with the Config_Service.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {
		$this->service = new Config_Service( ( new Config_Repository() ) );
	}

	/**
	 * Registers REST API routes for configuration CRUD.
	 *
	 * Defines routes for create, read, update, delete, and schema.
	 *
	 * @return void
	 * @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' ),
				'args'                => array_merge( $this->get_collection_params(), array(
					'mask' => array(
						'type' => 'boolean',
					)
				) ),
				'permission_callback' => array( $this, 'permission_callback' ),
			),
			// Register our schema callback.
			'schema' => array( $this, 'get_item_schema' ),
		) );
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/test', array(
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'test_params' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array(
					'related_to' => array(
						'type'     => 'string',
						'required' => true,
					),
					'params'     => array(
						'type'     => 'object',
						'required' => true,
					),
				),
			),
		) );
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<config_id>[\d]+)', array(
			array(
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'update_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array_merge( array(
					'config_id' => array(
						'validate_callback' => function ( $value ) {
							return ! empty( Config::count( [ 'id' => $value ] ) );
						}
					)
				), $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ) ),
			),
			// Register our schema callback.
			'schema' => array( $this, 'get_item_schema' ),
		) );
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<config_id>[\d]+)', array(
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array(
					'config_id' => array(
						'validate_callback' => function ( $param ) {
							return Config::count( [ 'id' => $param ] );
						}
					),
					'mask'      => array(
						'type' => 'boolean',
					)
				),
			),
			array(
				'methods'             => WP_REST_Server::DELETABLE,
				'callback'            => array( $this, 'delete_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => $this->get_delete_args()
			)
		) );
	}


	/**
	 * Checks whether the current user has permission to access the endpoint.
	 *
	 * Allows only users with 'manage_options' capability.
	 *
	 * @param  WP_REST_Request  $request  Incoming REST request.
	 *
	 * @return bool True if user can access, 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' );
	}

	/**
	 * Validates the 'related_to' parameter to ensure there are dependent config instances.
	 *
	 * @param  string  $value  The related_to value.
	 *
	 * @return bool True if valid, false otherwise.
	 * @since 1.0.0
	 */
	public function validate_related_to( $value ) {
		return ! empty( Config::get_config_dependant_instances( $value ) );
	}

	/**
	 * Creates a new config item.
	 *
	 * @param  WP_REST_Request  $request  REST request with item data.
	 *
	 * @return WP_REST_Response|WP_Error REST response or WP_Error on failure.
	 * @since 1.0.0
	 */
	public function create_item( $request ) {
		try {
			$config = $this->service->create( $request->get_json_params() );
			$config = Config::find($config->get_id());
			$config = $this->prepare_item_for_response( $config, $request );

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

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

	/**
	 * Updates an existing config item.
	 *
	 * @param  WP_REST_Request  $request REST request containing config ID and update data.
	 *
	 * @return WP_REST_Response|WP_Error REST response or WP_Error on failure.
	 * @since 1.0.0
	 */
	public function update_item( $request ) {
		try {
			$config = $this->prepare_item_for_response( $this->service->update( $request->get_param( 'config_id' ), $request->get_json_params() ), $request );

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

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

	/**
	 * Retrieves a single config item by ID.
	 *
	 * @param  WP_REST_Request  $request  REST request with config ID parameter.
	 *
	 * @return WP_REST_Response|WP_Error REST response or WP_Error on failure.
	 * @since 1.0.0
	 */
	public function get_item( $request ) {
		try {
			$config = $this->service->get_item( $request->get_param( 'config_id' ), $request->get_query_params() );

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

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

	/**
	 * Deletes a config item by ID.
	 *
	 * @param  WP_REST_Request  $request  REST request containing config ID.
	 *
	 * @return WP_REST_Response|WP_Error REST response or WP_Error on failure.
	 * @since 1.0.0
	 */
	public function delete_item( $request ) {
		try {
			$id     = $request->get_url_params()['config_id'];
			$config = Config::find( $id );
			if ( ! $config instanceof Config ) {
				throw new Exception( Error_Codes::NOT_FOUND, __( 'The API Key not found', 'limb-chatbot' ) );
			}

			return rest_ensure_response( $this->service->delete($config) );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

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

	/**
	 * Retrieves a collection of config items.
	 *
	 * @param  WP_REST_Request  $request  REST request with optional filters.
	 *
	 * @return WP_REST_Response|WP_Error REST response or WP_Error on failure.
	 * @since 1.0.0
	 */
	public function get_items( $request ) {
		try {
			$items = $this->service->get_items( $request->get_query_params() );

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

			return Helper::get_wp_error( $e );
		}

	}

	/**
	 * Returns the JSON schema for the config resource.
	 *
	 * Defines the structure, properties, and validation rules.
	 *
	 * @return array JSON schema array.
	 * @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'      => 'config',
			'type'       => 'object',
			'properties' => array(
				'id'          => array(
					'description' => __( 'Unique identifier for the resource.', 'limb-chatbot' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'title'       => array(
					'type'        => 'string',
					'description' => 'Config title',
				),
				'description' => array(
					'type'        => 'string',
					'description' => 'Config description',
				),
				'params'      => array(
					'type'        => 'object',
					'properties'  => array(
						'api_key' => array(
							'type'        => 'string',
							'description' => 'API Key'
						)
					),
					'readonly'    => false,
					'arg_options' => array(
						'required'          => true,
						'validate_callback' => array( $this, 'validate_params' ),
					)
				),
				'related_to'  => array(
					'type'        => 'string',
					'readonly'    => false,
					'arg_options' => array(
						'validate_callback' => array( $this, 'validate_related_to' )
					)
				),
				'default'     => array(
					'type' => 'boolean',
				)
			),
		);

		return $this->schema;
	}

	/**
	 * Prepares a config item for response.
	 *
	 * Masks sensitive fields like api_key if requested.
	 *
	 * @param mixed            $item    Config item or WP_Error.
	 * @param WP_REST_Request $request  REST request instance.
	 *
	 * @return mixed Prepared item or error.
	 * @since 1.0.0
	 */
	public function prepare_item_for_response( $item, $request ) {
		if ( is_a( $item, WP_Error::class ) ) {
			return $item;
		}
		if ( ! $item instanceof Config ) {
			return $item;
		}
		if ( $request->get_params()['mask'] ?? true ) {
			$item = $item->mask_params();
		}

		return $item;
	}

	/**
	 * Prepares a collection of config items for response.
	 *
	 * Sets total count and applies item preparation.
	 *
	 * @param Collection $collection Collection of items.
	 * @param WP_REST_Request  $request  REST request instance.
	 *
	 * @return Collection Prepared collection.
	 * @since 1.0.0
	 */
	public function prepare_collection( $collection, $request ) {
		$collection = parent::prepare_collection( $collection, $request );
		$params     = $request->get_query_params();
		$collection->set_total( Config::count( $params ) );
		if ( ! $collection->is_empty() ) {
			$collection = $collection->map( function ( Config $item ) use ( $request ) {
				return $this->prepare_item_for_response( $item, $request );
			} );
		}

		return $collection;
	}

	/**
	 * Returns argument definitions for delete requests.
	 *
	 * Includes validation callbacks for config_id and force delete flag.
	 *
	 * @since 1.0.0
	 *
	 * @return array Array of argument definitions.
	 */
	private function get_delete_args() {
		return array(
			'config_id' => array(
				'validate_callback' => function ( $param ) {
					return Config::count( [ 'id' => $param ] );
				}
			),
			'force'     => array(
				'validate_callback' => function ( $param, $request ) {
					if ( ! $param ) {
						return true;
					}
					$config_id = $request->get_param( 'config_id' );
					if ( ! Vector_Index::where( [ 'config_id' => $config_id ] )->is_empty() ) {
						return false;
					}
					if ( ! Vector::where( [ 'config_id' => $config_id ] )->is_empty() ) {
						return false;
					}

					//...
					return true;
				}
			)
		);
	}

	/**
	 * Validates the params field of a config item.
	 *
	 * Validates API key and checks config dependency validity.
	 *
	 * @param array            $value   Params array from request.
	 * @param  WP_REST_Request $request REST request instance.
	 *
	 * @return true|WP_Error True if valid, WP_Error otherwise.
	 * @since 1.0.0
	 */
	public function validate_params( array $value, $request ) {
		try {
			if ( empty( $value['api_key'] ) ) {
				return true;
			}
			if ( empty( $request->get_param( 'related_to' ) ) ) {
				return new WP_Error( Error_Codes::VALIDATION_REQUIRED, __( 'Related to is required.', 'limb-chatbot' ) );
			}
			if ( ! ( $class = Config::get_config_dependant_instances( $request->get_param( 'related_to' ) ) ) ) {
				return new WP_Error( Error_Codes::VALIDATION_INVALID_VALUE, __( 'Related to is invalid.', 'limb-chatbot' ) );
			}
			if ( ( $instance = new $class() ) instanceof Config_Dependent_Interface ) {
				if ( ! $instance->is_valid_config( Config::make( [ 'params' => $value ] ) ) ) {
					return new WP_Error( Error_Codes::VALIDATION_INVALID_VALUE, __( 'Api key is invalid', 'limb-chatbot' ) );
				}
			} else {
				return new WP_Error( Error_Codes::VALIDATION_INVALID_VALUE, __( 'The related object is not config dependent', 'limb-chatbot' ) );
			}

			return true;
		} catch ( Exception $e ) {
			if ( $e->getCode() === 10201 || $e->getCode() === 10700 || $e->getCode() === 10009 ) {
				$e = new Exception( 10201,
					__( 'API authentication failed. Please check your API key.', 'limb-chatbot' ) );
			}
			Helper::log( $e, __METHOD__ );

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

	/**
	 * Tests config params validation for a related object.
	 *
	 * @param  WP_REST_Request  $request REST request instance.
	 *
	 * @return WP_REST_Response|WP_Error Validation test response.
	 * @since 2.0.0
	 */
	public function test_params( WP_REST_Request $request ) {
		$validation = $this->validate_params( (array) $request->get_param( 'params' ), $request );
		if ( is_wp_error( $validation ) ) {
			return rest_ensure_response( $validation );
		}

		return rest_ensure_response( array(
			'success' => true,
			'message' => __( 'Params are valid.', 'limb-chatbot' ),
		) );
	}
}