<?php
/**
 * Core REST controller for exposing products data for select2 fields.
 *
 * @package Ilabs\Firevert\Core\Api\Controller
 */

declare( strict_types=1 );

namespace Ilabs\Firevert\Core\Api\Controller;

use Ilabs\Firevert\Core\Api\Preparer\ProductsResponsePreparer;
use Ilabs\Firevert\Core\Api\Validator\RequestValidator;
use WP_REST_Request;
use WP_REST_Response;
use Ilabs\Firevert\Core\Repository\Cache\CacheRepository;
use wpdb;

/**
 * Class ProductsController
 */
final class ProductsController extends AbstractController {

	/**
	 * Maximum products per page.
	 *
	 * @var int
	 */
	const MAX_PRODUCTS_PER_PAGE = 10;

	/**
	 * Products cache group name.
	 *
	 * @var string
	 */
	const PRODUCTS_CACHE_GROUP = 'firevert_core_products';

	/**
	 * Cache TTL in seconds
	 *
	 * @var int
	 */
	const CACHE_TTL = 3600;

	/**
	 * Database instance.
	 *
	 * @var wpdb
	 */
	private wpdb $wpdb;

	/**
	 * Constructor.
	 */
	public function __construct() {
		global $wpdb;
		$this->wpdb = $wpdb;

		parent::__construct();
	}

	/**
	 * Registers REST API routes.
	 *
	 * @return void
	 */
	public function register_routes(): void {
		add_action( 'rest_api_init', array( $this, 'on_rest_api_init' ) );
	}

	/**
	 * Registers the actual REST API route for products.
	 *
	 * @return void
	 */
	public function on_rest_api_init(): void {
		$request_validator = firevert_container()->get( 'request_validator' );

		register_rest_route(
			'firevert/v1',
			'/core/products',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'get_products' ),
				'permission_callback' => $this->get_permission_callback(),
			)
		);
	}

	/**
	 * Returns products for select2 fields.
	 *
	 * @param WP_REST_Request $request Request.
	 *
	 * @return WP_REST_Response REST response with products data.
	 */
	public function get_products( WP_REST_Request $request ): WP_REST_Response {
		$page = (int) $request->get_param( 'page' );
		$page = $page > 0 ? $page : 1;

		$search = $request->get_param( 'search' );
		$search = is_string( $search ) ? sanitize_text_field( $search ) : '';

		$products_data = $this->get_all_products( $page, $search );

		return new WP_REST_Response( ProductsResponsePreparer::prepare_response( $products_data ) );
	}

	/**
	 * Get all products for select2 fields.
	 *
	 * @param int    $page    Page number.
	 * @param string $search  Search query for product name.
	 *
	 * @return array<string, mixed> Products query result.
	 */
	private function get_all_products( int $page, string $search = '' ): array {
		/**
		 * Get container DI.
		 *
		 * @var CacheRepository $cache
		 */
		$cache = firevert_container()->get( 'cache_repository' );

		$cache_key = 'core_products_' . md5(
			wp_json_encode(
				array(
					'page'   => $page,
					'limit'  => self::MAX_PRODUCTS_PER_PAGE,
					'search' => $search,
				)
			)
		);

		$cached = $cache->get( $cache_key );
		if ( false !== $cached ) {
			return $cached;
		}

		$result = $this->query_products_directly( $page, $search );

		$cache->set( $cache_key, $result, self::CACHE_TTL, self::PRODUCTS_CACHE_GROUP );

		return $result;
	}

	/**
	 * Query products directly using SQL for better performance.
	 *
	 * @param int    $page   Page number.
	 * @param string $search Search query for product name.
	 *
	 * @return array<string, mixed> Products query result.
	 */
	private function query_products_directly( int $page, string $search = '' ): array {
		$offset          = ( $page - 1 ) * self::MAX_PRODUCTS_PER_PAGE;
		$table           = $this->wpdb->posts;
		$post_type_esc   = esc_sql( 'product' );
		$post_status_esc = esc_sql( 'publish' );

		// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
		if ( ! empty( $search ) ) {
			$like_search_esc = $this->wpdb->esc_like( $search );
			$like_search_esc = esc_sql( '%' . $like_search_esc . '%' );

			$count_sql = "SELECT COUNT(ID) FROM $table WHERE post_type = '$post_type_esc' AND post_status = '$post_status_esc' AND post_title LIKE '$like_search_esc'";
		} else {
			$count_sql = "SELECT COUNT(ID) FROM $table WHERE post_type = '$post_type_esc' AND post_status = '$post_status_esc'";
		}

		$count_query = $this->wpdb->get_var( $count_sql );
		// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared

		$total_products = (int) $count_query;
		$total_pages    = (int) ceil( $total_products / self::MAX_PRODUCTS_PER_PAGE );

		$limit_clause = 'LIMIT ' . intval( self::MAX_PRODUCTS_PER_PAGE ) . ' OFFSET ' . intval( $offset );

		// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
		if ( ! empty( $search ) ) {
			$like_search_esc = $this->wpdb->esc_like( $search );
			$like_search_esc = esc_sql( '%' . $like_search_esc . '%' );

			$sql = "SELECT ID, post_title FROM $table WHERE post_type = '$post_type_esc' AND post_status = '$post_status_esc' AND post_title LIKE '$like_search_esc' ORDER BY post_title ASC $limit_clause";
		} else {
			$sql = "SELECT ID, post_title FROM $table WHERE post_type = '$post_type_esc' AND post_status = '$post_status_esc' ORDER BY post_title ASC $limit_clause";
		}

		$product_posts = $this->wpdb->get_results( $sql );
		// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared

		$products = array();
		foreach ( $product_posts as $product_post ) {
			$products[] = array(
				'id'   => (int) $product_post->ID,
				'name' => $product_post->post_title,
			);
		}

		return array(
			'products'     => $products,
			'current_page' => $page,
			'total_pages'  => $total_pages,
			'has_more'     => ( $page < $total_pages ),
		);
	}
}
