<?php
/**
 * Checkout functions related to stock.
 *
 * @package flo-sync
 */

namespace NMFS\Sync\Items\Articles\Stock;

use Exception;
use NMFS\Shared;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts\Envs;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts\Models\Hub_Account_Model;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts\Hub_Account_Setup;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts\WP_Repository;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Modules\Loggers\Log_Level_Enum;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Modules\Sync\Orders\Models\Order_Base_Model;
use WC_Order;
use WC_Order_Refund;
use WC_Product;
use WC_Shipping_Rate;
use WC_Shipping_Zone;
use WC_Shipping_Zones;
use WP_Error;

defined( 'ABSPATH' ) || exit;

/**
 * Class Checkout
 */
class Checkout {
	/**
	 * Checkout constructor.
	 */
	public static function setup(): void {
		add_filter(
			'woocommerce_checkout_posted_data',
			array( __CLASS__, 'filter_posted_data' ),
		);

		add_action(
			'woocommerce_after_checkout_validation',
			array( __CLASS__, 'validate_checkout' ),
			10,
			2
		);

		add_action(
			'woocommerce_checkout_create_order',
			array( __CLASS__, 'add_order_meta' ),
			10,
			2
		);
	}

	/**
	 * Filter posted data. Adding allowed fields in checkout post-data.
	 *
	 * @param array $data Posted data.
	 *
	 * @return array Filtered data.
	 *
	 * @phpcs:disable WordPress.Security.NonceVerification.Missing
	 */
	public static function filter_posted_data( array $data ): array {
		if ( ! Hub_Account_Setup::is_multiple_stocks_enabled() ) {
			return $data;
		}

		$cart_packages = WC()->cart->get_shipping_packages();

		if (
			empty( $cart_packages[0] ) ||
			empty( $_POST['shipping_method'][0] )
		) {
			return $data;
		}

		/**
		 * Shipping method id.
		 */
		$shipping_method_id = wc_clean( wp_unslash( $_POST['shipping_method'][0] ) );

		/**
		 * Hub account id.
		 */
		$replaced_shipping_method_id = str_replace( ':', '_', $shipping_method_id );
		$backoffice_account_id       = $_POST[ 'nmf_backoffice_account_id_' . $replaced_shipping_method_id ] ?? null;
		$backoffice_account_id       = wc_clean( wp_unslash( $backoffice_account_id ) );
		$backoffice_account_id       = is_numeric( $backoffice_account_id ) ? absint( $backoffice_account_id ) : null;

		$cart_package    = $cart_packages[0];
		$cart_zone       = WC_Shipping_Zones::get_zone_matching_package( $cart_package );
		$shipping_method = null;

		$zone_methods = $cart_zone->get_shipping_methods( true );
		foreach ( $zone_methods as $zone_method ) {
			$zone_method_id = $zone_method->id . ':' . $zone_method->instance_id;
			if ( $zone_method_id === $shipping_method_id ) {
				$shipping_method = $zone_method;
				break;
			}
		}

		if ( null === $shipping_method ) {
			return $data;
		}

		$shipping_rate = new WC_Shipping_Rate(
			$shipping_method_id,
			$shipping_method->title,
			$shipping_method->cost ?? 0,
			array(),
			$shipping_method->id,
			$shipping_method->instance_id
		);

		$validated_backoffice_account_id = self::validate_backoffice_account_choice(
			$shipping_rate,
			$cart_zone,
			$backoffice_account_id
		);

		if ( ! empty( $validated_backoffice_account_id ) ) {
			$data[ Order_Base_Model::META_KEY_HUB_ACCOUNT_WP_ID ] = $validated_backoffice_account_id;
		}

		return $data;
	}
	// phpcs:enable

	/**
	 * Extra validation for checkout.
	 *
	 * @param array    $data   Order object to be created.
	 * @param WP_Error $errors Current WP_Error object.
	 *
	 * @return void
	 */
	public static function validate_checkout( array $data, WP_Error $errors ): void {
		if ( ! Hub_Account_Setup::is_multiple_stocks_enabled() ) {
			return;
		}

		if ( empty( $data[ Order_Base_Model::META_KEY_HUB_ACCOUNT_WP_ID ] ) ) {
			Shared::instance()->logger->log(
				'checkout',
				'Could not get Hub account for order',
				array(
					'checkout_data' => $data,
				),
				Log_Level_Enum::ERROR
			);
			$errors->add( 'store', __( 'Please select a valid store, or contact us if this error remains.', 'flo-sync' ) );
		}
	}

	/**
	 * Validate shipping method, and maybe store select.
	 *
	 * @param WC_Shipping_Rate $shipping_method Shipping method.
	 * @param WC_Shipping_Zone $shipping_zone Shipping zone.
	 * @param null|int         $backoffice_account_id Hub account ID.
	 *
	 * @return null|int Hub account ID, or null on error.
	 */
	public static function validate_backoffice_account_choice(
		WC_Shipping_Rate $shipping_method,
		WC_Shipping_Zone $shipping_zone,
		?int $backoffice_account_id
	): ?int {

		$shipping_type = Shipping::get_shipping_method_type_by_rate( $shipping_method, false );

		if ( 'delivery' === $shipping_type ) {
			$backoffice_account = self::get_delivery_backoffice_account_with_stock( $shipping_zone, $shipping_method );

			if ( empty( $backoffice_account ) ) {
				Shared::instance()->logger->log(
					'checkout',
					'Checkout could not find delivery Hub account with stock',
					array(
						'hub_account_id'  => $backoffice_account_id,
						'shipping_method' => $shipping_method,
						'shipping_zone'   => $shipping_zone,
					),
					Log_Level_Enum::ERROR
				);
				return null;
			}
		} elseif ( 'pickup' === $shipping_type ) {
			if ( empty( $backoffice_account_id ) ) {
				return null;
			}
			$backoffice_account = false;

			$allowed_backoffice_accounts = self::get_pickup_backoffice_accounts_with_stock( $shipping_zone, $shipping_method );

			foreach ( $allowed_backoffice_accounts as $allowed_backoffice_account ) {
				if ( $allowed_backoffice_account->wp_id === $backoffice_account_id ) {
					$backoffice_account = $allowed_backoffice_account;
					break;
				}
			}

			if ( ! $backoffice_account ) {
				Shared::instance()->logger->log(
					'checkout',
					'Checkout with invalid pickup Hub account',
					array(
						'hub_account_id' => $backoffice_account_id,
					),
					Log_Level_Enum::ERROR
				);
				return null;
			}
		} else {
			Shared::instance()->logger->log(
				'checkout',
				'Checkout with invalid shipping method',
				array(
					'shipping_type' => $shipping_type,
				),
				Log_Level_Enum::ERROR
			);
			return null;
		}

		return $backoffice_account->wp_id;
	}

	/**
	 * Get a Hub account that has stock for all items in its cart, and is that delivers.
	 *
	 * @param WC_Shipping_Zone $package_zone Shipping zone.
	 * @param WC_Shipping_Rate $shipping_method Shipping method.
	 *
	 * @return Hub_Account_Model|null
	 */
	public static function get_delivery_backoffice_account_with_stock( WC_Shipping_Zone $package_zone, WC_Shipping_Rate $shipping_method ): ?Hub_Account_Model {
		$main_account = WP_Repository::get_current_main_account();
		if (
			Shipping::is_shipping_method_allowed_for_backoffice_account( $main_account, $package_zone, $shipping_method ) &&
			self::is_all_cart_items_available_in_backoffice_account( $main_account )
		) {
			return $main_account;
		}

		$stock_accounts = WP_Repository::get_stock_accounts( Envs::get_current_env() );

		if ( empty( $stock_accounts ) ) {
			return null;
		}

		foreach ( $stock_accounts as $stock_account ) {
			if ( ! Shipping::is_shipping_method_allowed_for_backoffice_account( $stock_account, $package_zone, $shipping_method ) ) {
				continue;
			}

			if ( self::is_all_cart_items_available_in_backoffice_account( $stock_account ) ) {
				return $stock_account;
			}
		}

		return null;
	}

	/**
	 * Get all Hub accounts that have stock for all items in their cart, for order that is to be picked up.
	 *
	 * @param WC_Shipping_Zone $package_zone Shipping zone.
	 * @param WC_Shipping_Rate $shipping_method Shipping method.
	 *
	 * @return Hub_Account_Model[]
	 */
	public static function get_pickup_backoffice_accounts_with_stock( WC_Shipping_Zone $package_zone, WC_Shipping_Rate $shipping_method ): array {
		$accounts = WP_Repository::get_all_current_accounts();

		foreach ( $accounts as $account_key => $account ) {
			if (
				! Shipping::is_shipping_method_allowed_for_backoffice_account( $account, $package_zone, $shipping_method ) ||
				! self::is_all_cart_items_available_in_backoffice_account( $account )
			) {
				unset( $accounts[ $account_key ] );
			}
		}

		return $accounts;
	}

	/**
	 * Check if all items in the cart are available from a specific Hub account.
	 * It DOESN'T check actual stock in API, but the data stored in Woo.
	 *
	 * @param Hub_Account_Model $backoffice_account Account to check.
	 * @param null|WP_Error     $errors Current WP_Error object, or null to skip adding errors.
	 *
	 * @return bool
	 */
	public static function is_all_cart_items_available_in_backoffice_account(
		Hub_Account_Model $backoffice_account,
		?WP_Error $errors = null
	): bool {
		$cart_contents = WC()->cart->get_cart_contents();
		$add_errors    = null !== $errors;

		$error = false;

		foreach ( $cart_contents as $values ) {
			$quantity = $values['quantity'];
			/**
			 * Article data
			 *
			 * @var WC_Product $article_data
			 */
			$article_data = $values['data'];
			$sku          = $article_data->get_sku();
			$title        = $article_data->get_title();

			// If article is not stock managed, only check if it is available in the Hub account.
			if ( ! $article_data->managing_stock() ) {
				if ( ! Stock::is_article_in_hub_account( $article_data, $backoffice_account->wp_id ) ) {
					Shared::instance()->logger->log(
						'checkout',
						'Article does not exist in Hub account.',
						array(
							'sku'     => $sku,
							'account' => $backoffice_account->get_account_basics_for_logging(),
						),
						Log_Level_Enum::DEBUG
					);
					$error = true;
				}
				continue;
			}

			// Get stock quantity for Hub Account stored in Woo.
			$stock_quantity = Stock::get_stock_quantity_from_backoffice_account(
				$article_data,
				$backoffice_account->wp_id,
				null
			);

			if ( is_null( $stock_quantity ) ) {
				if ( $add_errors ) {
					$errors->add(
						'store',
						sprintf(
							// translators: %s is the article title.
							esc_html__(
								'Article %s not found in store. Try selecting another store.',
								'flo-sync'
							),
							'<i>' . $title . '</i>'
						)
					);
				}

				Shared::instance()->logger->log(
					'sync',
					'Article not found in API during stock sync.',
					array(
						'sku'     => $sku,
						'account' => $backoffice_account->get_account_basics_for_logging(),
					),
					Log_Level_Enum::DEBUG
				);

				$error = true;
				continue;
			}

			if ( ! $article_data->backorders_allowed() && $stock_quantity < $quantity ) {
				if ( $add_errors ) {
					$errors->add(
						'store',
						sprintf(
								// translators: %s is the article title.
							esc_html__(
								'Insufficient stock in store for article %s. Try changing the quantity or select another store.',
								'flo-sync'
							),
							'<i>' . $title . '</i>'
						),
					);
				}

				Shared::instance()->logger->log(
					'checkout',
					'Article has insufficient stock.',
					array(
						'sku'     => $sku,
						'account' => $backoffice_account->get_account_basics_for_logging(),
					),
					Log_Level_Enum::DEBUG
				);
				$error = true;
			}
		}

		return ! $error;
	}

	/**
	 * Add the customer-selected Hub account to order meta.
	 *
	 * @param WC_Order|WC_Order_Refund $order Order object.
	 * @param array                    $data  Data for order to be created.
	 *
	 * @return void
	 * @throws Exception When validation fails.
	 */
	public static function add_order_meta( WC_Order|WC_Order_Refund $order, array $data ): void {
		if ( ! Hub_Account_Setup::is_multiple_stocks_enabled() ) {
			return;
		}

		if ( empty( $data[ Order_Base_Model::META_KEY_HUB_ACCOUNT_WP_ID ] ) ) {
			return;
		}

		$order->update_meta_data( Order_Base_Model::META_KEY_HUB_ACCOUNT_WP_ID, $data[ Order_Base_Model::META_KEY_HUB_ACCOUNT_WP_ID ] );
	}
}
