<?php
/**
 * Helper class to generate a Klarna payments request body for a order/session request.
 *
 * @package WC_Klarna_Payments/Classes/Requests/Helpers
 */

defined( 'ABSPATH' ) || exit;

use KrokedilKlarnaPaymentsDeps\Krokedil\WooCommerce\Cart\Cart;
use KrokedilKlarnaPaymentsDeps\Krokedil\WooCommerce\Order\Order;

/**
 * Helper class to generate a Klarna payments request body for a order/session request.
 */
class KP_Order_Data {
	/**
	 * The WooCommerce order id is used to calculate the order data. If null the cart will be used instead.
	 *
	 * @var int|null
	 */
	private $order_id;

	/**
	 * The WooCommerce order is used to calculate the order data. If null the cart will be used instead.
	 *
	 * @var WC_Order|null
	 */
	private $order;

	/**
	 * The Klarna country to use for the request body.
	 *
	 * @var string
	 */
	private $klarna_country;

	/**
	 * The Customer type to use for KP. Either B2B or B2C. Based on the setting in the plugin.
	 *
	 * @var string
	 */
	private $customer_type;

	/**
	 * The generated order data.
	 *
	 * @var KrokedilKlarnaPaymentsDeps\Krokedil\WooCommerce\OrderData
	 */
	public $order_data;

	/**
	 * If we should use separate sales tax or not for the requests.
	 *
	 * @var bool
	 */
	public $separate_sales_tax;

	/**
	 * Class constructor.
	 *
	 * @param string   $customer_type The Customer type to use for KP. Either B2B or B2C. Based on the setting in the plugin.
	 * @param int|null $order_id The WooCommerce order it used to calculate the order data. If null the cart will be used instead.
	 */
	public function __construct( $customer_type, $order_id = null ) {
		$this->customer_type      = $customer_type;
		$this->order_id           = $order_id;
		$this->order              = $this->order_id ? wc_get_order( $this->order_id ) : null;
		$this->klarna_country     = kp_get_klarna_country( $this->order );
		$this->order_data         = $this->get_order_data();
		$this->separate_sales_tax = 'US' === $this->klarna_country;
	}

	/**
	 * Returns the request helper for the request based on if we have a order id passed or not.
	 *
	 * @return KrokedilKlarnaPaymentsDeps\Krokedil\WooCommerce\OrderData
	 */
	public function get_order_data() {
		$config = array(
			'slug'         => 'kp',
			'price_format' => 'minor',
		);

		if ( $this->order_id ?? false && ! empty( $this->order_id ) ) {
			$order = wc_get_order( $this->order_id );
			return new Order( $order, $config );
		} else {
			return new Cart( WC()->cart, $config );
		}
	}

	/**
	 * Returns a formatted Klarna order object.
	 *
	 * @param KP_IFrame $iframe_options The options to use for the Klarna Payments iframes.
	 * @return array
	 */
	public function get_klarna_order_object( $iframe_options = null ) {
		$customer = $this->get_klarna_customer_object();

		return array(
			'purchase_country'  => $this->klarna_country,
			'purchase_currency' => empty( $order ) ? get_woocommerce_currency() : $order->get_currency(),
			'locale'            => kp_get_locale(),
			'order_amount'      => $this->order_data->get_total(),
			'order_tax_amount'  => $this->order_data->get_total_tax(),
			'order_lines'       => $this->get_klarna_order_lines_array(),
			'customer'          => get_klarna_customer( $this->customer_type ),
			'billing_address'   => $customer['billing'],
			'shipping_address'  => $customer['shipping'],
			'options'           => $iframe_options ? $iframe_options->get_kp_color_options() : array(),
			'merchant_urls'     => array(
				'authorization' => home_url( '/wc-api/KP_WC_AUTHORIZATION' ),
			),
		);
	}

	/**
	 * Returns an array of Klarna order line objects.
	 *
	 * @return array
	 */
	public function get_klarna_order_lines_array() {
		$klarna_order_lines = array();

		// Order items/products.
		foreach ( $this->order_data->get_line_items() as $item ) {
			$klarna_order_lines[] = $this->get_klarna_order_line_object( $item );
		}

		// Order Shipping.
		foreach ( $this->order_data->get_line_shipping() as $item ) {
			$klarna_order_lines[] = $this->get_klarna_order_line_object( $item );
		}

		// Order Fees.
		foreach ( $this->order_data->get_line_fees() as $item ) {
			$klarna_order_lines[] = $this->get_klarna_order_line_object( $item );
		}

		// Order compatibility, to support third party plugins that add orders lines that needs to be handled separately.
		foreach ( $this->order_data->get_line_compatibility() as $item ) {
			$klarna_order_lines[] = $this->get_klarna_order_line_object( $item );
		}

		// Order Coupons/Giftcards.
		foreach ( KP_WC()->krokedil->compatibility()->giftcards() as $giftcards ) {
			if ( false !== ( strpos( get_class( $giftcards ), 'WCGiftCards', true ) ) && ! function_exists( 'WC_GC' ) ) {
				continue;
			}

			$retrieved_giftcards = ! empty( $this->order ) ? $giftcards->get_order_giftcards( $this->order ) : $giftcards->get_cart_giftcards();
			foreach ( $retrieved_giftcards as $retrieved_giftcard ) {
				$klarna_order_lines[] = $this->get_klarna_order_line_object( $retrieved_giftcard );
			}
		}

		if ( $this->separate_sales_tax ) {
			$sales_tax_line       = array(
				'name'                  => __( 'Sales Tax', 'klarna-payments-for-woocommerce' ),
				'quantity'              => 1,
				'reference'             => __( 'Sales Tax', 'klarna-payments-for-woocommerce' ),
				'tax_rate'              => 0,
				'total_amount'          => (int) round( (float) $this->order_data->get_total_tax(), 0 ),
				'total_discount_amount' => 0,
				'total_tax_amount'      => 0,
				'type'                  => 'sales_tax',
				'unit_price'            => (int) round( (float) $this->order_data->get_total_tax(), 0 ),
			);
			$klarna_order_lines[] = $sales_tax_line;
		}

		return $klarna_order_lines;
	}

	/**
	 * Returns an array of Klarna order line objects for interoperability data.
	 *
	 * @return array
	 */
	public function get_klarna_order_lines_interoperability() {
		$klarna_order_lines = array();

		// Order shipping.
		$shipping_lines     = $this->order_data->get_line_shipping() ?? array();
		$klarna_shipping    = $this->get_klarna_shipping_line_object_interoperability( $shipping_lines[0] ?? null );
		$shipping_reference = $klarna_shipping['shipping_reference'] ?? '';

		// Order products.
		foreach ( $this->order_data->get_line_items() as $item ) {
			$klarna_order_lines[] = $this->get_klarna_order_line_object_interoperability( $item, $shipping_reference );
		}

		return array(
			'line_items' => $klarna_order_lines,
			'shipping'   => $klarna_shipping,
		);
	}

	/**
	 * Returns a formatted Klarna order line object for interoperability data.
	 *
	 * @param  KrokedilKlarnaPaymentsDeps\Krokedil\WooCommerce\OrderLineData $order_line The order line data.
	 * @param  string                                                        $shipping_reference The shipping reference to use for the order line.
	 * @return array
	 */
	public function get_klarna_order_line_object_interoperability( $order_line, $shipping_reference ) {

		$klarna_interoperability_item = array(
			'name'               => $order_line->get_name(),
			'quantity'           => $order_line->get_quantity(),
			'total_amount'       => (int) round( (float) ( $this->separate_sales_tax ? $order_line->get_total_amount() : $order_line->get_total_amount() + $order_line->get_total_tax_amount() ), 0 ),
			'unit_price'         => (int) round( (float) ( $this->separate_sales_tax ? $order_line->get_total_amount() : $order_line->get_total_amount() + $order_line->get_total_tax_amount() ), 0 ),
			'total_tax_amount'   => (int) round( (float) ( $this->separate_sales_tax ? 0 : $order_line->get_total_tax_amount() ), 0 ),
			'product_url'        => $order_line->get_product_url(),
			'image_url'          => $this->maybe_allow_order_line_url( $order_line->get_image_url() ),
			'product_identifier' => apply_filters( $order_line->get_filter_name( 'product_identifiers' ), array(), $order_line ),
			'reference'          => $order_line->get_sku(),
			'shipping_reference' => $shipping_reference,
		);

		return $klarna_interoperability_item;
	}

	/**
	 * Returns a formatted Klarna shipping line object for interoperability data.
	 *
	 * @param  KrokedilKlarnaPaymentsDeps\Krokedil\WooCommerce\OrderLineData $shipping_line The shipping line data.
	 * @return array
	 */
	public function get_klarna_shipping_line_object_interoperability( $shipping_line ) {
		$customer_data         = $this->order_data->customer;
		$strip_postcode_spaces = apply_filters( 'wc_kp_remove_postcode_spaces', false );

		$recipient = array(
			'given_name'  => $customer_data->get_shipping_first_name() ?? '',
			'family_name' => $customer_data->get_shipping_last_name() ?? '',
			'email'       => $customer_data->get_shipping_email() ?? '',
			'phone'       => $customer_data->get_shipping_phone() ?? '',
		);

		$address = array(
			'street_address' => $customer_data->get_shipping_address_1() ?? '',
			'postal_code'    => $strip_postcode_spaces ? $customer_data->get_shipping_postcode() ?? '' : str_replace( ' ', '', $customer_data->get_shipping_postcode() ?? '' ),
			'city'           => $customer_data->get_shipping_city() ?? '',
			'country'        => $customer_data->get_shipping_country() ?? '',
		);

		return array(
			'shipping_reference' => $shipping_line ? $shipping_line->get_sku() : '',
			'recipient'          => $recipient,
			'address'            => $address,
		);
	}

	/**
	 * Returns a formatted Klarna order line object.
	 *
	 * @param  KrokedilKlarnaPaymentsDeps\Krokedil\WooCommerce\OrderLineData $order_line The order line data.
	 * @return array
	 */
	public function get_klarna_order_line_object( $order_line ) {
		$type = 'physical';

		switch ( $order_line->get_type() ) {
			case 'shipping':
				$type = 'shipping_fee';
				break;
			case 'fee':
				$type = 'surcharge';
				break;
			case 'gift_card':
				$type = 'gift_card';
				break;
			case 'discount':
				$type = 'discount';
				break;
		}

		$klarna_item = array(
			'image_url'             => $this->maybe_allow_order_line_url( $order_line->get_image_url() ),
			'merchant_data'         => apply_filters( $order_line->get_filter_name( 'merchant_data' ), array(), $order_line ),
			'name'                  => $order_line->get_name(),
			'product_identifiers'   => apply_filters( $order_line->get_filter_name( 'product_identifiers' ), array(), $order_line ),
			'product_url'           => $order_line->get_product_url(),
			'quantity'              => $order_line->get_quantity(),
			'quantity_unit'         => apply_filters( $order_line->get_filter_name( 'quantity_unit' ), 'pcs', $order_line ),
			'reference'             => $order_line->get_sku(),
			'tax_rate'              => (int) round( (float) ( $this->separate_sales_tax ? 0 : $order_line->get_tax_rate() ), 0 ),
			'total_amount'          => (int) round( (float) ( $this->separate_sales_tax ? $order_line->get_total_amount() : $order_line->get_total_amount() + $order_line->get_total_tax_amount() ), 0 ),
			'total_discount_amount' => (int) round( (float) ( $this->separate_sales_tax ? $order_line->get_total_discount_amount() : $order_line->get_total_discount_amount() + $order_line->get_total_discount_tax_amount() ), 0 ),
			'total_tax_amount'      => (int) round( (float) ( $this->separate_sales_tax ? 0 : $order_line->get_total_tax_amount() ), 0 ),
			'type'                  => $type,
			'unit_price'            => (int) round( (float) ( $this->separate_sales_tax ? $order_line->get_subtotal_unit_price() : $order_line->get_subtotal_unit_price() + $order_line->get_subtotal_unit_tax_amount() ), 0 ),
			'subscription'          => apply_filters( $order_line->get_filter_name( 'subscription' ), array(), $order_line ),
		);

		if ( isset( $order_line->product ) ) {
			$product = $order_line->product;
			if ( class_exists( 'WC_Subscriptions_Product' ) && WC_Subscriptions_Product::is_subscription( $product ) ) {
				$klarna_item['subscription'] = array(
					'name'           => $klarna_item['name'],
					'interval'       => strtoupper( WC_Subscriptions_Product::get_period( $product ) ),
					'interval_count' => absint( WC_Subscriptions_Product::get_interval( $product ) ),
				);
			}
		}

		return array_filter( $klarna_item, 'KP_Order_Data::remove_null' );
	}

	/**
	 * Returns a formatted Klarna customer object.
	 *
	 * @param  string|null $customer_type The customer type to use for generating the data. If empty it will use the class property instead.
	 * @return array
	 */
	public function get_klarna_customer_object( $customer_type = null ) {
		if ( empty( $customer_type ) ) {
			$customer_type = $this->customer_type;
		}

		$strip_postcode_spaces = apply_filters( 'wc_kp_remove_postcode_spaces', false );
		$customer_data         = $this->order_data->customer;

		$billing = array(
			'given_name'      => $customer_data->get_billing_first_name() ?? '',
			'family_name'     => $customer_data->get_billing_last_name() ?? '',
			'email'           => $customer_data->get_billing_email() ?? '',
			'phone'           => $customer_data->get_billing_phone() ?? '',
			'street_address'  => $customer_data->get_billing_address_1() ?? '',
			'street_address2' => $customer_data->get_billing_address_2() ?? '',
			'postal_code'     => $strip_postcode_spaces ? ( $customer_data->get_billing_postcode() ?? '' ) : str_replace( ' ', '', $customer_data->get_billing_postcode() ?? '' ),
			'city'            => $customer_data->get_billing_city() ?? '',
			'region'          => $customer_data->get_billing_state() ?? '',
			'country'         => empty( $customer_data->get_billing_country() ) ? ( kp_get_klarna_country() ?? '' ) : $customer_data->get_billing_country(),
		);

		$shipping = array(
			'given_name'      => $customer_data->get_shipping_first_name() ?? '',
			'family_name'     => $customer_data->get_shipping_last_name() ?? '',
			'email'           => $customer_data->get_shipping_email() ?? '',
			'phone'           => $customer_data->get_shipping_phone() ?? '',
			'street_address'  => $customer_data->get_shipping_address_1() ?? '',
			'street_address2' => $customer_data->get_shipping_address_2() ?? '',
			'postal_code'     => $strip_postcode_spaces ? ( $customer_data->get_shipping_postcode() ?? '' ) : str_replace( ' ', '', $customer_data->get_shipping_postcode() ?? '' ),
			'city'            => $customer_data->get_shipping_city() ?? '',
			'region'          => $customer_data->get_shipping_state() ?? '',
			'country'         => $customer_data->get_shipping_country() ?? '',
		);

		if ( 'b2b' === $customer_type ) {
			$billing['organization_name']  = $customer_data->get_billing_company() ?? '';
			$shipping['organization_name'] = $customer_data->get_shipping_company() ?? '';
		}

		foreach ( $shipping as $key => $value ) {
			if ( ! empty( $value ) ) {
				continue;
			}

			$shipping[ $key ] = $billing[ $key ];
		}

		$customer = array(
			'billing'  => $billing,
			'shipping' => $shipping,
		);

		return $customer;
	}

	/**
	 * Filters array values to remove all null values.
	 *
	 * @param mixed $value The line value to filter.
	 * @return bool
	 */
	public static function remove_null( $value ) {
		return is_array( $value ) ? ! empty( $value ) : null !== $value; // If empty array, or if value is null return true to remove value.
	}

	/**
	 * Maybe filters out product url for the order line before we send them to Klarna based on settings.
	 *
	 * @param string $url The URL to the product or product image.
	 * @return string|null
	 */
	public function maybe_allow_order_line_url( $url ) {
		$settings = get_option( 'woocommerce_klarna_payments_settings', array() );

		if ( 'yes' !== ( $settings['send_product_urls'] ?? 'no' ) ) {
			$url = null;
		}

		return $url;
	}
}
