<?php
/**
 * Core REST controller for exposing product categories 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\ProductCategoriesResponsePreparer;
use WP_REST_Request;
use WP_REST_Response;
use Ilabs\Firevert\Core\Repository\Cache\CacheRepository;
use wpdb;

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

	/**
	 * Maximum product categories per page.
	 *
	 * @var int
	 */
	const MAX_CATEGORIES_PER_PAGE = 10;

	/**
	 * Products cache group name.
	 *
	 * @var string
	 */
	const CATEGORIES_CACHE_GROUP = 'firevert_core_product_categories';

	/**
	 * 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 product categories.
	 *
	 * @return void
	 */
	public function on_rest_api_init(): void {
		register_rest_route(
			'firevert/v1',
			'/core/product_categories',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'get_product_categories' ),
				'permission_callback' => $this->get_permission_callback(),
			)
		);
	}

	/**
	 * Returns product categories for select2 fields.
	 *
	 * @param WP_REST_Request $request Request.
	 *
	 * @return WP_REST_Response REST response with product categories data.
	 */
	public function get_product_categories( 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 ) : '';

		$product_categories_data = $this->get_all_product_categories( $page, $search );

		return new WP_REST_Response( ProductCategoriesResponsePreparer::prepare_response( $product_categories_data ) );
	}

	/**
	 * Get all product categories 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_product_categories( int $page, string $search = '' ): array {
		/**
		 * Get container DI.
		 *
		 * @var CacheRepository $cache
		 */
		$cache = firevert_container()->get( 'cache_repository' );

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

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

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

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

		return $result;
	}

	/**
	 * Query product categories 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_product_categories_directly( int $page, string $search = '' ): array {
		$offset = ( $page - 1 ) * self::MAX_CATEGORIES_PER_PAGE;

		$term_table     = $this->wpdb->terms;
		$taxonomy_table = $this->wpdb->term_taxonomy;

		$taxonomy_esc = esc_sql( 'product_cat' );

		$where = "WHERE tt.taxonomy = '$taxonomy_esc'";
		if ( ! empty( $search ) ) {
			$search_esc = '%' . $this->wpdb->esc_like( $search ) . '%';
			$where     .= " AND t.name LIKE '{$search_esc}'";
		}
		$count_sql = "
			SELECT COUNT(*)
			FROM $term_table AS t
			INNER JOIN $taxonomy_table AS tt ON t.term_id = tt.term_id
			$where
		";

		// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
		$total_categories = (int) $this->wpdb->get_var( $count_sql );
		$total_pages      = (int) ceil( $total_categories / self::MAX_CATEGORIES_PER_PAGE );
		// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared

		$sql = "
			SELECT t.term_id, t.name
			FROM $term_table AS t
			INNER JOIN $taxonomy_table AS tt ON t.term_id = tt.term_id
			$where
			ORDER BY t.name ASC
			LIMIT " . intval( self::MAX_CATEGORIES_PER_PAGE ) . '
			OFFSET ' . intval( $offset ) . '
		';

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

		$categories = array_map(
			static function ( $row ) {
			return array(
				'id'   => (int) $row->term_id,
				'name' => $row->name,
			);
			},
			$results
		);

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