<?php
/**
 * Flo Order Model
 *
 * @package flo-sync
 *
 * @phpcs:disable Generic.Commenting.DocComment.MissingShort
 */

namespace NMFS\Sync\Items\Orders\Models;

use Automattic\WooCommerce\Utilities\NumberUtil;
use NMFS\Admin\Settings_Fields;
use NMFS\Shared;
use NMFS\Sync\Items\Articles\Stock\Stock;
use NMFS\Sync\Items\Orders\Orders;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Factories\Receipt_Factory;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Models\Hub_API_Properties;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Data\Payment_Methods;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Modules\Loggers\Log_Level_Enum;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Modules\Sync\Customers\Customer_Sync;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Modules\Sync\Orders\Models\Order_Base_Model;
use WC_Order_Item_Product;
use WC_Order_Item_Shipping;
use WC_Product;

defined( 'ABSPATH' ) || exit;

/**
 * Order model class.
 */
class Order_Model extends Order_Base_Model {

	/**
	 * Update order from API if the payment status has changed.
	 *
	 * @param string $mf_pay_status Payment status from API.
	 *
	 * @return bool True if the order was updated, false if not.
	 */
	public function update_order( string $mf_pay_status ): bool {
		if ( ! isset( $this->wc_order ) ) {
			return false;
		}

		$order_id = $this->wc_order->get_id();

		$current_nmf_order_status = $this->get_current_nmf_order_status();

		if ( $current_nmf_order_status !== $mf_pay_status ) {

			if ( 'FullyPaid' === $mf_pay_status && empty( $this->wc_order->get_transaction_id() ) ) {
				update_post_meta( $order_id, '_payment_method', 'PayedFromCashier' );
				update_post_meta( $order_id, '_payment_method_title', __( 'Payed From Cashier', 'flo-sync' ) );
				update_post_meta( $order_id, '_transaction_id', time() );
				update_post_meta( $order_id, Order_Base_Model::META_KEY_FULLY_PAID, true );
				$this->wc_order->payment_complete();
			}

			$mf_order = $this->order_to_nmflo_order();
			$new_hash = Orders::generate_order_hash( $mf_order );
			update_post_meta( $order_id, Order_Base_Model::META_KEY_HASH, $new_hash );

			return true;
		} else {
			Shared::instance()->logger->log(
				'sync',
				'Failed to update order in WC. Payment status not changed.',
				array(
					'order_id'       => $order_id,
					'new_status'     => $mf_pay_status,
					'current_status' => $current_nmf_order_status,
				),
				Log_Level_Enum::NOTICE
			);

			return false;
		}
	}

	/**
	 * Get NFM order status for current order
	 *
	 * @return string
	 */
	private function get_current_nmf_order_status(): string {
		$current_status = $this->wc_order->get_status();
		$new_mf_status  = '';

		switch ( $current_status ) {
			case 'cancelled':
			case 'refunded':
			case 'failed':
				$new_mf_status = 'NotSet';
				break;
			case 'pending':
			case 'on-hold':
				$new_mf_status = 'NotPaid';
				break;
			case 'processing':
			case 'completed':
				$new_mf_status = 'FullyPaid';
				break;
			default:
				break;
		}
		return $new_mf_status;
	}

	/**
	 * Transforms order to Northmill Flo order
	 *
	 * @return array|false Array Hub order, if not changed by filter.
	 */
	public function order_to_nmflo_order(): array|false {
		$order_no = $this->wc_order->get_order_number();

		$mf_order = array(
			Hub_API_Properties::ORDER_EXTERNAL_REFERENCE => $this->get_external_reference( $order_no, 'MFS-' ),
			Hub_API_Properties::ORDER_UPDATE_ON_EXISTING => true,
			Hub_API_Properties::ORDER_OUR_REFERENCE      => $order_no,
			Hub_API_Properties::ORDER_YOUR_ORDER_NUMBER  => $order_no,
		);

		$customer         = $this->get_customer();
		$billing_address  = $this->get_billing_address();
		$shipping_address = $this->get_shipping_address();
		$order_rows       = $this->get_order_rows();
		$order_notes      = $this->wc_order->get_customer_note();
		$order_type       = $this->get_order_type();
		$hub_order_number = $this->get_hub_order_number();

		// Saving the order type as meta.
		update_post_meta( $this->wc_order->get_id(), Order_Base_Model::META_KEY_ORDER_TYPE, $order_type );

		if ( ! empty( $hub_order_number ) ) {
			$mf_order[ Hub_API_Properties::ORDER_NUMBER ] = $hub_order_number;
		}

		if ( ! empty( $customer ) ) {
			if ( empty( $customer->CustomerNumber ) ) {
				$customer->CustomerNumber = Customer_Sync::get_hub_customer_number( $customer, $this->get_backoffice_account() );
			}
			$mf_order[ Hub_API_Properties::ORDER_CUSTOMER ] = $customer;
		}

		if ( ! empty( $billing_address ) ) {
			$mf_order[ Hub_API_Properties::ORDER_BILLING_ADDRESS ] = $billing_address;
		}

		if ( ! empty( $shipping_address ) ) {
			$mf_order[ Hub_API_Properties::ORDER_SHIPPING_ADDRESS ] = $shipping_address;
		}

		if ( ! empty( $order_notes ) ) {
			$mf_order[ Hub_API_Properties::ORDER_NOTE ] = $order_notes;
		}

		$mf_order[ Hub_API_Properties::ORDER_PAYMENT_OPTION ] = $this->get_hub_order_pay_method();
		$mf_order[ Hub_API_Properties::ORDER_PAYMENT_STATUS ] = $this->get_order_pay_status();

		$mf_order[ Hub_API_Properties::ORDER_PAYMENT_REFERENCE ] = match ( $this->get_pay_method() ) {
			Payment_Methods::NMC_MF_INVOICE => 'Invoice',
			Payment_Methods::NMC_BNPL => 'BNPL:' . $this->wc_order->get_transaction_id(),
			default => $this->get_pay_method() . ': ' . $this->wc_order->get_transaction_id(),
		};

		$mf_order[ Hub_API_Properties::ORDER_PAYMENT_ORIGIN ] = Order_Base_Model::DEFAULT_PAYMENT_ORIGIN;

		$mf_order[ Hub_API_Properties::ORDER_STATE ] = $this->get_order_status_for_hub( $order_type );

		$mf_order[ Hub_API_Properties::ORDER_ROWS ] = $order_rows;

		$mf_order[ Hub_API_Properties::ORDER_ADJUST_STOCK_IF_OPEN_STATE ] = Settings_Fields::ORDER_STOCK_ADJUSTMENT_ORDER_OPEN === Stock::get_stock_adjustment_setting();

		/**
		 * Filter the order before it is sent to Northmill Flo.
		 * Should return an array.
		 *
		 * @param array $mf_order Northmill Flo order.
		 * @param int   $order_id WC order id.
		 *
		 * @used Customize order
		 * @used Express Order
		 * @used SÖS Customize Order
		 * @used Shared
		 */
		$mf_order = apply_filters( 'moreflo_sync_order_to_moreflo_order', $mf_order, $this->wc_order->get_id() );

		return is_array( $mf_order ) ? $mf_order : false;
	}

	/**
	 * Get order rows for this order.
	 *
	 * @return array Array of order rows for API, or null if no order rows are set.
	 */
	private function get_order_rows(): array {

		$order_rows = array();

		/**
		 * Order item products
		 *
		 * @var WC_Order_Item_Product[] $order_items
		 */
		$order_items = $this->wc_order->get_items();

		$receipt_factory = new Receipt_Factory(
			$this->wc_order,
			$this
		);

		foreach ( $order_items as $item_data ) {

			// Uses receipt calculations to avoid duplicate code.
			$receipt_article = $receipt_factory->maybe_create_article(
				$item_data,
				0,
				'',
				false
			);

			if ( is_null( $receipt_article ) ) {
				// Not a valid row.
				continue;
			}

			$product = $item_data->get_product();

			$item = array(
				Hub_API_Properties::ARTICLE_NUMBER   => $receipt_article->get_sku(),
				Hub_API_Properties::ARTICLE_NAME     => $receipt_article->get_name(),
				Hub_API_Properties::ARTICLE_QUANTITY => $receipt_article->get_quantity(),
				Hub_API_Properties::ARTICLE_DISCOUNT => NumberUtil::round(
					$receipt_article->get_total_discount(),
					wc_get_rounding_precision()
				),
			);

			$item[ Hub_API_Properties::ARTICLE_AMOUNT_INC_VAT_EX_DISCOUNT ] = $receipt_article->get_unit_price_without_discounts();
			$item[ Hub_API_Properties::ARTICLE_VAT_RATE ]                   = $receipt_article->get_vat_rate();

			if ( ! empty( $item[ Hub_API_Properties::ARTICLE_NUMBER ] ) && ! empty( $item[ Hub_API_Properties::ARTICLE_QUANTITY ] ) ) {
				$order_rows[] = $item;

				/**
				 * Filter the order rows.
				 *
				 * @param array                 $order_rows Current order rows.
				 * @param WC_Order_Item_Product $item_data Current order item.
				 * @param WC_Product            $product Current product.
				 *
				 * @used Express Order
				 */
				$order_rows = apply_filters( 'moreflo_sync_add_order_row_after', $order_rows, $item_data, $product );
			}
		}

		$shipping_row = $this->get_shipping_order_row();

		if ( ! is_null( $shipping_row ) ) {
			$order_rows[] = $shipping_row;
		}

		return $order_rows;
	}

	/**
	 * Get shipping order row for this order.
	 *
	 * @return array|null
	 */
	private function get_shipping_order_row(): ?array {
		$shipping_article_sku = $this->get_backoffice_account()->get_shipping_settings()->get_article_sku();

		if ( ! empty( $shipping_article_sku ) ) {
			$shipping_tax_rate       = 0;
			$shipping_tax            = floatval( $this->wc_order->get_shipping_tax() );
			$shipping_total          = floatval( $this->wc_order->get_shipping_total() );
			$shipping_total_with_vat = round( $shipping_total + $shipping_tax, wc_get_price_decimals() );

			if ( $shipping_tax > 0 ) {
				/**
				 * Shipping items.
				 *
				 * @var WC_Order_Item_Shipping[] $tax_lines
				 */
				$tax_lines = $this->wc_order->get_items( 'shipping' );
				if ( ! empty( $tax_lines ) ) {
					foreach ( $tax_lines as $tl ) {
						$tax_totals         = $tl->get_taxes()['total'];
						$shipping_tax_total = floatval( array_shift( $tax_totals ) );
						$shipping_tax_rate  = ceil( 100 * ( ( ( $shipping_total + $shipping_tax_total ) / $shipping_total ) - 1 ) );
					}
				}
			}

			if ( $shipping_total_with_vat > 0 ) {
				return array(
					Hub_API_Properties::ARTICLE_NUMBER   => $shipping_article_sku,
					Hub_API_Properties::ARTICLE_NAME     => __( 'Shipping', 'flo-sync' ),
					Hub_API_Properties::ARTICLE_AMOUNT_INC_VAT_EX_DISCOUNT => $shipping_total_with_vat,
					Hub_API_Properties::ARTICLE_VAT_RATE => $shipping_tax_rate,
					Hub_API_Properties::ARTICLE_QUANTITY => 1,
				);
			}
		}

		return null;
	}

	/**
	 * Get the order type for use in API. We currently only support normal orders.
	 *
	 * @return string
	 */
	public function get_order_type(): string {
		return 'NORMAL_ORDER';
	}
}
