<?php
/**
 * User Events Rule Helper.
 *
 * @package PRECO
 */

namespace PRECO\Includes\Frontend\Engine\Filters\Query;

use PRECO\Includes\Frontend\UserEvents\UserEventsTable;

defined( 'ABSPATH' ) || exit;

/**
 * Helper for filtering products based on user events (views, add-to-cart, remove-from-cart).
 */
class UserEventsRuleHelper {

	/**
	 * Add frequently viewed rule.
	 *
	 * @param array|null &$include_ids Include IDs array.
	 * @param bool       &$enforce_empty Enforce empty flag.
	 * @param string     $relation Relation (in/not_in).
	 * @param array      $value Rule value.
	 * @param int        $user_id Current user ID.
	 * @return void
	 */
	public function add_frequently_viewed_rule( &$include_ids, &$enforce_empty, $relation, $value, $user_id ) {
		// $relation is 'min_views' or 'max_views'; $value is the threshold count.
		$threshold = max( 1, absint( $value ) );
		$min_views = ( 'max_views' === $relation ) ? 1 : $threshold;
		$max_views = ( 'max_views' === $relation ) ? $threshold : 999999;

		if ( 0 === $user_id ) {
			$guest_events = $this->get_guest_transient_events( UserEventsTable::EVENT_VIEW );
			if ( empty( $guest_events ) ) {
				$enforce_empty = true;
				return;
			}
			$product_ids = array();
			foreach ( $guest_events as $event ) {
				$count = (int) $event['count'];
				if ( $count >= $min_views && $count <= $max_views ) {
					$product_ids[] = (int) $event['product_id'];
				}
			}
		} else {
			$product_ids = $this->get_frequently_viewed_products( $user_id, $min_views, $max_views );
		}

		if ( 'not_in' === $relation ) {
			// Exclude these products.
			if ( null === $include_ids ) {
				// Cannot enforce exclusion without a positive set, so we skip.
				return;
			}
			$include_ids = array_diff( $include_ids, $product_ids );
		} else {
			// Include only these products.
			$include_ids = ( null === $include_ids ) ? $product_ids : array_intersect( $include_ids, $product_ids );
		}

		if ( empty( $include_ids ) ) {
			$enforce_empty = true;
		}
	}

	/**
	 * Add added to cart rule.
	 *
	 * @param array|null &$include_ids Include IDs array.
	 * @param bool       &$enforce_empty Enforce empty flag.
	 * @param string     $relation Relation (in/not_in).
	 * @param array      $value Rule value.
	 * @param int        $user_id Current user ID.
	 * @return void
	 */
	public function add_added_to_cart_rule( &$include_ids, &$enforce_empty, $relation, $value, $user_id ) {
		$raw_period = is_array( $value ) ? ( $value['period'] ?? 'last_30d' ) : (string) $value;
		$period     = $this->parse_period( sanitize_text_field( $raw_period ) );

		if ( 0 === $user_id ) {
			$guest_events = $this->get_guest_transient_events( UserEventsTable::EVENT_ADD_TO_CART );
			if ( empty( $guest_events ) ) {
				$enforce_empty = true;
				return;
			}
			$product_ids = array_map(
				function ( $e ) {
					return (int) $e['product_id'];
				},
				$guest_events
			);
		} else {
			$product_ids = UserEventsTable::get_user_products( $user_id, UserEventsTable::EVENT_ADD_TO_CART, $period );
		}

		if ( 'not_in' === $relation ) {
			if ( null === $include_ids ) {
				return;
			}
			$include_ids = array_diff( $include_ids, $product_ids );
		} else {
			$include_ids = ( null === $include_ids ) ? $product_ids : array_intersect( $include_ids, $product_ids );
		}

		if ( empty( $include_ids ) && 'in' === $relation ) {
			$enforce_empty = true;
		}
	}

	/**
	 * Add removed from cart rule.
	 *
	 * @param array|null &$include_ids Include IDs array.
	 * @param bool       &$enforce_empty Enforce empty flag.
	 * @param string     $relation Relation (in/not_in).
	 * @param array      $value Rule value.
	 * @param int        $user_id Current user ID.
	 * @return void
	 */
	public function add_removed_from_cart_rule( &$include_ids, &$enforce_empty, $relation, $value, $user_id ) {
		$raw_period = is_array( $value ) ? ( $value['period'] ?? 'last_30d' ) : (string) $value;
		$period     = $this->parse_period( sanitize_text_field( $raw_period ) );

		if ( 0 === $user_id ) {
			$guest_events = $this->get_guest_transient_events( UserEventsTable::EVENT_REMOVE_FROM_CART );
			if ( empty( $guest_events ) ) {
				$enforce_empty = true;
				return;
			}
			$product_ids = array_map(
				function ( $e ) {
					return (int) $e['product_id'];
				},
				$guest_events
			);
		} else {
			$product_ids = UserEventsTable::get_user_products( $user_id, UserEventsTable::EVENT_REMOVE_FROM_CART, $period );
		}

		if ( 'not_in' === $relation ) {
			if ( null === $include_ids ) {
				return;
			}
			$include_ids = array_diff( $include_ids, $product_ids );
		} else {
			$include_ids = ( null === $include_ids ) ? $product_ids : array_intersect( $include_ids, $product_ids );
		}

		if ( empty( $include_ids ) && 'in' === $relation ) {
			$enforce_empty = true;
		}
	}

	/**
	 * Add products viewed rule.
	 *
	 * @param array|null &$include_ids Include IDs array.
	 * @param bool       &$enforce_empty Enforce empty flag.
	 * @param string     $relation Relation (in/not_in).
	 * @param mixed      $value Rule value (period string or array with 'period' key).
	 * @param int        $user_id Current user ID.
	 * @return void
	 */
	public function add_products_viewed_rule( &$include_ids, &$enforce_empty, $relation, $value, $user_id ) {
		$raw_period = is_array( $value ) ? ( $value['period'] ?? 'last_30d' ) : (string) $value;
		$period     = $this->parse_period( sanitize_text_field( $raw_period ) );

		if ( 0 === $user_id ) {
			$guest_events = $this->get_guest_transient_events( UserEventsTable::EVENT_VIEW );
			if ( empty( $guest_events ) ) {
				if ( 'in' === $relation ) {
					$enforce_empty = true;
				}
				return;
			}
			$product_ids = array_map(
				function ( $e ) {
					return (int) $e['product_id'];
				},
				$guest_events
			);
		} else {
			$product_ids = UserEventsTable::get_user_products( $user_id, UserEventsTable::EVENT_VIEW, $period );
		}

		if ( 'not_in' === $relation ) {
			if ( null === $include_ids ) {
				return;
			}
			$include_ids = array_diff( $include_ids, $product_ids );
		} else {
			$include_ids = ( null === $include_ids ) ? $product_ids : array_intersect( $include_ids, $product_ids );
		}

		if ( empty( $include_ids ) && 'in' === $relation ) {
			$enforce_empty = true;
		}
	}

	/**
	 * Add previously purchased rule.
	 *
	 * @param array|null &$include_ids Include IDs array.
	 * @param bool       &$enforce_empty Enforce empty flag.
	 * @param string     $relation Relation (in/not_in).
	 * @param array      $value Rule value.
	 * @param int        $user_id Current user ID.
	 * @return void
	 */
	public function add_previously_purchased_rule( &$include_ids, &$enforce_empty, $relation, $value, $user_id ) {
		if ( 0 === $user_id ) {
			$enforce_empty = true;
			return;
		}

		$raw_period = is_array( $value ) ? ( $value['period'] ?? 'last_30d' ) : (string) $value;
		$period     = $this->parse_period( sanitize_text_field( $raw_period ) );

		$product_ids = $this->get_purchased_products( $user_id, $period );

		if ( 'not_in' === $relation ) {
			if ( null === $include_ids ) {
				return;
			}
			$include_ids = array_diff( $include_ids, $product_ids );
		} else {
			$include_ids = ( null === $include_ids ) ? $product_ids : array_intersect( $include_ids, $product_ids );
		}

		if ( empty( $include_ids ) && 'in' === $relation ) {
			$enforce_empty = true;
		}
	}

	/**
	 * Get events for a guest user from their transient, filtered by event type.
	 *
	 * @param int $event_type Event type constant.
	 * @return array Array of event entries with product_id, event_type, count.
	 */
	private function get_guest_transient_events( $event_type ) {
		$cookie_name = 'preco_guest_id';

		if ( empty( $_COOKIE[ $cookie_name ] ) ) {
			return array();
		}

		$guest_id      = sanitize_text_field( wp_unslash( $_COOKIE[ $cookie_name ] ) );
		$transient_key = 'preco_guest_' . $guest_id;
		$events        = get_transient( $transient_key );

		if ( empty( $events ) || ! is_array( $events ) ) {
			return array();
		}

		return array_values(
			array_filter(
				$events,
				function ( $event ) use ( $event_type ) {
					return (int) $event['event_type'] === $event_type;
				}
			)
		);
	}

	/**
	 * Get frequently viewed products for a user.
	 *
	 * @param int $user_id User ID.
	 * @param int $min_views Minimum views.
	 * @param int $max_views Maximum views.
	 * @return array Product IDs.
	 */
	private function get_frequently_viewed_products( $user_id, $min_views, $max_views ) {
		global $wpdb;
		$table_name = esc_sql( UserEventsTable::get_table_name() );

		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$results = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT product_id FROM {$table_name}
				WHERE user_id = %d AND event_type = %d AND count >= %d AND count <= %d
				ORDER BY count DESC",
				$user_id,
				UserEventsTable::EVENT_VIEW,
				$min_views,
				$max_views
			)
		);
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared

		return $results ? array_map( 'intval', $results ) : array();
	}

	/**
	 * Get purchased products for a user.
	 *
	 * @param int    $user_id User ID.
	 * @param string $period  Time period.
	 * @return array Product IDs.
	 */
	private function get_purchased_products( $user_id, $period ) {
		if ( ! function_exists( '\wc_get_orders' ) ) {
			return array();
		}

		$args = array(
			'customer_id' => $user_id,
			'status'      => array( 'wc-completed', 'wc-processing' ),
			'limit'       => -1,
			'return'      => 'ids',
		);

		if ( 'alltime' !== $period ) {
			$args['date_created'] = '>' . strtotime( '-' . $period );
		}

		$order_ids = \wc_get_orders( $args );

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

		global $wpdb;
		$opl_table = $wpdb->prefix . 'wc_order_product_lookup';

		// Check if the lookup table exists; if so, batch-fetch all product IDs in one query.
		$table_exists = (bool) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $opl_table ) )
		);

		if ( $table_exists ) {
			$opl_sql      = esc_sql( $opl_table );
			$placeholders = implode( ',', array_fill( 0, count( $order_ids ), '%d' ) );
			// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
			$product_ids = $wpdb->get_col(
				$wpdb->prepare(
					"SELECT DISTINCT product_id FROM {$opl_sql} WHERE order_id IN ( {$placeholders} )",
					$order_ids
				)
			);
			// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare

			return $product_ids ? array_map( 'intval', $product_ids ) : array();
		}

		// Fallback: load each order individually (N+1, but safe if lookup table is absent).
		$product_ids = array();
		foreach ( $order_ids as $order_id ) {
			$order = \wc_get_order( $order_id );
			if ( ! $order ) {
				continue;
			}
			foreach ( $order->get_items() as $item ) {
				$product_id = $item->get_product_id();
				if ( $product_id ) {
					$product_ids[] = $product_id;
				}
			}
		}

		return array_unique( $product_ids );
	}

	/**
	 * Parse period string to appropriate format.
	 *
	 * @param string $period Period string.
	 * @return string Parsed period.
	 */
	private function parse_period( $period ) {
		$map = array(
			'last_7d'  => '7 days',
			'last_14d' => '14 days',
			'last_30d' => '30 days',
			'last_60d' => '60 days',
			'alltime'  => 'alltime',
		);

		return isset( $map[ $period ] ) ? $map[ $period ] : '30 days';
	}
}
