<?php

/**
 * @package     ApprovedOrders
 * @since       1.0.4
 */

namespace ApproveOrders\Modules;

use ApproveOrders\Traits\Get_Instance;
use ApproveOrders\Helper;
use WC_Order;
use WC_Cart;

class Rules {

	use Get_Instance;

	protected $options   = array();
	protected $operators = array();
	protected $rules     = array();

	/**
	 * Constructor
	 *
	 * @since 1.0.0
	 */
	public function __construct() {
		// Define options and option groups
		$this->options = array(
			'products'         => array(
				'product_name'      => __( 'Product ', 'approve-orders' ),
				'product_variation' => __( 'Product Variation', 'approve-orders' ),
				'product_category'  => __( 'Product Category', 'approve-orders' ),
				'product_tag'       => __( 'Product Tag', 'approve-orders' ),
				// 'product_type'      => __( 'Product Type', 'approve-orders' ),
			),
			'users'            => array(
				'user_role' => __( 'User Role ', 'approve-orders' ),
			),
			'orders'           => array(
				'order_total'       => __( 'Order Total', 'approve-orders' ),
				'order_items_count' => __( 'Number of Items', 'approve-orders' ),
				'order_discount'    => __( 'Order Discount', 'approve-orders' ),
			),
			'cart_data'        => array(
				'cart_total'       => __( 'Cart Total', 'approve-orders' ),
				'cart_items_count' => __( 'Cart Items Count', 'approve-orders' ),
				'cart_discount'    => __( 'Cart Discount', 'approve-orders' ),
			),
			'payment_gateways' => array(
				'payment_method' => __( 'Payment Method', 'approve-orders' ),
			),
		);

		// Define operators for each option type
		$this->operators = array(
			'default'            => array(
				'in'     => __( 'Includes', 'approve-orders' ),
				'not_in' => __( 'Does Not Include', 'approve-orders' ),
				'='      => __( 'Equals', 'approve-orders' ),
				'!='     => __( 'Not Equals', 'approve-orders' ),
				'>'      => __( 'Greater Than', 'approve-orders' ),
				'<'      => __( 'Less Than', 'approve-orders' ),
			),
			'product_name'       => array(
				'='      => __( 'Is', 'approve-orders' ),
				'!='     => __( 'Is Not', 'approve-orders' ),
				'in'     => __( 'Includes', 'approve-orders' ),
				'not_in' => __( 'Does Not Include', 'approve-orders' ),
			),
			'order_total'        => array(
				'='  => __( 'Equals', 'approve-orders' ),
				'!=' => __( 'Not Equals', 'approve-orders' ),
				'>'  => __( 'Greater Than', 'approve-orders' ),
				'<'  => __( 'Less Than', 'approve-orders' ),
			),
			'order_status'       => array(
				'='  => __( 'Is', 'approve-orders' ),
				'!=' => __( 'Is Not', 'approve-orders' ),
			),
			'cart_total'         => array(
				'='  => __( 'Equals', 'approve-orders' ),
				'!=' => __( 'Not Equals', 'approve-orders' ),
				'>'  => __( 'Greater Than', 'approve-orders' ),
				'<'  => __( 'Less Than', 'approve-orders' ),
			),
			'cart_items_count'   => array(
				'='  => __( 'Equals', 'approve-orders' ),
				'!=' => __( 'Not Equals', 'approve-orders' ),
				'>'  => __( 'Greater Than', 'approve-orders' ),
				'<'  => __( 'Less Than', 'approve-orders' ),
			),
			'cart_discount'      => array(
				'='  => __( 'Equals', 'approve-orders' ),
				'!=' => __( 'Not Equals', 'approve-orders' ),
				'>'  => __( 'Greater Than', 'approve-orders' ),
				'<'  => __( 'Less Than', 'approve-orders' ),
			),
			'customer_email'     => array(
				'='    => __( 'Equals', 'approve-orders' ),
				'!='   => __( 'Not Equals', 'approve-orders' ),
				'like' => __( 'Contains', 'approve-orders' ),
			),
			'returning_customer' => array(
				'='  => __( 'Yes', 'approve-orders' ),
				'!=' => __( 'No', 'approve-orders' ),
			),
			'order_date'         => array(
				'='  => __( 'Equals', 'approve-orders' ),
				'!=' => __( 'Not Equals', 'approve-orders' ),
				'>'  => __( 'After', 'approve-orders' ),
				'<'  => __( 'Before', 'approve-orders' ),
			),
		);
	}

	// Method to get option groups
	public function get_option_groups() {
		return $this->options;
	}

	// Method to get operators for a given option (fallback to default if no specific operators found)
	public function get_operators( $option ) {
		if ( isset( $this->operators[ $option ] ) ) {
			return $this->operators[ $option ];
		}

		// Return default operators if no specific operators are found for the option
		return $this->operators['default'];
	}

	// Method to generate dropdown options for option groups
	public function create_dropdown_options() {
		$optionGroups = $this->get_option_groups();

		$html  = '<select name="rule_options" class="form-select item-selector" required>';
		$html .= '<option value="">' . esc_html__( 'Select Option', 'thank-you-page-pro' ) . '</option>';

		foreach ( $optionGroups as $groupName => $options ) {
			$html .= '<optgroup label="' . ucwords( str_replace( array( '_', '-' ), ' ', $groupName ) ) . '">';

			foreach ( $options as $value => $label ) {
				$html .= '<option value="' . $value . '">' . ucwords( str_replace( array( '_', '-' ), ' ', $label ) ) . '</option>';
			}

			$html .= '</optgroup>';
		}

		$html .= '</select>';

		return $html;
	}

	// Method to generate dropdown operators based on the selected option
	public function create_dropdown_operators( $selectedOption = '' ) {
		$operators = $this->get_operators( $selectedOption );

		$html  = '<select class="form-select operator-selector" required>';
		$html .= '<option value="">' . esc_html__( 'Select Operator', 'thank-you-page-pro' ) . '</option>';
		foreach ( $operators as $value => $label ) {
			$html .= '<option value="' . $value . '">' . $label . '</option>';
		}

		$html .= '</select>';

		return $html;
	}

	/**
	 * Create dropdown pages for rule pages
	 *
	 * @return string $html
	 */
	public function create_dropdown_pages() {
		$html  = '<select name="rule_pages" class="form-select url-dropdown" required>';
		$html .= '<option value="">' . esc_html__( 'Select Page', 'thank-you-page-pro' ) . '</option>';

		$pages = Helper::get_instance()->get_pages();

		foreach ( $pages as $page ) {
			$html .= '<option value="' . $page['url'] . '">' . $page['text'] . '</option>';
		}

		$html .= '</select>';

		return $html;
	}

	/**
	 * Process rules based on either an order ID or a WC_Cart object.
	 * This acts as the middleware function.
	 *
	 * @param int|WC_Cart|WC_Order $data_source The order ID, WC_Cart object, or WC_Order object.
	 * @param array                $saved_rules Array of saved rule groups.
	 * @return array|null The matched rule group or null if no rules match.
	 */
	public function process_rules( $data_source, array $saved_rules = array() ): ?array {

		if ( empty( $saved_rules ) ) {
			return null; // Return null consistently if no rules to process
		}

		$normalized_data = $this->prepare_data_for_evaluation( $data_source );

		if ( is_null( $normalized_data ) ) {
			return null; // Could not normalize data source
		}

		// Loop through each group of rules
		foreach ( $saved_rules as $rule_group ) {
			if ( ! isset( $rule_group['approvalAction'] ) || ! isset( $rule_group['rules'] ) ) {
				continue;
			}

			// Process the rules based on condition
			$valid = $this->evaluate_rules( $rule_group['rules'], $normalized_data );

			// Return the rule group if valid
			if ( $valid ) {
				return $rule_group;
			}
		}

		return null; // No rule group matched
	}

	/**
	 * Prepares data from an order or cart into a normalized array for rule evaluation.
	 * This function now delegates to specific methods for order or cart data.
	 *
	 * @param int|WC_Cart|WC_Order $data_source The order ID, WC_Cart object, or WC_Order object.
	 * @return array|null Normalized data array, or null if data source is invalid.
	 */
	private function prepare_data_for_evaluation( $data_source ): ?array {
		$base_data = null;

		if ( is_int( $data_source ) ) {
			$order = wc_get_order( $data_source );
			if ( ! $order ) {
				return null;
			}
			$base_data = $this->normalize_order_data( $order );
		} elseif ( $data_source instanceof WC_Cart ) {
			$base_data = $this->normalize_cart_data( $data_source );
		} elseif ( $data_source instanceof WC_Order ) {
			$base_data = $this->normalize_order_data( $data_source );
		} else {
			// Invalid data source
			return null;
		}

		if ( is_null( $base_data ) ) {
			return null;
		}

		$normalized_data = $base_data;

		// Initialize product related data, which is common for both cart and order
		$normalized_data['product_name']      = array();
		$normalized_data['product_variation'] = array();
		$normalized_data['product_category']  = array();
		$normalized_data['product_tag']       = array();

		// Process items to extract product-specific data
		foreach ( $normalized_data['items_to_process'] as $item_key => $item ) {
			$product    = null;
			$product_id = 0; // Initialize product_id

			if ( $item instanceof \WC_Order_Item_Product ) { // Order item
				$product    = $item->get_product();
				$product_id = $item->get_product_id();
			} elseif ( is_array( $item ) && isset( $item['data'] ) && $item['data'] instanceof \WC_Product ) { // Cart item
				$product    = $item['data'];
				$product_id = $product->get_id();
			} else {
				continue; // Skip if product cannot be found
			}

			if ( ! $product ) {
				continue;
			}

			// Product Name (ID for uniqueness, name for display/matching)
			$normalized_data['product_name'][ $product_id ] = $product->get_name();

			// Product Variation
			if ( $product->is_type( 'variation' ) ) {
				$variation_id = $product->get_id();
				$normalized_data['product_variation'][ $variation_id ] = $product->get_attributes(); // Store attributes array
			}

			// Product Category
			$terms = get_the_terms( $product_id, 'product_cat' );
			if ( $terms && ! is_wp_error( $terms ) ) {
				foreach ( $terms as $term ) {
					$normalized_data['product_category'][] = $term->name;
				}
			}

			// Product Tag
			$terms = get_the_terms( $product_id, 'product_tag' );
			if ( $terms && ! is_wp_error( $terms ) ) {
				foreach ( $terms as $term ) {
					$normalized_data['product_tag'][] = $term->name;
				}
			}
		}

		// Remove duplicate categories/tags if any
		$normalized_data['product_category'] = array_unique( $normalized_data['product_category'] );
		$normalized_data['product_tag']      = array_unique( $normalized_data['product_tag'] );

		// User Role (common for both)
		$current_user_roles = array();
		if ( is_user_logged_in() ) {
			$current_user       = wp_get_current_user();
			$current_user_roles = $current_user->roles;
		} else {
			// For guest users, you might define a 'guest' role or leave empty based on your rules.
			$current_user_roles[] = 'guest';
		}
		$normalized_data['user_role'] = $current_user_roles;

		return $normalized_data;
	}

	/**
	 * Normalizes data from a WC_Order object.
	 *
	 * @param WC_Order $order The WooCommerce order object.
	 * @return array Normalized base order data.
	 */
	private function normalize_order_data( WC_Order $order ): array {
		$item_count = 0;
		foreach ( $order->get_items() as $item ) {
			$item_count += $item->get_quantity();
		}
		return array(
			'order_total'       => (float) $order->get_total(),
			'order_discount'    => (float) $order->get_discount_total(),
			'order_items_count' => (int) $item_count, // Calculate here
			'payment_method'    => $order->get_payment_method(),
			'items_to_process'  => $order->get_items(),
			'cart_total'        => 0.0, // Initialize cart specific fields to default for order context
			'cart_discount'     => 0.0,
			'cart_items_count'  => 0,
		);
	}

	/**
	 * Normalizes data from a WC_Cart object.
	 *
	 * @param WC_Cart $cart The WooCommerce cart object.
	 * @return array Normalized base cart data.
	 */
	private function normalize_cart_data( WC_Cart $cart ): array {
		$item_count = 0;
		foreach ( $cart->get_cart() as $item ) {
			$item_count += $item['quantity'];
		}
		return array(
			'cart_total'        => (float) $cart->get_total( false ), // get_total(false) for raw total
			'cart_discount'     => (float) $cart->get_discount_total(),
			'cart_items_count'  => (int) $item_count, // Calculate here
			'payment_method'    => WC()->session->get( 'chosen_payment_method' ) ?? '',
			'items_to_process'  => $cart->get_cart(),
			'order_total'       => 0.0, // Initialize order specific fields to default for cart context
			'order_discount'    => 0.0,
			'order_items_count' => 0,
		);
	}

	/**
	 * Evaluate multiple rules based on the specified condition.
	 *
	 * @param array $rules Array of rules to evaluate.
	 * @param array $normalized_data Normalized data (from order or cart) to match against.
	 * @return bool True if the rules are satisfied, false otherwise.
	 */
	private function evaluate_rules( array $rules, array $normalized_data ): bool {
		// Determine the overall condition (AND/OR) for the group.
		// Assuming the condition is consistent across rules in a group, or defaults to AND.
		// If 'condition' is group-level, you'd pass it from $rule_group.
		$condition = 'AND'; // Default to 'AND' if not specified or for robust handling

		if ( ! empty( $rules ) && isset( $rules[0]['condition'] ) ) {
			$condition = $rules[0]['condition'];
		}

		foreach ( $rules as $rule ) {
			if ( ! isset( $rule['item'], $rule['operator'], $rule['value'] ) ) {
				continue; // Skip malformed rules
			}
			$item     = $rule['item'];
			$operator = $rule['operator'];
			$value    = $rule['value'];

			$data_value = $this->get_order_data_value( $item, $normalized_data );
			$rule_valid = $this->evaluate_rule( $data_value, $operator, $value );

			if ( $condition === 'AND' ) {
				if ( ! $rule_valid ) {
					return false; // If any rule is invalid in AND condition, return false
				}
			} elseif ( $condition === 'OR' ) {
				if ( $rule_valid ) {
					return true; // If any rule is valid in OR condition, return true
				}
			}
		}

		// If all rules are evaluated, return the result based on the condition
		// If condition is 'AND', all must be true. If 'OR', at least one must be true.
		// The loop logic already handles this correctly. If we reach here for 'AND', all were true.
		// If we reach here for 'OR' and no rule was true, then it should be false.
		return $condition === 'AND' ? true : false;
	}

	/**
	 * Get the value from the normalized data based on the rule item.
	 *
	 * @param string $item Rule item.
	 * @param array  $normalized_data Normalized order/cart data.
	 * @return mixed Value from the normalized data, or empty string/array if not found.
	 */
	private function get_order_data_value( string $item, array $normalized_data ) {
		switch ( $item ) {
			case 'payment_method':
				return $normalized_data['payment_method'] ?? '';
			case 'product_name':
				// Returns product IDs as keys, use array_keys for comparison
				return array_keys( $normalized_data['product_name'] ?? array() );
			case 'product_variation':
				// For variations, it returns an array of attribute arrays.
				// For 'in' or 'not_in' operator, if the $value is, say, 'blue', it needs a specific attribute check.
				// The current implementation directly passes the stored attributes array.
				// For proper evaluation against specific attributes, this would need refinement.
				// For now, it will check if any of the variation attribute arrays intersect with the rule's value.
				// This might not be intuitive if the value is a simple string.
				return $normalized_data['product_variation'] ?? array();
			case 'product_category':
				// Returns an array of category names
				return $normalized_data['product_category'] ?? array();
			case 'product_tag':
				// Returns an array of tag names
				return $normalized_data['product_tag'] ?? array();
			case 'user_role':
				// Returns an array of user roles
				return $normalized_data['user_role'] ?? array();
			case 'order_total':
				return $normalized_data['order_total'] ?? 0.0;
			case 'order_items_count':
				return $normalized_data['order_items_count'] ?? 0;
			case 'order_discount':
				return $normalized_data['order_discount'] ?? 0.0;
			case 'cart_total': // New case for cart total
				return $normalized_data['cart_total'] ?? 0.0;
			case 'cart_items_count': // New case for cart items count
				return $normalized_data['cart_items_count'] ?? 0;
			case 'cart_discount': // New case for cart discount
				return $normalized_data['cart_discount'] ?? 0.0;
			default:
				return '';
		}
	}

	/**
	 * Evaluate a single rule.
	 *
	 * @param mixed  $data_value The value to compare.
	 * @param string $operator The operator to use for comparison.
	 * @param mixed  $value The value to compare against.
	 * @return bool True if the rule is satisfied, false otherwise.
	 */
	private function evaluate_rule( $data_value, $operator, $value ) {

		switch ( $operator ) {
			case '=':
				return $data_value == $value;
			case '!=':
				return $data_value != $value;
			case '>':
				return $data_value > $value;
			case '<':
				return $data_value < $value;
			case 'in':
				if ( is_array( $data_value ) ) {
					// Check if there is any intersection between the two arrays
					return ! empty( array_intersect( $data_value, (array) $value ) );
				} else {
					// Check if the single value is in the array
					return in_array( $data_value, (array) $value, true );
				}
			case 'not_in':
				return is_array( $data_value ) ? ! array_intersect( $data_value, (array) $value ) : ! in_array( $data_value, $value, true );
			default:
				return false;
		}
	}
}
