<?php
/**
 * Syncs groups to WooCommerce as product categories
 *
 * @package flo-sync
 *
 * @phpcs:disable Generic.Commenting.DocComment.MissingShort
 */

namespace NMFS\Sync\Items\Groups;

use NMFS\Shared;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Abstracts\Hub_API\Sync\Abstract_Syncer_And_Cleaner;
use Northmill_Online_Shared_Sync\Northmill_Online_Shared\Modules\Loggers\Log_Level_Enum;

defined( 'ABSPATH' ) || exit;

/**
 * Class Sync
 */
class Sync_Groups extends Abstract_Syncer_And_Cleaner {

	public const META_KEY_GROUP_ID      = 'mf_group_id';
	public const META_KEY_LAST_UPDATED  = 'mf_last_updated';
	public const TRANSIENT_LAST_CLEANED = 'nmf_groups_last_clean';

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


	/**
	 * Check if sync is enabled.
	 *
	 * @return bool
	 */
	public function is_enabled(): bool {
		return Groups::is_enabled();
	}

	/**
	 * Syncs groups to WooCommerce as product categories
	 *
	 * @param array|null $args Arguments to pass to API, or null if no 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;
		}
		$groups = $current_backoffice_account->api->get_groups( $args );

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

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

		if ( is_array( $groups['list'] ) ) {
			$this->process_pulled_groups( $groups['list'] );
		}

		return true;
	}

	/**
	 * Process groups pulled from API
	 *
	 * @param array $api_list List of groups.
	 *
	 * @return void
	 */
	public function process_pulled_groups( array $api_list ): void {
		$api_list = $this->order_by_hierarchy( $api_list );

		if ( empty( $api_list ) ) {
			return;
		}

		foreach ( $api_list as $group ) {
			if ( empty( $group['Group'] ) || ! is_array( $group['Group'] ) ) {
				continue;
			}

			$action = $this->process_pulled_group( $group['Group'] );

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

	/**
	 * Process a single group pulled from API
	 *
	 * @param array $group Group.
	 *
	 * @return string
	 */
	public function process_pulled_group( array $group ): string {

		$term_id = null;
		$args    = array(
			'parent' => 0,
			'name'   => $group['Name'],
		);

		$current_terms = get_terms(
			array(
				'taxonomy'   => 'product_cat',
				'hide_empty' => false,
				'meta_query' => array(
					array(
						'key'   => self::META_KEY_GROUP_ID,
						'value' => $group['Id'],
					),
				),
			)
		);

		if ( ! empty( $current_terms ) ) {
			$term_id = $current_terms[0]->term_id;
		}

		if ( $group['ParentId'] ) {
			$parent_terms = get_terms(
				array(
					'taxonomy'   => 'product_cat',
					'hide_empty' => false,
					'meta_query' => array(
						array(
							'key'   => self::META_KEY_GROUP_ID,
							'value' => $group['ParentId'],
						),
					),
				)
			);

			if ( ! empty( $parent_terms ) ) {
				$args['parent'] = $parent_terms[0]->term_id;
			}
		}

		if ( is_null( $term_id ) ) {
			$term_parent = $args['parent'] ?? null;
			$term_id     = term_exists( $group['Name'], 'product_cat', $term_parent );
			if ( ! is_null( $term_id ) ) {
				$term_id = $term_id['term_id'];
				$return  = 'update';
			} else {
				$insert_result = wp_insert_term( $group['Name'], 'product_cat', $args );
				if ( is_wp_error( $insert_result ) ) {
					Shared::instance()->logger->log(
						'sync',
						'Error creating WC group',
						array(
							'error' => $insert_result->get_error_message(),
							'group' => $group,
						),
						Log_Level_Enum::ERROR
					);

					return '';
				}

				$term_id = $insert_result['term_id'];
				$return  = 'add';
			}
		} else {
			wp_update_term( $term_id, 'product_cat', $args );
			$return = 'update';
		}

		update_term_meta( $term_id, 'order', $group['DisplayOrder'] );
		update_term_meta( $term_id, self::META_KEY_GROUP_ID, $group['Id'] );
		update_term_meta( $term_id, self::META_KEY_LAST_UPDATED, time() );

		return $return;
	}

	/**
	 * Clean old groups.
	 *
	 * @param bool $getting_all_data If syncer is getting all data.
	 *
	 * @return void
	 */
	public function clean( bool $getting_all_data ): void {
		if (
			! $getting_all_data &&
			get_transient( self::TRANSIENT_LAST_CLEANED )
		) {
			// Already cleaned.
			Shared::instance()->logger->log(
				'sync',
				'Skipped clean, last clean was less than 1 minute ago.',
				null,
				Log_Level_Enum::DEBUG
			);

			return;
		}

		$outdated_terms = get_terms(
			array(
				'taxonomy'   => 'product_cat',
				'hide_empty' => false,
				'fields'     => 'ids',
				'meta_query' => array(
					'relation' => 'AND',
					array(
						'key'     => self::META_KEY_GROUP_ID,
						'compare' => 'EXISTS',
					),
					array(
						'key'     => self::META_KEY_LAST_UPDATED,
						'value'   => time() - 5 * MINUTE_IN_SECONDS,
						'compare' => '<',
					),
				),
			)
		);

		if ( ! empty( $outdated_terms ) ) {
			foreach ( $outdated_terms as $term_id ) {
				$mf_group_id = get_term_meta( $term_id, self::META_KEY_GROUP_ID, true );
				wp_delete_term( $term_id, 'product_cat' );

				Shared::instance()->logger->log(
					'sync',
					'Deleted product term',
					array(
						'term_id'     => $term_id,
						'mf_group_id' => $mf_group_id,
					),
					Log_Level_Enum::DEBUG
				);
			}
		}

		Shared::instance()->logger->log(
			'sync',
			'Done cleaning groups',
			null,
			Log_Level_Enum::DEBUG
		);

		set_transient( self::TRANSIENT_LAST_CLEANED, time(), MINUTE_IN_SECONDS );
	}

	/**
	 * Orders a group list with parents first
	 *
	 * @param array $api_list List of groups.
	 *
	 * @return array Ordered list
	 */
	public function order_by_hierarchy( array $api_list ): array {
		$ordered_list = $this->do_order_by_hierarchy( $api_list );

		$ordered_list_ids = array_map(
			function ( $i ) {
				return $i['Group']['Id'];
			},
			$ordered_list
		);

		foreach ( $api_list as $item ) {
			if ( in_array( $item['Group']['Id'], $ordered_list_ids, true ) ) {
				continue;
			}

			$ordered_list[] = $item;
		}

		return $ordered_list;
	}

	/**
	 * Orders a group list with parents first
	 *
	 * @param array       $api_list  List of groups.
	 * @param null|string $parent_id Parent id.
	 *
	 * @return array Ordered list
	 */
	private function do_order_by_hierarchy( array $api_list, ?string $parent_id = null ): array {
		$matching_parent     = array_filter(
			$api_list,
			function ( $item ) use ( $parent_id ) {
				return $item['Group']['ParentId'] === $parent_id;
			}
		);
		$not_matching_parent = array_filter(
			$api_list,
			function ( $item ) use ( $parent_id ) {
				return $item['Group']['ParentId'] !== $parent_id;
			}
		);

		if ( empty( $not_matching_parent ) ) {
			return $matching_parent;
		}

		if ( empty( $matching_parent ) ) {
			return array();
		}

		$ordered_children = array();
		foreach ( $matching_parent as $matching_parent_item ) {
			$ordered_children = array_merge(
				$ordered_children,
				$this->do_order_by_hierarchy( $not_matching_parent, $matching_parent_item['Group']['Id'] )
			);
		}

		return array_merge(
			$matching_parent,
			$ordered_children
		);
	}

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

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

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