<?php

namespace Limb_Chatbot\Includes\Api\V1\Controllers;

use Exception;
use Limb_Chatbot\Includes\Data_Objects\Action;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Repositories\Action_Repository;
use Limb_Chatbot\Includes\Services\Collection;
use Limb_Chatbot\Includes\Services\Helper;
use Limb_Chatbot\Includes\Services\User_Roles;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

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

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

	/**
	 * Action repository instance.
	 *
	 * @var Action_Repository
	 * @since 1.0.0
	 */
	protected Action_Repository $repository;

	/**
	 * Actions_Controller constructor.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {
		$this->repository = new Action_Repository();
	}

	/**
	 * Registers REST routes for actions.
	 *
	 * Defines routes for CRUD operations and batch operations.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function register_routes() {
		// GET /actions - List all actions
		// POST /actions - Create a new action
		// DELETE /actions - Batch delete actions
		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_collection_params(),
			),
			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::DELETABLE,
				'callback'            => array( $this, 'batch_delete' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array(
					'id' => array(
						'type'              => 'array',
						'required'          => true,
						'validate_callback' => function ( $value ) {
							return Action::count( [ 'id' => $value ] ) === count( $value );
						},
					),
				),
			),
			'schema' => array( $this, 'get_item_schema' ),
		) );

		// GET /actions/{id} - Get single action
		// PUT /actions/{id} - Update action
		// DELETE /actions/{id} - Delete single action
		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( Action::find( $value ) );
						},
					),
				),
			),
			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(),
			),
			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( Action::find( $value ) );
						},
					),
				),
			),
			'schema' => array( $this, 'get_item_schema' ),
		) );

		// POST /actions/batch - Batch create/update actions
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array(
			array(
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'batch' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array(
					'items' => array(
						'type'        => 'array',
						'description' => __( 'Array of action items to create or update.', 'limb-chatbot' ),
						'required'    => true,
					),
				),
			),
			'schema' => array( $this, 'get_item_schema' ),
		) );
	}

	/**
	 * Returns query parameters for the collection endpoint.
	 *
	 * These parameters allow filtering and sorting of action results.
	 *
	 * @return array Array of query parameters accepted by the collection route.
	 * @since 1.0.0
	 */
	public function get_collection_params() {
		return array_merge( 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', 'title', 'type', 'status' ),
				'validate_callback' => 'rest_validate_request_arg',
			),
			'search'        => array(
				'description' => __( 'Search actions by name.', 'limb-chatbot' ),
				'type'        => 'string',
			),
			'search_fields' => array(
				'description' => __( 'Fields to search in (e.g., name).', 'limb-chatbot' ),
				'type'        => 'array',
				'default'     => array( 'title' ),
			),
			'type'          => array(
				'description' => __( 'Filter actions by type.', 'limb-chatbot' ),
				'type'        => 'integer',
			),
			'include'       => array(
				'description' => __( 'Include extra data with actions.', 'limb-chatbot' ),
				'type'        => 'array',
			),
		) );
	}

	/**
	 * Returns a collection of action 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 {
			$actions = $this->repository->get_items( $request->get_query_params() );
			$items   = $this->prepare_collection( $actions, $request );

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

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

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

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

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

	/**
	 * Prepares a single action item for REST response.
	 *
	 * @param  mixed  $item  The item to prepare (Action instance).
	 * @param  WP_REST_Request  $request  The full REST request object.
	 *
	 * @return Action|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, Action::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 action 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 {
			$action = $this->repository->get_item( $request->get_param( 'id' ) );

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

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

	/**
	 * Updates an existing action 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 = $this->repository->update( $request->get_param( 'id' ), $request->get_json_params() );

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

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

	/**
	 * Deletes an action 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 );
		}
	}

	/**
	 * Batch deletes multiple actions by IDs.
	 *
	 * @param  WP_REST_Request  $request  REST request with IDs to delete.
	 *
	 * @return WP_REST_Response|WP_Error
	 * @since 1.0.0
	 */
	public function batch_delete( $request ) {
		try {
			$deleted = $this->repository->batch_delete( $request->get_json_params()['id'] ?? [] );

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

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

	/**
	 * Handles batch create or update of multiple actions.
	 *
	 * Processes batch action items for creation or update.
	 *
	 * @param  WP_REST_Request  $request  REST request with batch data.
	 *
	 * @return Collection|WP_Error
	 * @since 1.0.0
	 */
	public function batch( $request ) {
		try {
			$items = $request->get_json_params()['items'] ?? [];
			if ( empty( $items ) ) {
				throw new Exception( Error_Codes::VALIDATION_INVALID_VALUE,
					__( 'Items array is required for batch operation.', 'limb-chatbot' ) );
			}

			$error = $this->validate_batch_items( $items, $this->get_item_schema()['properties'] );
			if ( is_wp_error( $error ) ) {
				return $error;
			}

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

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

	/**
	 * Returns the JSON schema for an action item.
	 *
	 * Defines structure and validation rules for the action 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'      => 'action',
			'type'       => 'object',
			'properties' => array(
				'id'              => array(
					'description' => __( 'Unique identifier for the action.', 'limb-chatbot' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'title'           => array(
					'description' => __( 'Action title.', 'limb-chatbot' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'required'    => false,
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_text_field',
						'validate_callback' => function ( $value, $request ) {
							if ( $request->get_method() === 'PUT' && ! $request->has_param( 'title' ) ) {
								return true; // Allow missing title on update (keep existing)
							}
							if ( is_string( $value ) && ! empty( $value ) ) {
								if ( Action::find_by_title( $value ) instanceof Action && $request->get_method() !== 'PUT' ) {
									return new WP_Error(
										Error_Codes::VALIDATION_INVALID_VALUE,
										__( 'An action with this title already exists.', 'limb-chatbot' )
									);
								} else {
									return true;
								}
							}

							return false;
						},
					),
				),
				'ai_instructions' => array(
					'description' => __( 'AI instructions for the action.', 'limb-chatbot' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'required'    => false,
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_textarea_field',
						'validate_callback' => function ( $value, WP_REST_Request $request ) {
							if ($request->get_method() === 'PUT' && ! $request->has_param( 'ai_instructions' ) ) {
								return true; // Allow missing ai_instructions on update (keep existing)
							}
							if ( $request->get_param( 'type' ) == Action::TYPE_CONVERSATIONAL ) {
								return is_string( $value ) && ! empty( $value );
							}

							return true;
						},
					),
				),
				'type'            => array(
					'description' => __( 'Action type.', 'limb-chatbot' ),
					'type'        => 'integer',
					'default'     => Action::TYPE_CONVERSATIONAL,
					'context'     => array( 'view', 'edit' ),
					'arg_options' => array(
						'sanitize_callback' => 'absint',
						'validate_callback' => function ( $value ) {
							return in_array( $value, [ Action::TYPE_CONVERSATIONAL, Action::TYPE_STATIC ] );
						},
					),
				),
				'access_roles'    => array(
					'description' => __( 'Allowed roles for this action.', 'limb-chatbot' ),
					'type'        => 'array',
					'default'     => array( Action::ROLE_EVERYONE ),
					'context'     => array( 'view', 'edit' ),
					'arg_options' => array(
						'sanitize_callback' => array( $this, 'sanitize_access_roles' ),
						'validate_callback' => array( $this, 'validate_access_roles' ),
					),
				),
				'config'          => array(
					'description' => __( 'Action behavior config.', 'limb-chatbot' ),
					'type'        => 'object',
					'default'     => Action::DEFAULT_CONFIG,
					'context'     => array( 'view', 'edit' ),
					'arg_options' => array(
						'sanitize_callback' => array( $this, 'sanitize_config' ),
						'validate_callback' => array( $this, 'validate_config' ),
					),
				),
				'created_at'      => array(
					'description' => __( 'Creation timestamp.', 'limb-chatbot' ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'view' ),
					'readonly'    => true,
				),
				'updated_at'      => array(
					'description' => __( 'Last update timestamp.', 'limb-chatbot' ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'view' ),
					'readonly'    => true,
				),
				'include'         => array(
					'description' => __( 'Include extra data with action (e.g., parameters, callbacks).',
						'limb-chatbot' ),
					'type'        => 'array',
					'context'     => array( 'view' ),
				),
			),
		);

		return $this->schema;
	}

	/**
	 * Sanitizes action access roles.
	 *
	 * @param  mixed  $value  Roles payload from request.
	 *
	 * @return array
	 */
	public function sanitize_access_roles( $value ): array {
		if ( ! is_array( $value ) ) {
			return array( Action::ROLE_EVERYONE );
		}
		$roles = array_values(
			array_filter(
				array_map(
					static function ( $role ) {
						return is_string( $role ) ? sanitize_key( $role ) : '';
					},
					$value
				)
			)
		);

		return empty( $roles ) ? array( Action::ROLE_EVERYONE ) : array_values( array_unique( $roles ) );
	}

	/**
	 * Validates action access roles.
	 *
	 * @param  mixed  $value  Roles payload from request.
	 *
	 * @return bool
	 */
	public function validate_access_roles( $value, ...$_args ): bool {
		if ( ! is_array( $value ) ) {
			return false;
		}
		$user_roles = User_Roles::instance();
		foreach ( $value as $role ) {
			if ( ! is_string( $role ) || '' === trim( $role ) ) {
				return false;
			}
			if ( ! $user_roles->get_by_key( $role ) ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Sanitizes action config payload.
	 *
	 * @param  mixed  $value  Config payload from request.
	 *
	 * @return array
	 */
	public function sanitize_config( $value ): array {
		$value = is_array( $value ) ? $value : [];

		return [
			'show_fields'   => ! isset( $value['show_fields'] ) ? $value['show_fields'] : 1,
			'callback_text' => isset( $value['callback_text'] ) && null !== $value['callback_text']
				? sanitize_textarea_field( $value['callback_text'] )
				: null,
		];
	}

	/**
	 * Validates action config payload.
	 *
	 * @param  mixed  $value  Config payload from request.
	 *
	 * @return bool
	 */
	public function validate_config( $value, ...$_args ): bool {
		if ( ! is_array( $value ) ) {
			return false;
		}
		if ( array_key_exists( 'show_fields', $value ) && ! is_scalar( $value['show_fields'] ) && ! is_bool( $value['show_fields'] ) ) {
			return false;
		}
		if ( array_key_exists( 'callback_text', $value ) && null !== $value['callback_text'] && ! is_string( $value['callback_text'] ) ) {
			return false;
		}

		return true;
	}
}
