<?php

namespace Limb_Chatbot\Includes\Api\V1\Controllers;

use Limb_Chatbot\Includes\Data_Objects\Chatbot;
use Limb_Chatbot\Includes\Data_Objects\WP_Post_Data_Object;
use Limb_Chatbot\Includes\Services\Collection;
use Limb_Chatbot\Includes\Services\User_Manager;
use WP_Error;
use WP_REST_Request;

/**
 * Abstract REST controller providing common functionality for API endpoints.
 *
 * Extends WP_REST_Controller with batch validation, permission checks,
 * collection preparation, and pagination support.
 *
 * @since 1.0.0
 */
abstract class Rest_Controller extends \WP_REST_Controller {

	/**
	 * API namespace for the controller.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	protected $namespace = 'limb/chatbot/v1';


	/**
	 * Validates batch items against the given schema.
	 *
	 * Returns true if all items validate successfully,
	 * otherwise returns a WP_Error with validation messages.
	 *
	 * @param  array  $items  Array of items to validate.
	 * @param  array|null  $schema  Optional JSON schema for validation.
	 * @param  WP_REST_Request|null  $request  Optional REST request context.
	 *
	 * @return bool|WP_Error True if valid, WP_Error on validation failure.
	 * @since 1.0.0
	 */

	public function validate_batch_items( array $items, ?array $schema = null, ?WP_REST_Request $request = null ) {
		$errors = [];
		foreach ( $items as $index => $item ) {
			if ( ! is_array( $item ) ) {
				// translators: %d is the index of the item that is not an object.
				$errors[] = sprintf( __( 'Item %d is not an object.', 'limb-chatbot' ), $index );
				continue;
			}
			foreach ( $schema as $field => $field_schema ) {
				if ( isset( $item[ $field ] ) ) {
					$value = $item[ $field ];
					if ( isset( $field_schema['arg_options']['sanitize_callback'] ) && is_callable( $field_schema['arg_options']['sanitize_callback'] ) ) {
						$value = call_user_func( $field_schema['arg_options']['sanitize_callback'], $value );
					}
					$valid = rest_validate_value_from_schema( $value, $field_schema, $field );
					if ( $valid && isset( $field_schema['arg_options']['validate_callback'] ) && is_callable( $field_schema['arg_options']['validate_callback'] ) ) {
						$valid = call_user_func( $field_schema['arg_options']['validate_callback'], $value, $field_schema, $field );
					}
					if ( is_wp_error( $valid ) ) {
						// translators: %1$d is the item index, %2$s is the field name, and the final %3$s is the error message.
						$errors[] = sprintf( __( 'Error in item %1$d, field "%2$s": %3$s', 'limb-chatbot' ), $index, $field, $valid->get_error_message() );
					}
				} elseif ( ! empty( $field_schema['required'] ) ) {
					// translators: %1$s is the name of the missing field, %2$d is the index of the item.
					$errors[] = sprintf( __( 'Missing required field "%1$s" in item %1$d.', 'limb-chatbot' ), $field, $index );
				}
			}
		}
		if ( ! empty( $errors ) ) {
			return new WP_Error( 'validation_error', __( 'Validation errors occurred.', 'limb-chatbot' ), $errors );
		}

		return true;
	}

	/**
	 * Checks whether the current user has permission to access the endpoint.
	 *
	 * Allows all logged-in users or users with verified nonce and valid current user.
	 *
	 * @param  WP_REST_Request  $request  Incoming REST request.
	 * @return bool True if authorized, false otherwise.
	 * @since 1.0.0
	 */
	public function permission_callback( $request ) {
		if ( is_user_logged_in() ) {
			// So no matter from where the request came from, the user is authorized
			return true;
		} else {
			// If not logged in, then nonce should be verified alongside with current user existence
			if ( $this->nonce_is_verified( $request ) && User_Manager::instance()->get_current_user() ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Verifies the REST nonce from the request headers.
	 *
	 * @param  WP_REST_Request  $request  Incoming REST request.
	 *
	 * @return bool True if nonce is valid, false otherwise.
	 * @since 1.0.0
	 */
	public function nonce_is_verified( $request ) {
		return wp_verify_nonce( $request->get_header( 'X-WP-Nonce' ), 'wp_rest' );
	}

	/**
	 * Returns query parameters accepted for collection endpoints.
	 *
	 * Adds 'orderby' and 'order' params with validation and defaults.
	 *
	 * @return array Query parameter definitions.
	 * @since 1.0.0
	 */
	public function get_collection_params() {
		return parent::get_collection_params() + array(
				'orderby' => array(
					'description'       => __( 'Sort the collection by attribute.', 'limb-chatbot' ),
					'type'              => 'string',
					'default'           => 'created_at',
					'enum'              => array( 'created_at', 'id', 'updated_at' ),
					'validate_callback' => 'rest_validate_request_arg',
				),
				'order'   => array(
					'description'       => __( 'Order sort attribute ascending or descending.', 'limb-chatbot' ),
					'type'              => 'string',
					'default'           => 'desc',
					'enum'              => array( 'asc', 'desc' ),
					'validate_callback' => 'rest_validate_request_arg',
				),
			);
	}

	/**
	 * Prepares a collection of items with optional includes and pagination.
	 *
	 * Supports filtering includes, search fields, and setting pagination metadata.
	 *
	 * @param  Collection  $collection  The collection object.
	 * @param  WP_REST_Request  $request  Incoming REST request.
	 *
	 * @return Collection Prepared collection with metadata.
	 * @since 1.0.0
	 */
	public function prepare_collection( Collection $collection, WP_REST_Request $request ) {
		if ( $collection->is_empty() ) {
			return $collection;
		}
		if ( $include = $request->get_param( 'include' ) ) {
			$include    = array_filter( $include, function ( $property ) use ( $collection ) {
				return empty($collection->first()->included[$property]) && ! property_exists( $collection->first(), $property );
			} );
			$collection = $collection->with( $include );
		}
		if ( method_exists( $collection->first(), 'count' ) ) {
			$params = array_merge( $request->get_query_params(), $request->get_url_params() );
			if ( ( empty( $params['per_page'] ) || $params['per_page'] <= $collection->total() )
			     || ( ! empty( $params['page'] ) && $params['page'] > 1 ) ) {
				if ( ! empty( $params['search'] ) && ! empty( $params['search_fields'] ) ) {
					foreach ( $params['search_fields'] as $field ) {
						$params["{$field}LIKE"] = "%{$params['search']}%";
					}
				}
				$total = $collection->first()::count( $params );
				if ( ! $total == 0 && ! $collection->is_empty() ) {
					$collection->set_total( $total );
				}
			}
		}

		return $this->set_pagination_params( $collection, $request );
	}

	/**
	 * Sets pagination parameters (page, per_page, next_page, prev_page) on collection.
	 *
	 * @param  Collection  $collection  The collection to modify.
	 * @param  WP_REST_Request  $request  Incoming REST request.
	 *
	 * @return Collection Collection with pagination metadata.
	 * @since 1.0.0
	 */
	protected function set_pagination_params( Collection $collection, WP_REST_Request $request ) {
		if ( $page = $request->get_param( 'page' ) ) {
			$collection->push_property( 'page', (int) $page );
		}
		if ( $per_page = $request->get_param( 'per_page' ) ) {
			$collection->push_property( 'per_page', (int) $per_page );
		}
		if ( $page && $per_page ) {
			if ( $page == 1 && $collection->total() < $per_page ) {
				return $collection;
			}
			$route = $request->get_route();
			foreach ( $request->get_query_params() as $key => $value ) {
				if ( in_array( $key, [ '_from_admin', '_current_tab' ] ) ) {
					continue;
				}
				$route = add_query_arg( $key, $value, $route );
			}
			if ( $page * $per_page < $collection->total() ) {
				$collection->push_property( 'next_page', rest_url( add_query_arg( 'page', $page + 1, $route ) ) );
			}
			if ( $page != 1 ) {
				$collection->push_property( 'prev_page', rest_url( add_query_arg( 'page', $page - 1, $route ) ) );
			}
		}

		return $collection;
	}

	/**
	 * Returns argument validation array for chatbot ID parameter.
	 *
	 * Validates the chatbot ID and sets $this->chatbot if found.
	 *
	 * @return array Argument definition for 'chatbot_id'.
	 * @since 1.0.0
	 */
	protected function get_chatbot_id_arg() {
		return array(
			'chatbot_id' => array(
				'required'          => true,
				'validate_callback' => function ( $value ) {
					$chatbot = Chatbot::find( $value ) ?? Chatbot::where( [ 'id' => [ $value ], 'status' => WP_Post_Data_Object::STATUS_AUTO_DRAFT ] )->first();
					if ( $chatbot && property_exists( $this, 'chatbot' ) ) {
						$this->chatbot = $chatbot;
					}

					return $chatbot ?? null;
				}
			)
		);
	}

	/**
	 * Determines if the current request is intended for a streaming response.
	 *
	 * @param  WP_REST_Request  $request
	 *
	 * @return bool
	 * @since 1.0.0
	 */
	public function is_stream_waiting( WP_REST_Request $request ): bool {
		return str_contains( $request->get_header( 'Accept' ), 'text/event-stream' );
	}
}