<?php
/**
 * Handles syncing of orders.
 *
 * @package flo-sync
 */

namespace NMFS\Sync\Items\Orders;

use NMFS\Shared;
use NMFS\Sync\Items\Orders\Orders as Order_Functions;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts\WP_Repository;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Models\Hub_API_Properties;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Sync\Abstract_Syncer;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Helpers\Time_Helpers;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Modules\Loggers\Log_Level_Enum;
use WC_Abstract_Order;
use WC_Order;

defined( 'ABSPATH' ) || exit;

/**
 * Class Sync
 */
class Sync_Orders extends Abstract_Syncer {

	/**
	 * Number of items to get in each API request.
	 *
	 * @var int
	 */
	protected int $page_size = 10;

	/**
	 * Construct class.
	 *
	 * @param array  $backoffice_accounts Array of Hub accounts to sync stock for.
	 * @param string $type                Type of syncer. Can be 'cron' or 'ajax'.
	 */
	public function __construct( array $backoffice_accounts = array(), string $type = 'cron' ) {
		if ( empty( $backoffice_accounts ) ) {
			$backoffice_accounts = WP_Repository::get_all_current_accounts();
		}
		parent::__construct( $backoffice_accounts, $type );
	}

	/**
	 * Sync orders.
	 *
	 * @param array|null $args Query parameters.
	 * @param bool       $is_first_sync If this sync is the first page of the first Hub account.
	 *
	 * @return bool
	 */
	public function sync( ?array $args = null, bool $is_first_sync = false ): bool {
		$current_backoffice_account = $this->get_current_hub_account();

		if ( ! $current_backoffice_account ) {
			return false;
		}

		$orders = $current_backoffice_account->api->get_orders( $args );

		if (
			! isset( $orders['Total'] ) ||
			! isset( $orders['Page'] ) ||
			! isset( $orders['list'] )
		) {
			return false;
		}

		$this->update_status( 'page', $orders['Page'] );
		$this->update_status( 'total', $orders['Total'] );

		if ( is_array( $orders['list'] ) ) {
			foreach ( $orders['list'] as $order ) {
				if ( ! empty( $order['Order'] ) ) {
					$action = $this->process_pulled_order( $order['Order'] );

					if ( $action ) {
						$this->increase_status( $action );
					}
				}
			}
		}

		return true;
	}

	/**
	 * Process pulled order.
	 *
	 * @param array $mf_order Order data.
	 *
	 * @return string
	 */
	private function process_pulled_order( array $mf_order ): string {
		$return = 'ignore';
		$mf_id  = $mf_order['Id'];

		$wc_orders = wc_get_orders(
			array(
				'limit'      => 1,
				'meta_key'   => 'mf_id',
				'meta_value' => $mf_id,
			)
		);

		if (
			! empty( $wc_orders ) &&
			$mf_order[ Hub_API_Properties::ORDER_STATE ] &&
			$mf_order[ Hub_API_Properties::ORDER_PAYMENT_STATUS ]
		) {
			/**
			 * Can be both normal order or refund.
			 *
			 * @var WC_Abstract_Order $wc_order
			 */
			$wc_order = array_shift( $wc_orders );

			Shared::instance()->logger->log(
				'sync',
				'Found WC order ' . $wc_order->get_id() . ' for Hub order ' . $mf_id,
				null,
				Log_Level_Enum::DEBUG
			);

			if ( ! $wc_order instanceof WC_Order ) {
				Shared::instance()->logger->log(
					'sync',
					'Order is not WC_Order',
					array(
						'mf_id'      => $mf_id,
						'order_id'   => $wc_order->get_id(),
						'order_type' => get_class( $wc_order ),
					),
					Log_Level_Enum::DEBUG
				);

				return 'ignore';
			}

			$order_instance = Order_Model_Factory::get_order_model( $wc_order );
			$updated        = $order_instance->update_order( $mf_order[ Hub_API_Properties::ORDER_PAYMENT_STATUS ] );

			if ( $updated ) {
				$return = 'update';
			}
		} elseif ( $this->is_cron() ) {
			$this->log_sync_error( 'cron', Log_Level_Enum::ERROR, (string) $mf_id );
		} elseif ( $this->is_ajax() ) {
			$this->log_sync_error( 'ajax', Log_Level_Enum::DEBUG, (string) $mf_id );
		}

		return $return;
	}

	/**
	 * Log sync error.
	 *
	 * @param string         $context Context of the sync.
	 * @param Log_Level_Enum $level Log level.
	 * @param string         $mf_id Hub order ID.
	 */
	private function log_sync_error( string $context, Log_Level_Enum $level, string $mf_id ): void {
		Shared::instance()->logger->log(
			'sync',
			sprintf( 'WC Order not found during %s sync', $context ),
			array( 'mf_id' => $mf_id ),
			$level
		);
	}

	/**
	 * Disables order save action in order sync loop.
	 *
	 * @param array|null $args API params passed by reference.
	 */
	public function before_sync_loop( ?array &$args ): void {
		Order_Functions::set_hook_save_order( false );

		if ( $this->is_ajax() && ! isset( $args['modifiedSince'] ) ) {
			$args['modifiedSince'] = '1900-01-01T00:00:00';
		}
	}

	/**
	 * Checks if order sync is enabled.
	 *
	 * @return bool
	 */
	public function is_enabled(): bool {
		if ( $this->is_cron() ) {
			if ( ! $this->get_last_synced_wp_option() ) {
				// This is the first time we're syncing orders. Any old orders shouldn't be synced.
				$this->set_last_synced_wp_option( Time_Helpers::get_current_api_time(), 'cron' );

				Shared::instance()->logger->log(
					'sync',
					'mf_orders_sync_last_updated option not found, setting it to current time and skipping sync',
				);

				return false;
			}
		}
		return true;
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_display_name(): string {
		return __( 'Orders', 'flo-sync' );
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_last_synced_wp_option_name(): string {
		return 'mf_orders_sync_last_updated';
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_last_synced_api_parameter(): ?string {
		return 'modifiedSince';
	}
}
