<?php

namespace MOWCPS\Common\Services;

use MOWCPS\Common\Constants\Constants;
use MOWCPS\Common\Utils\Sync_Utils;
use MOWCPS\Common\Utils\Model_Utils;
use MOWCPS\Common\Services\Logging\Supplier_Logger;
use MOWCPS\Common\Models\Sync_Details_DAO;
use MOWCPS\Common\Models\Connection_Model;

class Filter_Data_Services {

	/**
	 * Filter data.
	 *
	 * @param array   $products         Product data to filter.
	 * @param array   $filter_details   Filter conditions and their relations.
	 * @param boolean $is_outbound_flow Whether this is an outbound flow. Default false.
	 * @param int     $supplier_id      The supplier ID. Required if is_outbound_flow is true.
	 * @param int     $connection_id    The connection ID. Required if is_outbound_flow is true.
	 * @return array
	 */
	public static function filter_data( $products, $filter_details, $is_outbound_flow = false, $supplier_id = 0, $connection_id = 0 ) {
		$filtered_products = array();
		$products_to_delete = array();

		// Iterate over each product and apply filter.
		foreach ( $products as $product ) {
			if ( self::filter_product( $product, $filter_details ) ) {
				$filtered_products[] = $product;
			} elseif ( $is_outbound_flow ) {
				// If outbound flow and product doesn't match filter conditions, mark for deletion.
				$connection    = Connection_Model::get_by_id( $connection_id );
				$prerequisites = $connection['configuration']['prerequisites'];
				if ( isset( $prerequisites['variable_response']['parent_identifier'] ) && ! empty( $prerequisites['variable_response']['parent_identifier'] ) ) {
					$unique_identifier_key = $prerequisites['variable_response']['parent_identifier'];
				} else {
					$unique_identifier_key = $prerequisites['main_product_unique_identifier'];
				}
				$product_to_delete = self::extract_product_identifier( $product, $unique_identifier_key, $supplier_id );
				if ( ! empty( $product_to_delete ) ) {
					$products_to_delete[] = $product_to_delete;
				}
			}
		}

		// Delete products that don't match filter conditions in outbound flow.
		if ( $is_outbound_flow && ! empty( $products_to_delete ) ) {
			self::delete_unmatched_products( $products_to_delete, $supplier_id, $connection_id );
		}

		return $filtered_products;
	}

	/**
	 * Extract product identifier from product data using the unique identifier key.
	 *
	 * @param array  $product              The product data.
	 * @param string $unique_identifier_key The unique identifier key path (supports nested paths with '->').
	 * @param int    $supplier_id         The supplier ID for logging.
	 * @return int|string|null Product ID from WooCommerce or extracted identifier, or null if not found.
	 */
	private static function extract_product_identifier( $product, $unique_identifier_key, $supplier_id = 0 ) {
		// First, try to extract using the unique identifier key.
		if ( ! empty( $unique_identifier_key ) ) {
			try {
				$unique_value = Sync_Utils::map_particular_attr( $unique_identifier_key, $product );
				$supplier_details         = Sync_Details_DAO::get_instance()->get_supplier_details( $supplier_id );
				$supplier_unique_id_key   = $supplier_details['unique_product_identifier'] ?? 'mo_uid';
				if ( ! empty( $unique_value ) ) {
					// Try to find the product ID by this unique identifier in postmeta.
					return Model_Utils::get_product_id_by_meta( $supplier_unique_id_key, $unique_value );
				}
			} catch ( \Exception $e ) {
				Supplier_Logger::debug( 
					$supplier_id, 
					'Error extracting product identifier: ' . $e->getMessage() 
				);
			}
		}

		return null;
	}

	/**
	 * Delete products that don't match filter conditions.
	 *
	 * @param array $product_ids Array of product IDs to delete.
	 * @param int   $supplier_id The supplier ID.
	 * @param int   $connection_id The connection ID.
	 * @return void
	 */
	private static function delete_unmatched_products( $product_ids, $supplier_id, $connection_id ) {
		if ( empty( $product_ids ) ) {
			return;
		}

		// Use the bulk delete function from Model_Utils.
		$deletion_result = Model_Utils::bulk_delete_products_direct_sql_batched( $product_ids );

		if ( is_wp_error( $deletion_result ) ) {
			Supplier_Logger::debug( 
				$supplier_id, 
				'Failed to delete unmatched products for connection ' . $connection_id . ': ' . $deletion_result->get_error_message() 
			);
		} else {
			$deleted_count = isset( $deletion_result['deleted_count'] ) ? $deletion_result['deleted_count'] : 0;
			if ( $deleted_count > 0 ) {
				Supplier_Logger::debug( 
					$supplier_id, 
					'Deleted ' . $deleted_count . ' unmatched products for connection ' . $connection_id . ' during outbound sync filter.' 
				);
			}
		}
	}

	/**
	 * Filter a single product based on multiple filter conditions and their relations.
	 *
	 * @param array $product    The product data to filter.
	 * @param array $filter_details Filter conditions and relations.
	 *
	 * @return bool True if the product matches all filter conditions, false otherwise.
	 */
	public static function filter_product( $product, $filter_details ) {
		$condition_results = array();

		// Apply each filter condition to the current product.
		foreach ( $filter_details['conditions'] as $filter ) {
			$attribute = $filter['wc_attribute'];
			$condition = $filter['operator'];
			$values    = $filter['supplier_attribute'];

			// Apply the condition to the product's attribute or nested attribute.
			$condition_results[] = self::is_product_met_condition( $product, $attribute, $condition, $values );
		}

		// Combine condition results using filter_relation.
		$final_result = $condition_results[0];
		for ( $i = 1; $i < count( $condition_results ); $i++ ) {
			$relation = $filter_details['combine_operator'][ $i - 1 ];
			if ( Constants::AND_CONDITION === $relation ) {
				$final_result = $final_result && $condition_results[ $i ];
			} elseif ( Constants::OR_CONDITION === $relation ) {
				$final_result = $final_result || $condition_results[ $i ];
			}
		}

		// Return true if the product meets all filter conditions.
		return $final_result;
	}

	/**
	 * Apply a filter condition to a product, supporting both array-based and nested attributes.
	 *
	 * @param array  $product  The product data to filter.
	 * @param string $attribute The attribute or path to the attribute to filter on (supports nested attributes using '->').
	 * @param string $condition The condition to apply ('=', '!=', 'null', 'not null', '>', '<', 'contains', 'not contains').
	 * @param array  $values    The values to compare against.
	 *
	 * @return bool True if the product meets the condition, false otherwise.
	 */
	public static function is_product_met_condition( $product, $attribute, $condition, $values ) {
		// Split the attribute path by '->' and check for nested paths.
		$path_parts   = explode( '->', $attribute );
		$nested_value = $product;

		// Traverse through the path parts.
		foreach ( $path_parts as $part ) {
			// Check if the part contains array notation like [some_key] (e.g., _links->self->[href]).
			if ( preg_match( '/\[(.*?)\]/', $part, $matches ) ) {
				$key = $matches[1];
				if ( is_array( $nested_value ) ) {
					// If it's an array, check each element for the matching key.
					$found = false;
					foreach ( $nested_value as $element ) {
						if ( isset( $element[ $key ] ) ) {
							$nested_value = $element[ $key ];
							$found        = true;
							break;
						}
					}
					if ( ! $found ) {
						return false; // If the key is not found in the array.
					}
				} else {
					// If it's not an array, return false.
					return false;
				}
			} else {
				// Normal key-based access (e.g., categories->id, dimensions->length).
				if ( is_array( $nested_value ) && isset( $nested_value[ $part ] ) ) {
					$nested_value = $nested_value[ $part ];
				} elseif ( is_object( $nested_value ) && isset( $nested_value->$part ) ) {
					$nested_value = $nested_value->$part;
				} elseif ( null === ( is_array( $nested_value ) ? $nested_value[ $part ] : $nested_value->$part ) ) {
					// If the value is explicitly null, check if the condition is 'null' and pass null value to evaluate.
					if ( Constants::IS_NULL !== $condition ) {
						return false;
					} else {
						$nested_value = null;
					}
				} else {
					return false; // Return false if any part of the path doesn't exist.
				}
			}
		}

		// Now that we have reached the value, apply the condition on the nested value.
		return self::evaluate_condition( $nested_value, $condition, $values );
	}

	/**
	 * Evaluate the condition on a product value based on the provided condition.
	 *
	 * @param mixed  $product_value The value of the product attribute to evaluate.
	 * @param string $condition    The condition to apply ('=', '!=', 'null', 'not null', '>', '<', 'contains', 'not contains').
	 * @param array  $values       The values to compare against.
	 *
	 * @return bool True if the condition is met, false otherwise.
	 */
	public static function evaluate_condition( $product_value, $condition, $values ) {
		switch ( $condition ) {

			case Constants::EQUAL:
				return in_array( $product_value, $values, false );

			case Constants::NOT_EQUAL:
				return ! in_array( $product_value, $values, false );

			case Constants::IS_NULL:
				return is_null( $product_value );

			case Constants::IS_NOT_NULL:
				return ! is_null( $product_value );

			case Constants::GREATER_THAN:
				return isset( $values[0] ) && (int) $product_value > (int) $values[0];

			case Constants::LESS_THAN:
				return isset( $values[0] ) && (int) $product_value < (int) $values[0];

			case Constants::IS_EMPTY:
				return empty( $product_value );

			case Constants::IS_NOT_EMPTY:
				return ! empty( $product_value );

			case Constants::CONTAINS:
				// Check if the product value is an array and contains any of the values.
				if ( is_array( $product_value ) ) {
					foreach ( $values as $value ) {
						if ( in_array( $value, $product_value, true ) ) {
							return true;
						}
					}
				}
				return false;

			case Constants::NOT_CONTAINS:
				// Check if the product value is an array and does NOT contain any of the values.
				if ( is_array( $product_value ) ) {
					foreach ( $values as $value ) {
						if ( in_array( $value, $product_value, true ) ) {
							return false;
						}
					}
					return true;
				}
				return false;

			case Constants::LIKE:
				foreach ( $values as $value ) {
					if ( false !== stripos( $product_value, $value ) ) {
						return true;
					}
				}
				return false;

			case Constants::NOT_LIKE:
				foreach ( $values as $value ) {
					if ( false !== stripos( $product_value, $value ) ) {
						return false;
					}
				}
				return true;

			default:
				return false;
		}
	}
}
