<?php

namespace Limb_Chatbot\Includes\Api\V1\Controllers;

use Limb_Chatbot\Includes\Data_Objects\Setting;
use Limb_Chatbot\Includes\Data_Schemas;
use Limb_Chatbot\Includes\Services\Helper;
use Limb_Chatbot\Includes\Repositories\Setting_Repository;
use Limb_Chatbot\Includes\Services\Sanitizer;
use Limb_Chatbot\Includes\Services\Validator;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;


/**
 * Controller for managing settings via REST API.
 *
 * Handles CRUD and batch operations on settings.
 *
 * @since 1.0.0
 */
class Settings_Controller extends Rest_Controller {

	/**
	 * Repository for settings data access.
	 *
	 * @var Setting_Repository
	 * @since 1.0.0
	 */
	protected Setting_Repository $setting_repository;

	/**
	 * Schema validator for settings.
	 *
	 * @var Data_Schemas\Setting
	 * @since 1.0.0
	 */
	protected Data_Schemas\Setting $setting_schema;

	/**
	 * Sanitizer instance for input sanitation.
	 *
	 * @var Sanitizer
	 * @since 1.0.0
	 */
	protected Sanitizer $sanitize;

	/**
	 * Validator instance for input validation.
	 *
	 * @var Validator
	 * @since 1.0.0
	 */
	protected Validator $validator;

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

	/**
	 * Constructor.
	 *
	 * Initializes repository, schema, sanitizer, and validator.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {
		$this->setting_repository = new Setting_Repository();
		$this->setting_schema     = new Data_Schemas\Setting();
		$this->sanitize           = new Sanitizer( $this->setting_schema );
		$this->validator          = new Validator( $this->setting_schema );
	}

	/**
	 * Registers REST API routes for settings.
	 *
	 * @since 1.0.0
	 */
	public function register_routes() {
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch/', array(
			array(
				'methods'             => \WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'batch_update_items' ),
				'permission_callback' => array( $this, 'permission_callback' ),
			),
		) );
		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' ),
			),
			array(
				'methods'             => \WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'create_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => $this->get_endpoint_args_for_item_schema()
			),
			array(
				'methods'             => \WP_REST_Server::DELETABLE,
				'callback'            => array( $this, 'delete_items' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => [
					'suffix' => [
						'required'          => true,
						'validate_callback' => function ( $param ) {
							return $param === 'preview';
						},
						'sanitize_callback' => 'sanitize_text_field',
					],
				],
			),
			'schema' => array( $this, 'get_item_schema' ),
		) );
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<key>[' . Setting::KEY_REGEXP . ']+)', array(
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
			),
			array(
				'methods'             => \WP_REST_Server::DELETABLE,
				'callback'            => array( $this, 'delete_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array(
					'uuid' => array(
						'type'              => 'string',
						'format'            => 'uuid',
						'validate_callback' => function ( $value, $request ) {
							$existing_value = Setting::find( $request->get_url_params()['key'] )->get_value();
							$uuids          = is_array( $existing_value ) ? array_column( $existing_value, 'uuid' ) : null;
							if ( is_null( $uuids ) || ! in_array( $value, $uuids, true ) ) {
								return false;
							}

							return true;
						},
					)
				)
			),
			array(
				'methods'             => \WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'update_item' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => $this->get_endpoint_args_for_item_schema()
			),
			'schema' => array( $this, 'get_item_schema' ),
		) );
	}

	/**
	 * Checks permission for current user to access settings.
	 *
	 * Requires 'manage_options' capability.
	 *
	 * @param  WP_REST_Request  $request  Incoming 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' );
	}

	/**
	 * Creates a new setting.
	 *
	 * Sanitizes and validates input before creation.
	 *
	 * @param  WP_REST_Request  $request  Incoming REST request.
	 *
	 * @return WP_REST_Response|WP_Error REST response or error.
	 * @since 1.0.0
	 */
	public function create_item( $request ) {
		try {
			$data = $request->get_json_params();
			$this->sanitize->sanitize( [ $data['key'] => $data['value'] ], 'create', (bool) $request->get_param( 'preview' ), [ $data['key'] ] );
			$this->validator->validate( $this->sanitize->get_sanitized(), 'create', (bool) $request->get_param( 'preview' ), [ $data['key'] ] );
			if ( $this->validator->has_errors() ) {
				return rest_ensure_response( $this->validator->get_error() );
			}

			return rest_ensure_response( $this->setting_repository->create( [ 'key' => $data['key'], 'value' => $this->validator->get_validated()[ $data['key'] ] ] ) );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

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

	/**
	 * Updates an existing setting.
	 *
	 * Sanitizes and validates input before update.
	 *
	 * @param  WP_REST_Request  $request  Incoming REST request.
	 *
	 * @return WP_REST_Response|WP_Error REST response or error.
	 * @since 1.0.0
	 */
	public function update_item( $request ) {
		try {
			$data = $request->get_json_params();
			$this->sanitize->sanitize( [ $request->get_param( 'key' ) => $data['value'] ], 'create', (bool) $request->get_param( 'preview' ), [ $request->get_param( 'key' ) ] );
			$this->validator->validate( $this->sanitize->get_sanitized(), 'create', (bool) $request->get_param( 'preview' ), [ $request->get_param( 'key' ) ] );
			if ( $this->validator->has_errors() ) {
				return rest_ensure_response( $this->validator->get_error() );
			}

			return rest_ensure_response( $this->setting_repository->update( $request->get_param( 'key' ), [
				'value' => $this->validator->get_validated()[ $request->get_param( 'key' ) ]
			] ) );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

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

	/**
	 * Retrieves all settings matching query.
	 *
	 * @param  WP_REST_Request  $request  Incoming REST request.
	 *
	 * @return WP_REST_Response|WP_Error REST response or error.
	 * @since 1.0.0
	 */
	public function get_items( $request ) {
		try {
			return rest_ensure_response( $this->setting_repository->get_items( $request->get_query_params() ) );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

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

	/**
	 * Retrieves a single setting by key.
	 *
	 * @param  WP_REST_Request  $request  Incoming REST request.
	 *
	 * @return WP_REST_Response|WP_Error REST response or error.
	 * @since 1.0.0
	 */
	public function get_item( $request ) {
		try {
			return rest_ensure_response( Setting::find( $request->get_param( 'key' ) ) );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

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

	/**
	 * Deletes a setting by key.
	 *
	 * @param  WP_REST_Request  $request  Incoming REST request.
	 *
	 * @return WP_REST_Response|WP_Error REST response or error.
	 * @since 1.0.0
	 */
	public function delete_item( $request ) {
		try {
			return rest_ensure_response( $this->setting_repository->delete( $request->get_url_params(), $request->get_json_params() ) );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

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

	/**
	 * Deletes all settings based on query parameters.
	 *
	 * @param  WP_REST_Request  $request  Incoming REST request.
	 *
	 * @return WP_REST_Response|WP_Error REST response or error.
	 * @since 1.0.0
	 */
	public function delete_items( $request ) {
		try {
			return rest_ensure_response( $this->setting_repository->delete_all( $request->get_query_params() ) );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

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

	/**
	 * Batch update of multiple settings.
	 *
	 * Validates batch before updating.
	 *
	 * @param  WP_REST_Request  $request  Incoming REST request.
	 *
	 * @return WP_REST_Response|WP_Error REST response or error.
	 * @since 1.0.0
	 */
	public function batch_update_items( $request ) {
		try {
			$error = $this->validate_batch_items( $request->get_json_params(), null, $request );
			if ( is_wp_error( $error ) ) {
				return rest_ensure_response( $error );
			}

			return rest_ensure_response( $this->setting_repository->batch_update( $this->validator->get_validated(), $request->get_param( 'preview' ) ?? null ) );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

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

	/**
	 * Validates batch items using sanitizer and validator.
	 *
	 * @param array           $items   Items to validate.
	 * @param  array|null  $schema  Optional schema.
	 * @param  WP_REST_Request|null  $request  Optional REST request.
	 *
	 * @return true|WP_Error True if valid, WP_Error if errors.
	 * @since 1.0.0
	 */
	public function validate_batch_items( array $items, ?array $schema = null, ?WP_REST_Request $request = null ) {
		$assoc_items = array_column( $request->get_json_params(), 'value', 'key' );
		$keys        = array_keys( $assoc_items );
		$this->sanitize->sanitize( $assoc_items, 'create', (bool) $request->get_param( 'preview' ), $keys );
		$this->validator->validate( $this->sanitize->get_sanitized(), 'create', (bool) $request->get_param( 'preview' ), $keys );
		if ( $this->validator->has_errors() ) {
			return $this->validator->get_error();
		}

		return true;
	}

	/**
	 * Returns the item schema definition.
	 *
	 * @return array JSON schema for settings item.
	 * @since 1.0.0
	 */
	public function get_item_schema(): array {
		if ( $this->schema ) {
			// Return cached schema if already set.
			return $this->schema;
		}
		$this->schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'custom_item',
			'type'       => 'object',
			'properties' => array(
				'key'   => array(
					'description' => esc_html__( 'Unique identifier for the object.', 'limb-chatbot' ),
					'type'        => 'string',
					'required'    => true,
					'arg_options' => array(
						'required'          => true,
						'sanitize_callback' => 'sanitize_text_field',
						'validate_callback' => function ( $value ) {
							return ! empty( $value );
						}
					)
				),
				'value' => array(
					'description'       => esc_html__( 'Item value.', 'limb-chatbot' ),
					'required'          => true,
					'type'              => array( 'string', 'integer', 'array', 'object', 'null' ),
					'sanitize_callback' => '__return_true',
					'validate_callback' => '__return_true',
				)
			)
		);

		return $this->schema;
	}
}