<?php
/**
 * Handles article availability.
 *
 * @package flo-sync
 */

namespace NMFS\Sync\Items\Article_Availabilities;

use NMFS\Admin\Settings_Fields;
use NMFS\Shared;
use NMFS\Sync\Items\Groups\Groups;

/**
 * Class Article_Availabilities
 */
class Article_Availabilities {
	public const META_KEY_MODEL   = '_nmf_availability_model';
	public const META_KEY_HUB_ID  = '_nmf_availability_hub_id';
	public const META_KEY_ENABLED = '_nmf_availability_enabled';

	/**
	 * Check if article availability functionality is enabled.
	 *
	 * @return bool
	 */
	public static function is_enabled(): bool {
		return Shared::instance()->admin->settings->is_setting_enabled( Settings_Fields::SYNC_ARTICLE_AVAILABILITIES );
	}

	/**
	 * Get availability CPT by meta _nmf_availability_hub_id.
	 *
	 * @param string $hub_id The hub ID.
	 *
	 * @return int|null The availability CPT ID.
	 */
	public static function get_availability_by_hub_id( string $hub_id ): ?int {
		$availability = get_posts(
			array(
				'post_type'  => Setup::CPT_KEY,
				'meta_key'   => self::META_KEY_HUB_ID,
				'meta_value' => $hub_id,
			)
		);

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

		return $availability[0]->ID;
	}

	/**
	 * Get availabilities.
	 *
	 * Retrieves the availabilities from the database.
	 *
	 * @param array $additional_args Additional arguments to be merged with the default arguments.
	 *
	 * @return Models\Article_Availability_Model[] The availabilities.
	 */
	private static function get_availabilities( array $additional_args = array() ): array {
		$args = array(
			'post_type'      => Setup::CPT_KEY,
			'posts_per_page' => -1,
			'post_status'    => 'any',
		);

		$args = array_merge( $args, $additional_args );

		$posts = get_posts( $args );

		$availabilities = array();
		foreach ( $posts as $post ) {
			$availability = Factory_DB::create_availability_from_db( $post );

			if ( ! empty( $availability ) ) {
				$availabilities[ $availability->hub_id ] = $availability;
			}
		}

		return $availabilities;
	}

	/**
	 * Get all availabilities.
	 *
	 * @return Models\Article_Availability_Model[] The availabilities.
	 */
	public static function get_all_availabilities(): array {
		static $availabilities;

		if ( ! empty( $availabilities ) ) {
			return $availabilities;
		}

		$availabilities = self::get_availabilities();

		return $availabilities;
	}

	/**
	 * Get all availabilities.
	 *
	 * @return Models\Article_Availability_Model[] The availabilities.
	 */
	public static function get_enabled_availabilities(): array {
		static $enabled_availabilities;

		if ( ! empty( $enabled_availabilities ) ) {
			return $enabled_availabilities;
		}

		$enabled_availabilities = self::get_availabilities(
			array(
				'meta_key'    => self::META_KEY_ENABLED,
				'meta_value'  => '1',
				'post_status' => 'publish',
			)
		);

		return $enabled_availabilities;
	}

	/**
	 * Check if an article is currently available.
	 *
	 * If the article is not in any availability, it is considered available.
	 *
	 * If the article is in one or more availabilities by SKU we check these. If we are within the availability time for any, the article is considered available.
	 * Otherwise, the article is considered unavailable.
	 * In this case, group availabilities are NOT checked.
	 *
	 * Otherwise, if the article is in any availability by group we check these in the same way as above.
	 *
	 * @param int $id Article WP id.
	 *
	 * @return bool True if the article is available, false otherwise.
	 */
	public static function is_article_available( int $id ): bool {
		$availabilities = self::get_enabled_availabilities();

		if ( empty( $availabilities ) ) {
			return true;
		}

		$product = wc_get_product( $id );
		$sku     = $product->get_sku();

		$is_available_by_sku = self::is_available_by_type(
			Models\Items\Item_Type_Enum::Article,
			$sku,
			$availabilities
		);

		if ( Availability_Check_Enum::Available === $is_available_by_sku ) {
			return true;
		} elseif ( Availability_Check_Enum::Not_Available === $is_available_by_sku ) {
			return false;
		}

		$category_ids = $product->get_category_ids();

		if ( empty( $category_ids ) ) {
			return true;
		}

		$is_available = true;
		foreach ( $category_ids as $category_id ) {
			$category_hub_id          = Groups::get_hub_id_by_post_id( $category_id );
			$is_available_by_category = self::is_available_by_type(
				Models\Items\Item_Type_Enum::ArticleGroup,
				$category_hub_id,
				$availabilities
			);

			if ( Availability_Check_Enum::Available === $is_available_by_category ) {
				return true;
			} elseif ( Availability_Check_Enum::Not_Available === $is_available_by_category ) {
				$is_available = false;
			}
		}

		return $is_available;
	}

	/**
	 * Check if an item is available based on its type and ID.
	 *
	 * @param Models\Items\Item_Type_Enum         $type           The item type.
	 * @param string                              $id             Article SKU or category Hub ID.
	 * @param Models\Article_Availability_Model[] $availabilities The availabilities to check.
	 *
	 * @return Availability_Check_Enum The availability status.
	 */
	private static function is_available_by_type( Models\Items\Item_Type_Enum $type, string $id, array $availabilities ): Availability_Check_Enum {
		$matching_availability = false;
		foreach ( $availabilities as $availability ) {

			$matching_availability_items = array_filter(
				$availability->items,
				fn( $item ) =>
					$type === $item->type &&
					$item->id === $id
			);

			if ( empty( $matching_availability_items ) ) {
				continue;
			}

			$matching_availability = true;

			foreach ( $availability->schedules as $schedule ) {
				if ( $schedule->is_available_now() ) {
					return Availability_Check_Enum::Available;
				}
			}
		}

		return $matching_availability ?
			Availability_Check_Enum::Not_Available :
			Availability_Check_Enum::No_Match;
	}
}
