<?php
/**
 * Fulfillment Order Payload Builder
 *
 * Builds Amazon MCF createFulfillmentOrder payloads from WooCommerce orders.
 *
 * @package    MOMCS_Sync
 * @subpackage MOMCS_Sync/includes/common/services/amazon
 * @author     miniOrange
 */

declare(strict_types=1);

namespace MOMCS_Sync\Includes\Common\Services\Amazon;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Builder for outbound Amazon fulfillment payloads.
 */
class Fulfillment_Order_Payload_Builder {

	/**
	 * WooCommerce order.
	 *
	 * @var \WC_Order
	 */
	private \WC_Order $order;

	/**
	 * Channel UUID.
	 *
	 * @var string
	 */
	private string $channel_uuid;

	/**
	 * Channel marketplace ID.
	 *
	 * @var string
	 */
	private string $marketplace_id;

	/**
	 * Constructor.
	 *
	 * @param \WC_Order $order WooCommerce order.
	 * @param string    $channel_uuid Channel UUID.
	 * @param string    $marketplace_id Amazon marketplace ID.
	 */
	public function __construct( \WC_Order $order, string $channel_uuid, string $marketplace_id ) {
		$this->order          = $order;
		$this->channel_uuid   = $channel_uuid;
		$this->marketplace_id = $marketplace_id;
	}

	/**
	 * Build createFulfillmentOrder payload.
	 *
	 * @param string $seller_fulfillment_order_id Existing seller fulfillment order ID.
	 * @return array|\WP_Error
	 */
	public function build( string $seller_fulfillment_order_id = '' ) {
		$destination_address = $this->build_destination_address();
		if ( is_wp_error( $destination_address ) ) {
			return $destination_address;
		}

		$items = $this->build_items();
		if ( is_wp_error( $items ) ) {
			return $items;
		}

		$seller_fulfillment_order_id = '' !== $seller_fulfillment_order_id ? $seller_fulfillment_order_id : $this->generate_fulfillment_order_id();
		$order_date                  = $this->order->get_date_created();

		if ( ! $order_date ) {
			return new \WP_Error( 'invalid_order_date', __( 'Order creation date is missing.', 'multi-channel-for-woocommerce' ) );
		}

		$payload = array(
			'marketplaceId'           => $this->marketplace_id,
			'sellerFulfillmentOrderId' => $seller_fulfillment_order_id,
			'displayableOrderId'      => (string) $this->order->get_order_number(),
			'displayableOrderDate'    => gmdate( 'Y-m-d\TH:i:s\Z', $order_date->getTimestamp() ),
			'displayableOrderComment' => sprintf(
				/* translators: %s: WooCommerce order number. */
				__( 'WooCommerce Order #%s', 'multi-channel-for-woocommerce' ),
				$this->order->get_order_number()
			),
			'shippingSpeedCategory'   => $this->resolve_shipping_speed(),
			'fulfillmentAction'       => 'Ship',
			'destinationAddress'      => $destination_address,
			'items'                   => $items,
		);

		$emails = $this->get_notification_emails();
		if ( ! empty( $emails ) ) {
			$payload['notificationEmails'] = $emails;
		}

		/**
		 * Filters outbound Amazon fulfillment payload before API dispatch.
		 *
		 * @param array    $payload      Payload for createFulfillmentOrder.
		 * @param \WC_Order $order       WooCommerce order object.
		 * @param string   $channel_uuid Channel UUID.
		 */
		return apply_filters( 'momcs_amazon_fulfillment_payload', $payload, $this->order, $this->channel_uuid );
	}

	/**
	 * Build destination address payload.
	 *
	 * @return array|\WP_Error
	 */
	private function build_destination_address() {
		$first_name = (string) $this->order->get_shipping_first_name();
		$last_name  = (string) $this->order->get_shipping_last_name();
		$name       = trim( $first_name . ' ' . $last_name );

		$address_1 = (string) $this->order->get_shipping_address_1();
		$address_2 = (string) $this->order->get_shipping_address_2();
		$city      = (string) $this->order->get_shipping_city();
		$state     = (string) $this->order->get_shipping_state();
		$postcode  = (string) $this->order->get_shipping_postcode();
		$country   = (string) $this->order->get_shipping_country();
		$phone     = (string) $this->order->get_billing_phone();

		// Fallback to billing address when shipping address is not provided.
		if ( '' === $address_1 ) {
			$address_1 = (string) $this->order->get_billing_address_1();
			$address_2 = (string) $this->order->get_billing_address_2();
			$city      = (string) $this->order->get_billing_city();
			$state     = (string) $this->order->get_billing_state();
			$postcode  = (string) $this->order->get_billing_postcode();
			$country   = (string) $this->order->get_billing_country();
			$name      = trim( (string) $this->order->get_billing_first_name() . ' ' . (string) $this->order->get_billing_last_name() );
		}

		if ( '' === $name || '' === $address_1 || '' === $city || '' === $postcode || '' === $country ) {
			return new \WP_Error(
				'missing_address_fields',
				__( 'Required shipping fields are missing for Amazon fulfillment.', 'multi-channel-for-woocommerce' )
			);
		}

		$address = array(
			'name'          => mb_substr( $name, 0, 50 ),
			'addressLine1'  => mb_substr( $address_1, 0, 180 ),
			'city'          => mb_substr( $city, 0, 50 ),
			'stateOrRegion' => mb_substr( $state, 0, 50 ),
			'postalCode'    => mb_substr( $postcode, 0, 20 ),
			'countryCode'   => strtoupper( $country ),
		);

		if ( '' !== $address_2 ) {
			$address['addressLine2'] = mb_substr( $address_2, 0, 180 );
		}

		if ( '' !== $phone ) {
			$address['phoneNumber'] = mb_substr( preg_replace( '/\s+/', '', $phone ), 0, 25 );
		}

		return $address;
	}

	/**
	 * Build line items payload.
	 *
	 * @return array<int, array<string, mixed>>|\WP_Error
	 */
	private function build_items() {
		$items = array();

		foreach ( $this->order->get_items( 'line_item' ) as $item_id => $item ) {
			if ( ! $item instanceof \WC_Order_Item_Product ) {
				continue;
			}

			$product = $item->get_product();
			if ( ! $product ) {
				return new \WP_Error(
					'product_missing',
					sprintf(
						/* translators: %d: WooCommerce order item ID. */
						__( 'Product missing for order item %d.', 'multi-channel-for-woocommerce' ),
						$item_id
					)
				);
			}

			$sku = (string) $product->get_meta( '_amazon_mcf_sku' );
			if ( '' === $sku ) {
				$sku = (string) $product->get_sku();
			}

			if ( '' === $sku ) {
				return new \WP_Error(
					'sku_missing',
					sprintf(
						/* translators: %s: Product name. */
						__( 'SKU missing for "%s".', 'multi-channel-for-woocommerce' ),
						$item->get_name()
					)
				);
			}

			$quantity = (int) $item->get_quantity();
			if ( $quantity <= 0 ) {
				continue;
			}

			$items[] = array(
				'sellerSku'                    => $sku,
				'sellerFulfillmentOrderItemId' => (string) $item_id,
				'quantity'                     => $quantity,
			);
		}

		if ( empty( $items ) ) {
			return new \WP_Error( 'no_items_for_fulfillment', __( 'No eligible order items found for Amazon fulfillment.', 'multi-channel-for-woocommerce' ) );
		}

		return $items;
	}

	/**
	 * Resolve shipping speed category for Amazon.
	 *
	 * @return string
	 */
	private function resolve_shipping_speed(): string {
		$speed = 'Standard';

		$shipping_methods = $this->order->get_shipping_methods();
		$shipping_method  = reset( $shipping_methods );
		$method_id        = $shipping_method ? (string) $shipping_method->get_method_id() : '';

		if ( false !== strpos( $method_id, 'express' ) || false !== strpos( $method_id, 'expedited' ) ) {
			$speed = 'Expedited';
		}
		if ( false !== strpos( $method_id, 'priority' ) || false !== strpos( $method_id, 'overnight' ) ) {
			$speed = 'Priority';
		}

		/**
		 * Filters shipping speed category used for Amazon fulfillment.
		 *
		 * @param string    $speed Default/derived speed.
		 * @param string    $method_id Shipping method ID.
		 * @param \WC_Order $order WooCommerce order.
		 */
		$speed = (string) apply_filters( 'momcs_amazon_fulfillment_shipping_speed', $speed, $method_id, $this->order );

		// Priority is not used in US marketplace due current MCF behavior.
		if ( 'ATVPDKIKX0DER' === $this->marketplace_id && 'Priority' === $speed ) {
			return 'Expedited';
		}

		if ( ! in_array( $speed, array( 'Standard', 'Expedited', 'Priority' ), true ) ) {
			return 'Standard';
		}

		return $speed;
	}

	/**
	 * Return notification emails.
	 *
	 * @return array<int, string>
	 */
	private function get_notification_emails(): array {
		$email = (string) $this->order->get_billing_email();
		if ( '' !== $email && is_email( $email ) ) {
			return array( $email );
		}
		return array();
	}

	/**
	 * Generate a stable seller fulfillment order ID for the order/channel pair.
	 *
	 * @return string
	 */
	private function generate_fulfillment_order_id(): string {
		$order_id  = (string) $this->order->get_id();
		$channel_h = strtoupper( substr( hash( 'sha256', $this->channel_uuid ), 0, 10 ) );
		$value     = 'WC-' . $order_id . '-' . $channel_h;
		return substr( $value, 0, 40 );
	}
}
