<?php
/**
 * Payment request factory.
 *
 * @package flo-checkout
 */

namespace Flo_Checkout\Gateways\Apple_Pay;

use Exception;
use Flo_Checkout\Gateways\Shift4_Apple_Pay;
use WC_Product;

defined( 'ABSPATH' ) || exit;

/**
 * Class Payment_Request_Factory
 */
readonly class Payment_Request_Factory {

	/**
	 * Payment_Request_Factory constructor.
	 *
	 * @param Shift4_Apple_Pay $gateway The Apple Pay gateway.
	 * @param bool             $allow_coupon_codes Whether to allow coupon codes.
	 */
	public function __construct( private Shift4_Apple_Pay $gateway, private bool $allow_coupon_codes = true ) {}

	/**
	 * AJAX request handler.
	 *
	 * @return void
	 */
	public function ajax_get_request(): void {
		try {
			$request_data = $this->get_request();
			wp_send_json_success( $request_data );
		} catch ( Exception $e ) {
			wp_send_json_error( $e->getMessage() );
		}
	}

	/**
	 * Get the frontend data for Apple Pay.
	 *
	 * @return array
	 * @throws Exception If request data is invalid.
	 */
	public function get_request(): array {
		$total        = WC()->cart->get_total( 'raw' );
		$request_data = array(
			'total'                        => array(
				'label'  => $this->gateway->store_name,
				'amount' => $total,
				'type'   => 'final',
			),
			'lineItems'                    => $this->get_line_items( (float) $total ),
			'countryCode'                  => 'SE',
			'currencyCode'                 => 'SEK',
			'merchantCapabilities'         => $this->get_merchant_capabilities(),
			'supportedNetworks'            => $this->get_supported_networks(),
			'shippingType'                 => $this->get_shipping_type(),
			'requiredBillingContactFields' => $this->get_required_billing_contact_fields(),
			'supportsCouponCode'           => $this->allow_coupon_codes && wc_coupons_enabled(),
		);

		$request_data = apply_filters(
			'flo_checkout_apple_pay_request_data_pre_shipping',
			$request_data
		);

		$this->append_shipping_info( $request_data );

		return apply_filters(
			'flo_checkout_apple_pay_request_data',
			$request_data
		);
	}

	/**
	 * Get the line items for the Apple Pay payment request.
	 *
	 * @param float $total The cart total amount.
	 *
	 * @return array
	 * @throws Exception If the line items total doesn't match the total amount.
	 */
	private function get_line_items( float $total ): array {
		$line_items_total = 0;
		$line_items       = array();
		$cart_items       = WC()->cart->get_cart();
		if ( ! empty( $cart_items ) ) {
			foreach ( $cart_items as $cart_item ) {
				/**
				 * Cart item.
				 *
				 * @var WC_Product $product
				 */
				$product    = $cart_item['data'];
				$price      = $product->get_price();
				$quantity   = $cart_item['quantity'];
				$item_total = $price * $quantity;

				$line_items[] = array(
					'label'  => $product->get_name(),
					'amount' => $item_total,
					'type'   => 'final',
				);

				$line_items_total += (float) $item_total;
			}
		}

		$add_free_shipping_item = apply_filters(
			'flo_checkout_apple_pay_add_free_shipping_item',
			true,
		);

		// Get shipping total including tax.
		$shipping_total = WC()->cart->get_shipping_total() + WC()->cart->get_shipping_tax();
		if ( $shipping_total > 0 ) {
			$line_items[]      = array(
				'label'  => __( 'Shipping', 'flo-checkout-plugin' ),
				'amount' => $shipping_total,
				'type'   => 'final',
			);
			$line_items_total += $shipping_total;

		} elseif ( $add_free_shipping_item ) {
			$line_items[] = array(
				'label'  => __( 'Free shipping', 'flo-checkout-plugin' ),
				'amount' => 0,
				'type'   => 'final',
			);
		}

		// Get the cart discounts.
		$cart_discounts = WC()->cart->get_cart_discount_total() + WC()->cart->get_cart_discount_tax_total();
		if ( $cart_discounts > 0 ) {
			$line_items[]      = array(
				'label'  => __( 'Discount', 'flo-checkout-plugin' ),
				'amount' => $cart_discounts,
				'type'   => 'final',
			);
			$line_items_total -= $cart_discounts;
		}

		if ( $line_items_total !== $total ) {
			$this->gateway->log_and_throw_error(
				__( 'Failed to process cart.', 'flo-checkout-plugin' ),
				array(
					'line_items_total' => $line_items_total,
					'total'            => $total,
					'line_items'       => $line_items,
				),
				'Line items total does not match the total amount'
			);
		}

		return $line_items;
	}

	/**
	 * Get the shipping type for Apple Pay.
	 *
	 * @link https://developer.apple.com/documentation/apple_pay_on_the_web/applepaypaymentrequest/1916128-shippingtype
	 * @return string
	 */
	private function get_shipping_type(): string {

		$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );

		if ( is_array( $chosen_shipping_methods ) && ! empty( $chosen_shipping_methods ) ) {
			foreach ( $chosen_shipping_methods as $shipping_method ) {
				if ( str_contains( $shipping_method, 'local_pickup' ) ) {
					return 'storePickup';
				}
			}
		}

		return 'shipping';
	}

	/**
	 * Get the merchant capabilities for Apple Pay.
	 *
	 * @link https://developer.apple.com/documentation/apple_pay_on_the_web/applepaypaymentrequest/1916123-merchantcapabilities
	 * @return string[]
	 */
	private function get_merchant_capabilities(): array {
		return array(
			'supports3DS',
		);
	}

	/**
	 * Get the supported networks for Apple Pay.
	 *
	 * @link https://developer.apple.com/documentation/apple_pay_on_the_web/applepaypaymentrequest/1916122-supportednetworks
	 * @return string[]
	 * @throws Exception On failure to get supported payment networks.
	 */
	private function get_supported_networks(): array {
		$networks = $this->gateway->get_option( 'payment_networks' );
		if ( empty( $networks ) || ! is_array( $networks ) ) {
			$this->gateway->log_and_throw_error(
				__( 'Failed to get supported payment networks.', 'flo-checkout-plugin' ),
				array(
					'networks' => $networks,
				),
			);
		}

		/**
		 * Networks is an array.
		 *
		 * @var string[] $networks
		 */
		return $networks;
	}

	/**
	 * Get the required billing contact fields for Apple Pay.
	 *
	 * @link https://developer.apple.com/documentation/apple_pay_on_the_web/applepaycontactfield
	 *
	 * @return string[]
	 */
	private function get_required_billing_contact_fields(): array {
		return array(
			'name',
			'phone',
			'email',
			'postalAddress',
		);
	}

	/**
	 * Maybe append the shipping contact to the request data.
	 *
	 * @param array $request_data The request data.
	 */
	private function append_shipping_info( array &$request_data ): void {
		if (
			'storePickup' === $request_data['shippingType'] ||
			'servicePickup' === $request_data['shippingType']
		) {
			$request_data['requiredShippingContactFields'] = array(
				'phone',
				'email',
				'postalAddress',
			);

			$request_data['shippingContact'] = array(
				'addressLines' => array(
					WC()->countries->get_base_address(),
					WC()->countries->get_base_address_2(),
				),
				'locality'     => WC()->countries->get_base_city(),
				'postalCode'   => WC()->countries->get_base_postcode(),
				'countryCode'  => WC()->countries->get_base_country(),
				'familyName'   => $this->gateway->store_name,
			);

			$request_data['shippingContactEditingMode'] = 'storePickup';
		} else {
			$request_data['requiredShippingContactFields'] = array(
				'name',
				'postalAddress',
				'phone',
				'email',
			);
		}
	}
}
