<?php
/**
 * REST controller exposing the bulk PackageAssignmentService.
 *
 * @package Automattic\WCShipping\PackageAssignment
 */

namespace Automattic\WCShipping\PackageAssignment;

use Automattic\WCShipping\Connect\WC_Connect_Functions;
use Automattic\WCShipping\FeatureFlags\FeatureFlags;
use Automattic\WCShipping\WCShippingRESTController;
use WP_Error;
use WP_REST_Request;
use WP_REST_Server;

defined( 'ABSPATH' ) || exit;

/**
 * REST controller that suggests a single shipping package per order for a
 * batch of orders, gated by the bulk_labels feature flag.
 */
class PackageAssignmentRESTController extends WCShippingRESTController {

	/**
	 * Maximum number of orders allowed in a single auto-assign request.
	 */
	private const BATCH_SIZE_CAP = 25;

	/**
	 * REST base.
	 *
	 * @var string
	 */
	protected $rest_base = 'label/auto-assign-packages';

	/**
	 * Service that resolves the package suggestion for each order.
	 *
	 * @var PackageAssignmentService
	 */
	private $service;

	/**
	 * Constructor.
	 *
	 * @param PackageAssignmentService $service Package assignment service.
	 */
	public function __construct( PackageAssignmentService $service ) {
		$this->service = $service;
	}

	/**
	 * Register API routes. Only registers when the bulk_labels feature flag is on.
	 *
	 * @return void
	 */
	public function register_routes() {
		if ( ! FeatureFlags::is_bulk_labels_enabled() ) {
			return;
		}

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'auto_assign_packages' ),
					'permission_callback' => array( WC_Connect_Functions::class, 'user_can_manage_labels' ),
				),
			)
		);
	}

	/**
	 * Handle POST /wcshipping/v1/label/auto-assign-packages.
	 *
	 * @param WP_REST_Request $request REST request.
	 * @return \WP_REST_Response|WP_Error REST response with per-order assignment results, or a validation error.
	 */
	public function auto_assign_packages( WP_REST_Request $request ) {
		$params    = $request->get_json_params();
		$order_ids = $params['order_ids'] ?? null;

		if ( ! is_array( $order_ids ) || empty( $order_ids ) ) {
			return $this->invalid_batch_payload(
				__( 'order_ids must be a non-empty array of positive integers.', 'woocommerce-shipping' )
			);
		}

		if ( count( $order_ids ) > self::BATCH_SIZE_CAP ) {
			return new WP_Error(
				'batch_too_large',
				sprintf(
					/* translators: %d: maximum number of orders allowed in a single batch. */
					__( 'Batch exceeds the maximum of %d orders.', 'woocommerce-shipping' ),
					self::BATCH_SIZE_CAP
				),
				array( 'status' => 400 )
			);
		}

		$sanitized = array();
		foreach ( $order_ids as $candidate ) {
			if ( ! is_int( $candidate ) && ! ( is_string( $candidate ) && ctype_digit( $candidate ) ) ) {
				return $this->invalid_batch_payload(
					__( 'order_ids must be a non-empty array of positive integers.', 'woocommerce-shipping' )
				);
			}

			$id = (int) $candidate;
			if ( $id <= 0 ) {
				return $this->invalid_batch_payload(
					__( 'order_ids must be a non-empty array of positive integers.', 'woocommerce-shipping' )
				);
			}

			$sanitized[] = $id;
		}

		// The result map is keyed by order_id, so duplicate ids would collapse
		// into a single entry and break request->response cardinality. Reject
		// them up front rather than silently accepting and returning fewer
		// rows than the caller submitted.
		if ( count( $sanitized ) !== count( array_unique( $sanitized, SORT_NUMERIC ) ) ) {
			return $this->invalid_batch_payload(
				__( 'order_ids must contain unique positive integers.', 'woocommerce-shipping' )
			);
		}

		return rest_ensure_response( $this->service->assign_for_orders( $sanitized ) );
	}

	/**
	 * Build a 400 response for an invalid batch payload.
	 *
	 * The four input-validation branches in `auto_assign_packages()` all
	 * surface the same `invalid_batch_payload` error code at HTTP 400; this
	 * helper keeps the construction in one place so the code, status, and
	 * shape stay aligned even if the per-branch message text drifts.
	 *
	 * @param string $message Translated, branch-specific error message.
	 *
	 * @return WP_Error
	 */
	private function invalid_batch_payload( string $message ): WP_Error {
		return new WP_Error(
			'invalid_batch_payload',
			$message,
			array( 'status' => 400 )
		);
	}
}