<?php

/**
 * Can create WC orders.
 *
 * @package WooCommerce\PayPalCommerce\Button\Helper
 */
declare (strict_types=1);
namespace WooCommerce\PayPalCommerce\Button\Helper;

use Exception;
use RuntimeException;
use WC_Cart;
use WC_Customer;
use WC_Data_Exception;
use WC_Order;
use WC_Order_Item_Product;
use WC_Order_Item_Shipping;
use WC_Product;
use WC_Subscription;
use WC_Subscriptions_Product;
use WC_Tax;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingFactory;
use WooCommerce\PayPalCommerce\Button\Session\CartData;
use WooCommerce\PayPalCommerce\Button\Session\CartDataFactory;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WP_Error;
/**
 * Class WooCommerceOrderCreator
 */
class WooCommerceOrderCreator
{
    /**
     * The funding source renderer.
     *
     * @var FundingSourceRenderer
     */
    protected $funding_source_renderer;
    /**
     * The Session handler.
     *
     * @var SessionHandler
     */
    protected $session_handler;
    /**
     * The subscription helper
     *
     * @var SubscriptionHelper
     */
    protected $subscription_helper;
    protected CartDataFactory $cart_data_factory;
    protected ShippingFactory $shipping_factory;
    protected PayerFactory $payer_factory;
    public function __construct(FundingSourceRenderer $funding_source_renderer, SessionHandler $session_handler, SubscriptionHelper $subscription_helper, CartDataFactory $cart_data_factory, ShippingFactory $shipping_factory, PayerFactory $payer_factory)
    {
        $this->funding_source_renderer = $funding_source_renderer;
        $this->session_handler = $session_handler;
        $this->subscription_helper = $subscription_helper;
        $this->cart_data_factory = $cart_data_factory;
        $this->shipping_factory = $shipping_factory;
        $this->payer_factory = $payer_factory;
    }
    /**
     * Creates WC order based on given PayPal order.
     *
     * @param Order            $order The PayPal order.
     * @param WC_Cart|CartData $cart The WC cart (converted into CartData).
     * @param array|null       $paypal_data The PayPal Response Data.
     *
     * @throws RuntimeException If problem creating.
     */
    public function create_from_paypal_order(Order $order, $cart, ?array $paypal_data = null): WC_Order
    {
        $cart_data = $cart;
        if ($cart_data instanceof WC_Cart) {
            $cart_data = $this->cart_data_factory->from_current_cart($cart_data);
        }
        $wc_order = wc_create_order();
        if (!$wc_order instanceof WC_Order) {
            throw new RuntimeException('Problem creating WC order.');
        }
        try {
            $payer = $this->get_payer($order, $paypal_data);
            $shipping = $this->get_shipping($order, $paypal_data);
            $this->configure_payment_source($wc_order);
            $this->configure_customer($wc_order, $cart_data);
            $this->configure_line_items($wc_order, $cart_data, $payer, $shipping);
            $this->configure_addresses($wc_order, $payer, $shipping, $cart_data->needs_shipping());
            $this->configure_coupons($wc_order, $cart_data->coupons());
            $wc_order->calculate_totals();
            $wc_order->save();
        } catch (Exception $exception) {
            $wc_order->delete(\true);
            throw new RuntimeException('Failed to create WooCommerce order: ' . $exception->getMessage());
        }
        do_action('woocommerce_paypal_payments_woocommerce_order_created_from_cart', $wc_order, $cart_data);
        return $wc_order;
    }
    /**
     * Configures the line items.
     *
     * @psalm-suppress InvalidScalarArgument
     */
    protected function configure_line_items(WC_Order $wc_order, CartData $cart_data, ?Payer $payer, ?Shipping $shipping): void
    {
        foreach ($cart_data->items() as $cart_item) {
            $product_id = $cart_item['product_id'] ?? 0;
            $variation_id = $cart_item['variation_id'] ?? 0;
            $quantity = $cart_item['quantity'] ?? 0;
            $variation_attributes = $cart_item['variation'];
            $item = new WC_Order_Item_Product();
            $item->set_product_id($product_id);
            $item->set_quantity($quantity);
            if (isset($cart_item['bundled_by'])) {
                $item->add_meta_data('_bundled_by', $cart_item['bundled_by'], \true);
            }
            if (isset($cart_item['bundled_item_id'])) {
                $item->add_meta_data('_bundled_item_id', $cart_item['bundled_item_id'], \true);
            }
            if (isset($cart_item['key'])) {
                $item->add_meta_data('_bundle_cart_key', $cart_item['key'], \true);
            }
            if ($variation_id) {
                $item->set_variation_id($variation_id);
                $item->set_variation($variation_attributes);
            }
            $product = wc_get_product($variation_id ?: $product_id);
            if (!$product) {
                return;
            }
            $subtotal = apply_filters('woocommerce_paypal_payments_shipping_callback_cart_line_item_total', $cart_item['line_subtotal'], $cart_item);
            $item->set_name($product->get_name());
            $item->set_subtotal($subtotal);
            $item->set_total($cart_item['line_total']);
            $this->configure_taxes($product, $item, $item->get_total());
            $product_id = $product->get_id();
            if ($this->is_subscription($product_id)) {
                $subscription = $this->create_subscription($wc_order, $product_id);
                $sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee($product);
                $subscription_total = (float) $subtotal + (float) $sign_up_fee;
                $item->set_subtotal((string) $subscription_total);
                $item->set_total((string) $subscription_total);
                $subscription->add_product($product);
                $this->configure_addresses($subscription, $payer, $shipping, $cart_data->needs_shipping());
                $this->configure_payment_source($subscription);
                $this->configure_coupons($subscription, $cart_data->coupons());
                $dates = array('trial_end' => WC_Subscriptions_Product::get_trial_expiration_date($product_id), 'next_payment' => WC_Subscriptions_Product::get_first_renewal_payment_date($product_id), 'end' => WC_Subscriptions_Product::get_expiration_date($product_id));
                $subscription->update_dates($dates);
                $subscription->calculate_totals();
                $subscription->payment_complete_for_order($wc_order);
            }
            $wc_order->add_item($item);
        }
    }
    /**
     * Configures the shipping & billing addresses for WC order from given payer.
     *
     * @throws WC_Data_Exception|RuntimeException When failing to configure shipping.
     * @psalm-suppress RedundantConditionGivenDocblockType
     */
    protected function configure_addresses(WC_Order $wc_order, ?Payer $payer, ?Shipping $shipping, bool $needs_shipping): void
    {
        $shipping_address = null;
        $billing_address = null;
        $shipping_options = null;
        $wc_customer = WC()->customer;
        if (!$shipping && $needs_shipping) {
            if ($wc_customer instanceof WC_Customer) {
                $shipping = $this->shipping_factory->from_wc_customer($wc_customer, \true);
            }
        }
        if ($payer) {
            $address = $payer->address();
            $payer_name = $payer->name();
            $payer_phone = $payer->phone();
            $wc_email = null;
            if ($wc_customer instanceof WC_Customer) {
                $wc_email = $wc_customer->get_email();
            }
            $email = $wc_email ?: $payer->email_address();
            $billing_address = array('email' => $email ?: '', 'first_name' => $payer_name ? $payer_name->given_name() : '', 'last_name' => $payer_name ? $payer_name->surname() : '', 'address_1' => $address ? $address->address_line_1() : '', 'address_2' => $address ? $address->address_line_2() : '', 'city' => $address ? $address->admin_area_2() : '', 'state' => $address ? $address->admin_area_1() : '', 'postcode' => $address ? $address->postal_code() : '', 'country' => $address ? $address->country_code() : '', 'phone' => $payer_phone ? $payer_phone->phone()->national_number() : '');
        }
        if ($shipping) {
            $address = $shipping->address();
            if ($address) {
                $shipping_address = array('first_name' => $shipping->name(), 'last_name' => '', 'address_1' => $address->address_line_1(), 'address_2' => $address->address_line_2(), 'city' => $address->admin_area_2(), 'state' => $address->admin_area_1(), 'postcode' => $address->postal_code(), 'country' => $address->country_code());
            }
            $shipping_options = $shipping->options()[0] ?? '';
        }
        if ($needs_shipping && empty($shipping_options)) {
            throw new RuntimeException('No shipping method has been selected.');
        }
        if ($shipping_address) {
            $wc_order->set_shipping_address($shipping_address);
        }
        if ($billing_address || $shipping_address) {
            $wc_order->set_billing_address($billing_address ?: $shipping_address);
        }
        if ($shipping_options) {
            $shipping = new WC_Order_Item_Shipping();
            $shipping->set_method_title($shipping_options->label());
            $shipping->set_method_id($shipping_options->id());
            $shipping->set_total($shipping_options->amount()->value_str());
            $items = $wc_order->get_items();
            $items_in_package = array();
            foreach ($items as $item) {
                $items_in_package[] = $item->get_name() . ' &times; ' . (string) $item->get_quantity();
            }
            $shipping->add_meta_data(__('Items', 'woocommerce-paypal-payments'), implode(', ', $items_in_package));
            $wc_order->add_item($shipping);
        }
        $wc_order->calculate_totals();
    }
    /**
     * Configures the payment source.
     *
     * @param WC_Order $wc_order The WC order.
     *
     * @return void
     */
    protected function configure_payment_source(WC_Order $wc_order): void
    {
        $funding_source = $this->session_handler->funding_source();
        $wc_order->set_payment_method(PayPalGateway::ID);
        if ($funding_source) {
            $wc_order->set_payment_method_title($this->funding_source_renderer->render_name($funding_source));
        }
    }
    /**
     * Configures the customer ID.
     */
    protected function configure_customer(WC_Order $wc_order, CartData $cart_data): void
    {
        $current_user = wp_get_current_user();
        if ($current_user->ID !== 0) {
            $wc_order->set_customer_id($current_user->ID);
            return;
        }
        $saved_user_id = $cart_data->user_id();
        if ($saved_user_id !== 0) {
            $wc_order->set_customer_id($saved_user_id);
        }
    }
    /**
     * Configures the applied coupons.
     *
     * @param WC_Order $wc_order The WC order.
     * @param string[] $coupons The list of applied coupons.
     *
     * @return void
     */
    protected function configure_coupons(WC_Order $wc_order, array $coupons): void
    {
        foreach ($coupons as $coupon_code) {
            $wc_order->apply_coupon($coupon_code);
        }
    }
    /**
     * Configures the taxes.
     *
     * @param WC_Product            $product The Product.
     * @param WC_Order_Item_Product $item The line item.
     * @param float|string          $subtotal The subtotal.
     *
     * @return void
     * @psalm-suppress InvalidScalarArgument
     */
    protected function configure_taxes(WC_Product $product, WC_Order_Item_Product $item, $subtotal): void
    {
        $tax_rates = WC_Tax::get_rates($product->get_tax_class());
        $taxes = WC_Tax::calc_tax($subtotal, $tax_rates, \true);
        $item->set_tax_class($product->get_tax_class());
        $item->set_total_tax((string) array_sum($taxes));
    }
    /**
     * Checks if the product with given ID is WC subscription.
     *
     * @param int $product_id The product ID.
     *
     * @return bool true if the product is subscription, otherwise false.
     */
    protected function is_subscription(int $product_id): bool
    {
        if (!$this->subscription_helper->plugin_is_active()) {
            return \false;
        }
        return WC_Subscriptions_Product::is_subscription($product_id);
    }
    /**
     * Creates WC subscription from given order and product ID.
     *
     * @param WC_Order $wc_order The WC order.
     * @param int      $product_id The product ID.
     *
     * @return WC_Subscription The subscription order
     * @throws RuntimeException If problem creating.
     */
    protected function create_subscription(WC_Order $wc_order, int $product_id): WC_Subscription
    {
        $subscription = wcs_create_subscription(array('order_id' => $wc_order->get_id(), 'status' => 'pending', 'billing_period' => WC_Subscriptions_Product::get_period($product_id), 'billing_interval' => WC_Subscriptions_Product::get_interval($product_id), 'customer_id' => $wc_order->get_customer_id()));
        if ($subscription instanceof WP_Error) {
            throw new RuntimeException($subscription->get_error_message());
        }
        return $subscription;
    }
    /**
     * Get the Payer from the PayPal order with fallback to the PayPal response data.
     *
     * @param Order      $order The PayPal Order.
     * @param array|null $paypal_data The PayPal Response data.
     *
     * @return Payer|null The Payer object or null if no payer information is available.
     */
    private function get_payer(Order $order, ?array $paypal_data = null): ?Payer
    {
        $payer = $order->payer();
        if (is_null($payer) && isset($paypal_data['payer'])) {
            $payer_data = json_decode(wp_json_encode($paypal_data['payer']) ?: '');
            $payer = $this->payer_factory->from_paypal_response($payer_data);
        }
        return $payer;
    }
    /**
     * Get the Shipping information from the PayPal order with fallback to the PayPal response data.
     *
     * @param Order      $order The PayPal Order.
     * @param array|null $paypal_data The PayPal Response data.
     *
     * @return Shipping|null The shipping object or null if no shipping information is available.
     */
    private function get_shipping(Order $order, ?array $paypal_data = null): ?Shipping
    {
        $purchase_units = $order->purchase_units();
        $shipping = !empty($purchase_units) ? $purchase_units[0]->shipping() : null;
        if ($shipping && is_null($shipping->address()) && isset($paypal_data['shipping_address'])) {
            $paypal_data['shipping_address']['options'] = array_map(function ($option) {
                return $option->to_array();
            }, $shipping->options());
            $shipping_address_data = json_decode(wp_json_encode($paypal_data['shipping_address']) ?: '');
            $shipping = $this->shipping_factory->from_paypal_response($shipping_address_data);
        }
        return apply_filters('woocommerce_paypal_payments_order_creator_get_shipping', $shipping, $order, $paypal_data);
    }
}
