<?php
/**
 * Base REST API controller for Dokan Kits.
 *
 * @package DokanKits\REST\Controllers
 * @since   3.0.0
 */

namespace DokanKits\REST\Controllers;

use DokanKits\REST\ApiResponse;
use DokanKits\REST\Middleware\Authentication;
use DokanKits\REST\Middleware\Validation;
use WP_Error;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;

/**
 * Abstract REST Controller
 *
 * Base class for all REST controllers with consistent patterns
 *
 * @package DokanKits\REST\Controllers
 * @since   3.0.1
 */
abstract class Controller extends WP_REST_Controller {

	/**
	 * Endpoint namespace
	 *
	 * @var string
	 */
	protected $namespace = 'dokan-kits/v1';

	/**
	 * Route base
	 *
	 * @var string
	 */
	protected $rest_base = '';

	/**
	 * Check general permissions for the endpoint
	 *
	 * @since 3.0.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return bool|WP_Error True if permission granted, WP_Error otherwise.
	 */
	public function check_permissions( WP_REST_Request $request ) {
		return ( new Authentication() )->validate( $request );
	}

	/**
	 * Check if the current user has admin permissions.
	 *
	 * @since 3.0.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return bool|WP_Error True if the user has permission, WP_Error if not.
	 */
	public function admin_permissions_check( WP_REST_Request $request ) {
		return ( new Authentication() )->validate( $request, true );
	}

	/**
	 * Check if the current user is a vendor
	 *
	 * @since 3.0.1
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return bool|WP_Error True if the user is vendor, WP_Error if not.
	 */
	public function vendor_permissions_check( WP_REST_Request $request ) {
		return ( new Authentication() )->check_vendor( $request );
	}

	/**
	 * Check if the current user has specific capability
	 *
	 * @since 3.0.1
	 *
	 * @param WP_REST_Request $request    Full details about the request.
	 * @param string          $capability Required capability.
	 *
	 * @return bool|WP_Error True if the user has capability, WP_Error if not.
	 */
	public function capability_permissions_check( WP_REST_Request $request, string $capability ) {
		return ( new Authentication() )->check_capability( $request, $capability );
	}

	/**
	 * Check if the current user owns the resource
	 *
	 * @since 3.0.1
	 *
	 * @param WP_REST_Request $request        Full details about the request.
	 * @param int             $resource_owner Resource owner user ID.
	 *
	 * @return bool|WP_Error True if the user owns resource, WP_Error if not.
	 */
	public function ownership_permissions_check( WP_REST_Request $request, int $resource_owner ) {
		return ( new Authentication() )->check_ownership( $request, $resource_owner );
	}

	/**
	 * Validate request data
	 *
	 * @since 3.0.0
	 *
	 * @param WP_REST_Request $request  Full details about the request.
	 * @param array           $rules    Validation rules.
	 * @param array           $messages Custom validation messages.
	 *
	 * @return bool|WP_Error True if validation passes, WP_Error otherwise.
	 */
	protected function validate( WP_REST_Request $request, array $rules, array $messages = array() ) {
		return ( new Validation() )->validate( $request, $rules, $messages );
	}

	/**
	 * Create a success response
	 *
	 * @since 3.0.0
	 *
	 * @param mixed  $data    Response data.
	 * @param string $message Success message.
	 * @param int    $status  HTTP status code.
	 *
	 * @return WP_REST_Response
	 */
	protected function success( $data = array(), string $message = '', int $status = 200 ): WP_REST_Response {
		return ApiResponse::success( $data, $message, $status );
	}

	/**
	 * Create an error response
	 *
	 * @since 3.0.0
	 *
	 * @param string $message Error message.
	 * @param string $code    Error code.
	 * @param int    $status  HTTP status code.
	 * @param mixed  $data    Additional error data.
	 *
	 * @return WP_Error
	 */
	protected function error( string $message, string $code = 'error', int $status = 400, $data = null ): WP_Error {
		return ApiResponse::error( $message, $code, $status, $data );
	}

	/**
	 * Handle exceptions in a standardized way
	 *
	 * @since 3.0.1
	 *
	 * @param \Throwable $exception The exception to handle.
	 * @param string     $context   Context where the exception occurred.
	 *
	 * @return WP_Error Standardized error response.
	 */
	protected function handle_exception( \Throwable $exception, string $context = '' ): WP_Error {
		// Log the exception.
		dokan_kits_logger()->error(
			// translators: 1. Current class, 2. Error message.
			sprintf( 'Exception in %s: %s', '' !== $context ? $context : static::class, $exception->getMessage() ),
			array(
				'exception' => $exception,
				'context'   => $context,
				'file'      => $exception->getFile(),
				'line'      => $exception->getLine(),
			)
		);

		return ApiResponse::from_exception( $exception );
	}

	/**
	 * Sanitize and validate pagination parameters
	 *
	 * @since 3.0.1
	 *
	 * @param WP_REST_Request $request The request object.
	 *
	 * @return array Sanitized pagination parameters.
	 */
	protected function get_pagination_params( WP_REST_Request $request ): array {
		$per_page = max( 1, min( 100, absint( $request->get_param( 'per_page' ) ? $request->get_param( 'per_page' ) : 10 ) ) );
		$page     = max( 1, absint( $request->get_param( 'page' ) ? $request->get_param( 'page' ) : 1 ) );
		$offset   = $request->get_param( 'offset' );

		return array(
			'per_page' => $per_page,
			'page'     => $page,
			'offset'   => null === $offset ? ( $page - 1 ) * $per_page : absint( $offset ),
		);
	}

	/**
	 * Add pagination headers to response
	 *
	 * @since 3.0.1
	 *
	 * @param WP_REST_Response $response    The response object.
	 * @param WP_REST_Request  $request     The request object.
	 * @param int              $total_items Total number of items.
	 * @param int              $per_page    Items per page.
	 * @param int              $page        Current page.
	 *
	 * @return void
	 */
	protected function add_pagination_headers( WP_REST_Response $response, WP_REST_Request $request, int $total_items, int $per_page, int $page ): void {
		$total_pages = (int) ceil( $total_items / $per_page );

		$response->header( 'X-WP-Total', (string) $total_items );
		$response->header( 'X-WP-TotalPages', (string) $total_pages );

		if ( $total_pages <= 1 ) {
			return;
		}

		$base_url = add_query_arg( $request->get_query_params(), rest_url( $this->namespace . '/' . $this->rest_base ) );
		$links    = array();

		if ( $page > 1 ) {
			$links[] = sprintf( '<%s>; rel="prev"', esc_url( add_query_arg( 'page', $page - 1, $base_url ) ) );
		}

		if ( $page < $total_pages ) {
			$links[] = sprintf( '<%s>; rel="next"', esc_url( add_query_arg( 'page', $page + 1, $base_url ) ) );
		}

		if ( ! empty( $links ) ) {
			$response->header( 'Link', implode( ', ', $links ) );
		}
	}

	/**
	 * Sanitize text input
	 *
	 * @since 3.0.1
	 *
	 * @param mixed $value The value to sanitize.
	 *
	 * @return string Sanitized text.
	 */
	protected function sanitize_text( $value ): string {
		return sanitize_text_field( (string) $value );
	}

	/**
	 * Sanitize key input
	 *
	 * @since 3.0.1
	 *
	 * @param mixed $value The value to sanitize.
	 *
	 * @return string Sanitized key.
	 */
	protected function sanitize_key( $value ): string {
		return sanitize_key( (string) $value );
	}

	/**
	 * Sanitize integer input
	 *
	 * @since 3.0.1
	 *
	 * @param mixed $value   The value to sanitize.
	 * @param int   $default Default value if invalid.
	 *
	 * @return int Sanitized integer.
	 */
	protected function sanitize_int( $value, int $default = 0 ): int {
		$int = absint( $value );
		return $int > 0 ? $int : $default;
	}

	/**
	 * Sanitize boolean input
	 *
	 * @since 3.0.1
	 *
	 * @param mixed $value The value to sanitize.
	 *
	 * @return bool Sanitized boolean.
	 */
	protected function sanitize_bool( $value ): bool {
		return rest_sanitize_boolean( $value );
	}

	/**
	 * Sanitize array input
	 *
	 * @since 3.0.1
	 *
	 * @param mixed $value    The value to sanitize.
	 * @param array $defaults Default array if invalid.
	 *
	 * @return array Sanitized array.
	 */
	protected function sanitize_array( $value, array $defaults = array() ): array {
		if ( ! is_array( $value ) ) {
			return $defaults;
		}

		return array_map( 'sanitize_text_field', $value );
	}

	/**
	 * Get standard collection parameters
	 *
	 * @since 3.0.1
	 *
	 * @return array Standard collection parameters.
	 */
	protected function get_standard_collection_params(): array {
		return array(
			'page'     => array(
				'description' => __( 'Current page of the collection.', 'dokan-kits' ),
				'type'        => 'integer',
				'default'     => 1,
				'minimum'     => 1,
			),
			'per_page' => array(
				'description' => __( 'Maximum number of items to be returned in result set.', 'dokan-kits' ),
				'type'        => 'integer',
				'default'     => 10,
				'minimum'     => 1,
				'maximum'     => 100,
			),
			'search'   => array(
				'description' => __( 'Limit results to those matching a string.', 'dokan-kits' ),
				'type'        => 'string',
			),
			'orderby'  => array(
				'description' => __( 'Sort collection by attribute.', 'dokan-kits' ),
				'type'        => 'string',
				'default'     => 'id',
			),
			'order'    => array(
				'description' => __( 'Order sort attribute ascending or descending.', 'dokan-kits' ),
				'type'        => 'string',
				'default'     => 'ASC',
				'enum'        => array( 'ASC', 'DESC' ),
			),
		);
	}

	/**
	 * Get standard response schema properties
	 *
	 * @since 3.0.1
	 *
	 * @return array Standard response schema properties.
	 */
	protected function get_standard_response_schema(): array {
		$schema = array(
			'success' => array(
				'description' => __( 'Whether the request was successful.', 'dokan-kits' ),
				'type'        => 'boolean',
				'context'     => array( 'view', 'edit' ),
				'readonly'    => true,
			),
			'message' => array(
				'description' => __( 'Human-readable message describing the result.', 'dokan-kits' ),
				'type'        => 'string',
				'context'     => array( 'view', 'edit' ),
				'readonly'    => true,
			),
			'data'    => array(
				'description' => __( 'Response data.', 'dokan-kits' ),
				'type'        => 'object',
				'context'     => array( 'view', 'edit' ),
			),
		);

		/**
		 * Filter the standard response schema.
		 *
		 * @since 3.0.0
		 *
		 * @param array $schema Standard response schema.
		 */
		return apply_filters( 'dokan_kits_rest_standard_response_schema', $schema );
	}

	/**
	 * Prepare a standard error response for invalid resource ID
	 *
	 * @since 3.0.1
	 *
	 * @param string $resource_type Resource type (e.g., 'setting', 'user').
	 * @param mixed  $resource_id   Resource ID that was invalid.
	 *
	 * @return WP_Error Error response.
	 */
	protected function invalid_resource_id_error( string $resource_type, $resource_id ): WP_Error {
		return $this->error(
			sprintf(
			/* translators: 1: resource type, 2: resource ID */
				__( 'Invalid %1$s ID: %2$s', 'dokan-kits' ),
				$resource_type,
				$resource_id
			),
			'invalid_' . $resource_type . '_id',
			400
		);
	}

	/**
	 * Prepare a standard error response for resource not found
	 *
	 * @since 3.0.1
	 *
	 * @param string $resource_type Resource type (e.g., 'setting', 'user').
	 * @param mixed  $resource_id   Resource ID that was not found.
	 *
	 * @return WP_Error Error response.
	 */
	protected function resource_not_found_error( string $resource_type, $resource_id ): WP_Error {
		return $this->error(
			sprintf(
			/* translators: 1: resource type, 2: resource ID */
				__( '%1$s not found: %2$s', 'dokan-kits' ),
				ucfirst( $resource_type ),
				$resource_id
			),
			$resource_type . '_not_found',
			404
		);
	}

	/**
	 * Prepare a standard error response for missing required parameter
	 *
	 * @since 3.0.1
	 *
	 * @param string $parameter_name Parameter name that is missing.
	 *
	 * @return WP_Error Error response.
	 */
	protected function missing_parameter_error( string $parameter_name ): WP_Error {
		return $this->error(
			sprintf(
			/* translators: %s: parameter name */
				__( 'Missing required parameter: %s', 'dokan-kits' ),
				$parameter_name
			),
			'missing_parameter',
			400
		);
	}

	/**
	 * Prepare a standard success message for resource operation
	 *
	 * @since 3.0.1
	 *
	 * @param string $resource_type Resource type (e.g., 'setting', 'user').
	 * @param string $operation     Operation performed (e.g., 'created', 'updated', 'deleted').
	 * @param mixed  $resource_id   Resource ID (optional).
	 *
	 * @return string Success message.
	 */
	protected function get_operation_success_message( string $resource_type, string $operation, $resource_id = null ): string {
		if ( $resource_id ) {
			return sprintf(
			/* translators: 1: resource type, 2: resource ID, 3: operation */
				__( '%1$s "%2$s" %3$s successfully.', 'dokan-kits' ),
				ucfirst( $resource_type ),
				$resource_id,
				$operation
			);
		}

		return sprintf(
		/* translators: 1: resource type, 2: operation */
			__( '%1$s %2$s successfully.', 'dokan-kits' ),
			ucfirst( $resource_type ),
			$operation
		);
	}
}
