<?php
/**
 * Syncs article stock from API.
 *
 * @package flo-sync
 */

namespace NMFS\Sync\Items\Articles\Stock;

use NMFS\Shared;
use NMFS\Sync\Items\Articles\Articles;
use NMFS\Sync\Items\Articles\Stock\Models\Stock_Data_Collection_Model;
use NMFS\Sync\Items\Articles\Sync_Articles as Article_Sync;
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\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\Modules\Loggers\Log_Level_Enum;
use WP_Error;

defined( 'ABSPATH' ) || exit;

/**
 * Sync class for Stock.
 */
class Sync_Stock extends Abstract_Syncer {

	private const TRANSIENT_SYNCED_ARTICLES = 'nmf_synced_articles';

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

	/**
	 * Construct class.
	 *
	 * @param array  $backoffice_accounts Array of backoffice 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 );
	}

	/**
	 * Pull articles from API, to sync stock.
	 *
	 * @param array|null $args API request arguments.
	 * @param bool       $is_first_sync If this sync is the first page of the first Hub account.
	 */
	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;
		}

		if ( $is_first_sync ) {
			set_transient( self::TRANSIENT_SYNCED_ARTICLES, array() );
		}

		$article_sync = new Article_Sync( array(), $this->get_type() );
		$article_sync->set_current_hub_account( $current_backoffice_account );

		$args['custom_callback'] = array( $this, 'process_pulled_article' );

		add_filter(
			'northmill_online_shared_get_request_response_silent_404_' . Shared::instance()->package_info->get_service_name_underscored(),
			array( $this, 'silent_404' )
		);

		$synced = $article_sync->sync( $args );

		if ( ! $synced ) {
			return false;
		}

		$current_status      = $this->get_status();
		$article_sync_status = $article_sync->get_status();

		$status = array();
		foreach ( array_keys( $current_status + $article_sync_status ) as $key ) {
			if (
				'total' === $key ||
				'page' === $key
			) {
				$status[ $key ] = $current_status[ $key ] ?? $article_sync_status[ $key ] ?? 0;
			}
			$status[ $key ] =
				( $current_status[ $key ] ?? 0 ) +
				( $article_sync_status[ $key ] ?? 0 );
		}
		$this->set_status( $status );

		remove_filter(
			'northmill_online_shared_get_request_response_silent_404_' . Shared::instance()->package_info->get_service_name_underscored(),
			array( $this, 'silent_404' )
		);

		return true;
	}

	/**
	 * Update stock data for article being synced.
	 *
	 * @param array $article Retrieved article to update stock data for.
	 *
	 * @return string|null Action, or null on failure.
	 */
	public function process_pulled_article( array $article ): ?string {
		$article_sku = $article[ Hub_API_Properties::ARTICLE_NUMBER ] ?? null;

		if ( empty( $article_sku ) ) {
			Shared::instance()->logger->log(
				'sync',
				'SKU not set for article during stock sync',
				array(
					'article' => $article,
				),
				Log_Level_Enum::ERROR
			);
			return null;
		}

		$synced_articles = get_transient( self::TRANSIENT_SYNCED_ARTICLES );

		if ( isset( $synced_articles[ $article_sku ] ) ) {
			return null; // Article already synced.
		}

		$synced_articles[ $article_sku ] = true;
		set_transient( self::TRANSIENT_SYNCED_ARTICLES, $synced_articles );

		if ( ! wc_get_product_id_by_sku( $article_sku ) ) {
			return null; // Article isn't found in WC.
		}

		/**
		 * We don't check the article type or whether it is enabled here since we're only updating stock.
		 * Articles of the incorrect type, or that are disabled, shouldn't exist in WC anyway.
		 */

		$stock_data_collection = $this->get_article_stock_data_collection( $article_sku, $article );

		if ( is_wp_error( $stock_data_collection ) ) {
			Shared::instance()->logger->log(
				'sync',
				'Failed to get article from main account when syncing stock',
				array(
					'sku' => $article_sku,
				),
				Log_Level_Enum::ERROR
			);

			return null;
		}

		$status = Stock::update_article_stock( $article_sku, $stock_data_collection );

		if ( is_wp_error( $status ) ) {
			Shared::instance()->logger->log(
				'sync',
				'Failed to update stock for article',
				array(
					'sku'   => $article_sku,
					'error' => $status->get_error_message(),
				),
				Log_Level_Enum::ERROR
			);
		}

		return is_int( $status ) ? 'update' : null;
	}

	/**
	 * Get stock data for article.
	 *
	 * @param string $article_sku Article SKU.
	 * @param array  $article     Article data from Articles::transform_article_to_product().
	 *
	 * @return Stock_Data_Collection_Model|WP_Error Stock data if found. WP_Error if data not found in the main account.
	 */
	private function get_article_stock_data_collection( string $article_sku, array $article ): Stock_Data_Collection_Model|WP_Error {

		$stock_data_collection = new Stock_Data_Collection_Model();

		$stock_data_collection->add_stock_data(
			$this->current_backoffice_account->wp_id,
			$article[ Hub_API_Properties::ARTICLE_QUANTITY ] ?? 0,
			true
		);

		if ( $this->current_backoffice_account->is_main() ) {
			$stock_data_collection->is_stock_kept = $article[ Hub_API_Properties::ARTICLE_IS_STOCK_ITEM ] ?? false;
		}

		if ( Hub_Account_Setup::is_multiple_stocks_enabled() ) {

			$stock_data_collection = Stock::get_stock_data_from_hub_accounts(
				$article_sku,
				$stock_data_collection,
			);

			if ( is_wp_error( $stock_data_collection ) ) {
				// Failed to get stock data from the main Hub account.
				return $stock_data_collection;
			}
		}
		return $stock_data_collection;
	}

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

	/**
	 * Used by filter 'northmill_online_shared_get_request_response_silent_404_$snu'.
	 *
	 * @return true
	 */
	public function silent_404(): bool {
		return true;
	}

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

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

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