<?php

namespace Objectiv\Plugins\Checkout\Model\Bumps;

use Objectiv\Plugins\Checkout\Interfaces\BumpInterface;
use Objectiv\Plugins\Checkout\Model\MatchedVariationResult;
use WC_Product;
use WC_Product_Variation;

class SpecificProductsBump extends BumpAbstract {
	public function is_displayable(): bool {
		$offer_product = $this->get_offer_product();

		if ( ! $this->can_offer_product_be_added_to_the_cart() ) {
			cfw_debug_log( 'Order Bump Rule Failure: Offer Product cannot be added to the cart: ' . $this->get_id() );
			return false;
		}

		/**
		 * Filters whether to show a bump if the offer product is already in the cart
		 *
		 * @since 8.2.18
		 * @param bool $show_bump_if_offer_product_in_cart Whether to show a bump if the offer product is already in the cart
		 */
		if ( apply_filters( 'cfw_hide_bump_if_offer_product_in_cart', true ) && $this->quantity_of_product_in_cart( $this->offer_product ) ) {
			cfw_debug_log( 'Order Bump Rule Failure: Offer product is in the cart: ' . $this->get_id() );
			return false;
		}

		if ( $this->bump_is_in_cart() ) {
			cfw_debug_log( 'Order Bump Rule Failure: Bump is already in the cart: ' . $this->get_id() );
			return false;
		}

		// If we are variation matching, make sure we have matched variations
		if ( $offer_product->is_type( 'variable' ) && 'yes' === get_post_meta( $this->get_id(), 'cfw_ob_enable_auto_match', true ) && ! $this->get_matched_variation_attributes_from_cart_search_product( $offer_product )->get_id() ) {
			cfw_debug_log( 'Order Bump Rule Failure: Could not find matching variation: ' . $this->get_id() );
			return false;
		}

		// Is it a valid upsell (setup correctly) and are there enough units of the offer product to match the cart product?
		// TODO: Technically this disallows upsell offer products that are backordered. Bug or feature? YOU DECIDE.
		if ( $this->is_valid_upsell() && $offer_product->get_manage_stock() && $this->quantity_of_normal_product_in_cart( array_values( $this->products )[0] ) > $offer_product->get_stock_quantity() ) {
			cfw_debug_log( 'Order Bump Rule Failure: Not a valid upsell: ' . $this->get_id() );
			return false;
		}

		if ( $this->match_offer_product_quantity && $offer_product->get_manage_stock() && $this->quantity_of_normal_product_in_cart( array_values( $this->products )[0] ) > $offer_product->get_stock_quantity() ) {
			cfw_debug_log( 'Order Bump Rule Failure: Cannot match quantity: ' . $this->get_id() );
			return false;
		}

		$matching_products_in_cart = 0;

		// Count matching products in the cart
		foreach ( $this->get_products() as $product ) {
			if ( $this->quantity_of_normal_product_in_cart( (int) $product ) ) {
				$matching_products_in_cart++;
			}
		}

		// If all products must match and we have fewer products in the cart than in our matching list, return false
		if ( ! $this->any_product && count( $this->products ) > $matching_products_in_cart ) {
			cfw_debug_log( 'Order Bump Rule Failure: Cart does not contain all required products: ' . $this->get_id() );
			return false;
		}

		// If we get here, matching rule is set to any product, so we can
		// use the number of matching products to determine if we have a match
		$result = boolval( $matching_products_in_cart );

		if ( ! $result ) {
			cfw_debug_log( 'Order Bump Rule Failure: Cart does not contain any required products: ' . $this->get_id() );
		}

		return $result;
	}

	public function is_cart_bump_valid(): bool {
		$offer_product = $this->get_offer_product();

		if ( ! $offer_product ) {
			return $this->filtered_is_cart_bump_valid( false );
		}

		if ( $this->is_valid_upsell() && $this->is_in_cart() ) {
			return $this->filtered_is_cart_bump_valid( true );
		}

		// Is it a valid upsell (setup correctly) and are there enough units of the offer product to match the cart product?
		if ( $this->is_valid_upsell() && $offer_product->get_manage_stock() && $this->quantity_of_product_in_cart( array_values( $this->products )[0] ) > $offer_product->get_stock_quantity() ) {
			return $this->filtered_is_cart_bump_valid( false );
		}

		if ( $this->match_offer_product_quantity && $offer_product->get_manage_stock() && $this->quantity_of_normal_product_in_cart( array_values( $this->products )[0] ) > $offer_product->get_stock_quantity() ) {
			return $this->filtered_is_cart_bump_valid( false );
		}

		$matching_products_in_cart = 0;

		// Count matching products in the cart
		foreach ( $this->products as $product ) {
			if ( $this->quantity_of_normal_product_in_cart( (int) $product ) ) {
				$matching_products_in_cart++;
			}
		}

		// If all products must match and we have fewer products in the cart than in our matching list, return false
		if ( ! $this->any_product && count( $this->products ) > $matching_products_in_cart ) {
			return $this->filtered_is_cart_bump_valid( false );
		}

		// If we get here, matching rule is set to any product, so we can
		// use the number of matching products to determine if we have a match
		return $this->filtered_is_cart_bump_valid( boolval( $matching_products_in_cart ) );
	}

	private function filtered_is_cart_bump_valid( bool $result ) {
		/**
		 * Filters whether the bump is valid
		 *
		 * @param string $is_cart_bump_valid Whether the categories bump in the cart is still valid
		 * @since 6.3.0
		 */
		return apply_filters( 'cfw_is_cart_bump_valid', $result, $this );
	}

	/**
	 * @throws \Exception
	 */
	public function add_to_cart( \WC_Cart $cart ) {
		$product        = $this->get_offer_product();
		$variation_id   = $product->is_type( 'variable' ) ? $product->get_id() : null;
		$product_id     = $product->get_id();
		$variation_data = null;
		$metadata       = array(
			'_cfw_order_bump_id' => $this->id,
		);

		if ( $product->is_type( 'variation' ) ) {
			$variation_data = array();
			foreach ( $product->get_variation_attributes() as $taxonomy => $term_names ) {
				$taxonomy                                = str_replace( 'attribute_', '', $taxonomy );
				$attribute_label_name                    = str_replace( 'attribute_', '', wc_attribute_label( $taxonomy ) );
				$variation_data[ $attribute_label_name ] = $term_names;
			}
		}

		// Attempt to match variation attributes to search product
		if ( $product->is_type( 'variable' ) ) {
			$matched_variation_data = $this->get_matched_variation_attributes_from_cart_search_product( $product );

			if ( $matched_variation_data->get_id() ) {
				$variation_id   = $matched_variation_data->get_id();
				$variation_data = $matched_variation_data->get_attributes();
			}
		}

		$quantity = $this->get_offer_quantity();

		if ( $this->is_valid_upsell() ) {
			/**
			 * The max number of items that upsell can replace (-1 is unlimited)
			 *
			 * @since 7.6.1
			 *
			 * @param int $replace_up_to_quantity
			 * @param BumpInterface $bump
			 */
			$replace_up_to_quantity = apply_filters( 'cfw_order_bump_upsell_quantity_to_replace', -1, $this );

			$replace_up_to_quantity = $replace_up_to_quantity < 0 ? PHP_INT_MAX : $replace_up_to_quantity;

			$search_product = array_values( $this->get_products() )[0];
			$quantity       = $this->quantity_of_product_in_cart( $search_product );

			$quantity = min( $quantity, $replace_up_to_quantity );

			cfw_remove_product_from_cart( $search_product, $quantity );
		}

		if ( $this->match_offer_product_quantity ) {
			$quantity = $this->quantity_of_normal_product_in_cart( array_values( $this->products )[0] );
		}

		do_action( 'cfw_before_order_bump_add_to_cart', $this );

		$product_type = $product->get_type();

		if ( has_action( 'cfw_order_bump_add_to_cart_product_type_' . $product_type ) ) {
			/**
			 * Fires before order bump is added to the cart
			 *
			 * @since 9.0.0
			 * @param string $product_type The product type
			 * @param int $product_id The product ID
			 * @param int $quantity The quantity
			 * @param int $variation_id The variation ID
			 * @param array $variation_data The variation data
			 * @param array $metadata The metadata
			 * @param WC_Product $product The product
			 */
			do_action( 'cfw_order_bump_add_to_cart_product_type_' . $product_type, $product_id, $quantity, $variation_id, $variation_data, $metadata, $product );

			return true;
		}

		return $cart->add_to_cart( $product_id, $quantity, $variation_id, $variation_data, $metadata );
	}

	protected function get_cart_item_for_product( $search_product_id ) {
		foreach ( WC()->cart->get_cart() as $cart_item ) {
			if ( $this->cart_item_is_product( $cart_item, $search_product_id ) ) {
				return $cart_item;
			}
		}

		return array();
	}

	protected function get_matched_variation_attributes_from_cart_search_product( WC_Product $offer_product ): MatchedVariationResult {
		$variation_data = null;

		if ( ! $offer_product->is_type( 'variable' ) || count( $this->products ) !== 1 ) {
			return new MatchedVariationResult();
		}

		// Attempt to match variation attributes to search product
		/** @var WC_Product_Variation $search_product */
		$search_product_id = $this->products[0];
		$cart_item         = $this->get_cart_item_for_product( $search_product_id );
		$variation_id      = cfw_get_variation_id_from_attributes( $offer_product, $cart_item['variation'] ?? array() );

		if ( empty( $cart_item ) || empty( $variation_id ) ) {
			return new MatchedVariationResult();
		}

		foreach ( $cart_item['variation'] as $taxonomy => $term_names ) {
			$taxonomy                                = str_replace( 'attribute_', '', $taxonomy );
			$attribute_label_name                    = str_replace( 'attribute_', '', wc_attribute_label( $taxonomy ) );
			$variation_data[ $attribute_label_name ] = $term_names;
		}

		return new MatchedVariationResult( $variation_id, $variation_data );
	}
}
