<?php
/**
 * Endpoint to update payment data like shipping method and address.
 *
 * @package WooCommerce\PayPalCommerce\Button\Endpoint
 */

declare(strict_types=1);

namespace WooCommerce\PayPalCommerce\Googlepay\Endpoint;

use Psr\Log\LoggerInterface;
use Throwable;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;

/**
 * Class UpdatePaymentDataEndpoint
 */
class UpdatePaymentDataEndpoint {

	const ENDPOINT = 'ppc-googlepay-update-payment-data';

	/**
	 * The request data helper.
	 *
	 * @var RequestData
	 */
	private $request_data;

	/**
	 * The logger.
	 *
	 * @var LoggerInterface
	 */
	private $logger;

	/**
	 * UpdatePaymentDataEndpoint constructor.
	 *
	 * @param RequestData     $request_data The request data helper.
	 * @param LoggerInterface $logger The logger.
	 */
	public function __construct(
		RequestData $request_data,
		LoggerInterface $logger
	) {
		$this->request_data = $request_data;
		$this->logger       = $logger;
	}

	/**
	 * Returns the nonce.
	 *
	 * @return string
	 */
	public static function nonce(): string {
		return self::ENDPOINT;
	}

	/**
	 * Handles the request.
	 *
	 * @return bool
	 * @throws RuntimeException When a validation fails.
	 */
	public function handle_request(): bool {
		try {
			$data = $this->request_data->read_request( $this->nonce() );

			// Validate payment data.
			if ( ! isset( $data['paymentData'] ) ) {
				throw new RuntimeException(
					__( 'No paymentData provided.', 'woocommerce-paypal-payments' )
				);
			}

			$payment_data = $data['paymentData'];

			// Set context as cart.
			if ( is_callable( 'wc_maybe_define_constant' ) ) {
				wc_maybe_define_constant( 'WOOCOMMERCE_CART', true );
			}

			$this->update_addresses( $payment_data );
			$this->update_shipping_method( $payment_data );

			WC()->cart->calculate_shipping();
			WC()->cart->calculate_fees();
			WC()->cart->calculate_totals();

			$total = (float) WC()->cart->get_total( 'numeric' );

			// Shop settings.
			$base_location     = wc_get_base_location();
			$shop_country_code = $base_location['country'];
			$currency_code     = get_woocommerce_currency();

			wp_send_json_success(
				array(
					'total'            => $total,
					'total_str'        => ( new Money( $total, $currency_code ) )->value_str(),
					'currency_code'    => $currency_code,
					'country_code'     => $shop_country_code,
					'shipping_options' => $this->get_shipping_options(),
				)
			);

			return true;
		} catch ( Throwable $error ) {
			$this->logger->error( "UpdatePaymentDataEndpoint execution failed. {$error->getMessage()} {$error->getFile()}:{$error->getLine()}" );

			wp_send_json_error();
			return false;
		}
	}

	/**
	 * Returns the array of available shipping methods.
	 *
	 * @return array
	 */
	public function get_shipping_options(): array {
		$shipping_options = array();

		$calculated_packages = WC()->shipping->calculate_shipping(
			WC()->cart->get_shipping_packages()
		);

		if ( ! isset( $calculated_packages[0] ) && ! isset( $calculated_packages[0]['rates'] ) ) {
			return array();
		}

		foreach ( $calculated_packages[0]['rates'] as $rate ) {
			/**
			 * The shipping rate.
			 *
			 * @var \WC_Shipping_Rate $rate
			 */
			$shipping_options[] = array(
				'id'          => $rate->get_id(),
				'label'       => $rate->get_label(),
				'description' => html_entity_decode(
					wp_strip_all_tags(
						wc_price( (float) $rate->get_cost(), array( 'currency' => get_woocommerce_currency() ) )
					)
				),
			);
		}

		if ( ! isset( $shipping_options[0] ) ) {
			return array();
		}

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

		return array(
			'defaultSelectedOptionId' => ( $chosen_shipping_methods[0] ?? null ) ? $chosen_shipping_methods[0] : $shipping_options[0]['id'],
			'shippingOptions'         => $shipping_options,
		);
	}

	/**
	 * Update addresses.
	 *
	 * @param array $payment_data The payment data.
	 * @return void
	 */
	private function update_addresses( array $payment_data ): void {
		if ( ! in_array( $payment_data['callbackTrigger'] ?? '', array( 'SHIPPING_ADDRESS', 'INITIALIZE' ), true ) ) {
			return;
		}

		/**
		 * The shipping methods.
		 *
		 * @var \WC_Customer|null $customer
		 */
		$customer = WC()->customer;

		if ( ! $customer ) {
			return;
		}

		$customer->set_billing_postcode( $payment_data['shippingAddress']['postalCode'] ?? '' );
		$customer->set_billing_country( $payment_data['shippingAddress']['countryCode'] ?? '' );
		$customer->set_billing_state( '' );
		$customer->set_billing_city( $payment_data['shippingAddress']['locality'] ?? '' );

		$customer->set_shipping_postcode( $payment_data['shippingAddress']['postalCode'] ?? '' );
		$customer->set_shipping_country( $payment_data['shippingAddress']['countryCode'] ?? '' );
		$customer->set_shipping_state( '' );
		$customer->set_shipping_city( $payment_data['shippingAddress']['locality'] ?? '' );

		// Save the data.
		$customer->save();

		WC()->session->set( 'customer', WC()->customer->get_data() );
	}

	/**
	 * Update shipping method.
	 *
	 * @param array $payment_data The payment data.
	 * @return void
	 */
	private function update_shipping_method( array $payment_data ): void {
		$rate_id             = $payment_data['shippingOptionData']['id'];
		$calculated_packages = WC()->shipping->calculate_shipping(
			WC()->cart->get_shipping_packages()
		);

		if ( $rate_id && isset( $calculated_packages[0]['rates'][ $rate_id ] ) ) {
			WC()->session->set( 'chosen_shipping_methods', array( $rate_id ) );
		}
	}

}
