<?php
/**
 * WooCommerce AB Variant Runtime
 *
 * @package Ugcify\Integrations\WooCommerce
 */

namespace Ugcify\Integrations\WooCommerce;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class ABVariantRuntime
 */
class ABVariantRuntime {
	/**
	 * Option name that stores A/B testing payload.
	 *
	 * @var string
	 */
	private const AB_TESTS_OPTION_NAME = 'ugcify_ab_tests';

	/**
	 * Prefix used for storefront AB assignment cookies.
	 *
	 * @var string
	 */
	private const AB_COOKIE_PREFIX = 'ugcify_ab_variant_';

	/**
	 * Assignment cookie TTL.
	 *
	 * @var int
	 */
	private const AB_COOKIE_TTL = 2592000;

	/**
	 * Request-level cache for A/B runtime contexts.
	 *
	 * @var array<int,array<string,mixed>>
	 */
	private $ab_runtime_context = array();

	/**
	 * Prime AB assignment context for current single product request.
	 *
	 * @return array<string,mixed>
	 */
	public function prime_product_context() {
		if ( ! $this->is_single_product_context() ) {
			return array(
				'has_test' => false,
			);
		}

		$product_id = get_queried_object_id();
		if ( $product_id < 1 ) {
			return array(
				'has_test' => false,
			);
		}

		return $this->get_ab_variant_context_for_product( $product_id );
	}

	/**
	 * Resolve variant featured image ID for product.
	 *
	 * @param int $product_id Product ID.
	 * @param int $fallback_id Current featured image ID.
	 * @return int
	 */
	public function resolve_featured_image_id( $product_id, $fallback_id ) {
		if ( ! $this->is_featured_image_context() ) {
			return $fallback_id;
		}

		$context = $this->get_ab_variant_context_for_product( $product_id );
		if ( empty( $context['can_override_images'] ) ) {
			return $fallback_id;
		}

		$featured_id = isset( $context['featured_attachment_id'] ) ? absint( $context['featured_attachment_id'] ) : 0;
		return $featured_id > 0 ? $featured_id : $fallback_id;
	}

	/**
	 * Resolve variant gallery IDs for product.
	 *
	 * @param int   $product_id Product ID.
	 * @param array $fallback_gallery_ids Current gallery image IDs.
	 * @return array
	 */
	public function resolve_gallery_image_ids( $product_id, $fallback_gallery_ids ) {
		if ( ! $this->is_single_product_context() ) {
			return $fallback_gallery_ids;
		}

		$context = $this->get_ab_variant_context_for_product( $product_id );
		if ( empty( $context['can_override_images'] ) ) {
			return $fallback_gallery_ids;
		}

		$variant_gallery_ids = isset( $context['gallery_attachment_ids'] ) && is_array( $context['gallery_attachment_ids'] )
			? array_values( array_filter( array_map( 'absint', $context['gallery_attachment_ids'] ) ) )
			: array();

		return $variant_gallery_ids;
	}

	/**
	 * Get public AB tracking context for a product.
	 *
	 * @param int $product_id Product ID.
	 * @return array<string,mixed>
	 */
	public function get_tracking_context( $product_id ) {
		$context = $this->get_ab_variant_context_for_product( $product_id );

		return array(
			'product_id' => isset( $context['product_id'] ) ? absint( $context['product_id'] ) : absint( $product_id ),
			'has_test'   => ! empty( $context['has_test'] ),
			'test_id'    => isset( $context['test_id'] ) ? sanitize_key( (string) $context['test_id'] ) : '',
			'variant'    => isset( $context['variant'] ) ? sanitize_key( (string) $context['variant'] ) : 'original',
			'tab_key'    => isset( $context['tab_key'] ) ? sanitize_key( (string) $context['tab_key'] ) : '',
		);
	}

	/**
	 * Get image assignment context for a product regardless of page template context.
	 *
	 * @param int $product_id Product ID.
	 * @return array<string,mixed>
	 */
	public function get_variant_image_context( $product_id ) {
		$context = $this->get_ab_variant_context_for_product( $product_id );

		return array(
			'product_id'             => isset( $context['product_id'] ) ? absint( $context['product_id'] ) : absint( $product_id ),
			'has_test'               => ! empty( $context['has_test'] ),
			'test_id'                => isset( $context['test_id'] ) ? sanitize_key( (string) $context['test_id'] ) : '',
			'variant'                => isset( $context['variant'] ) ? sanitize_key( (string) $context['variant'] ) : 'original',
			'can_override_images'    => ! empty( $context['can_override_images'] ),
			'featured_attachment_id' => isset( $context['featured_attachment_id'] ) ? absint( $context['featured_attachment_id'] ) : 0,
			'gallery_attachment_ids' => isset( $context['gallery_attachment_ids'] ) && is_array( $context['gallery_attachment_ids'] )
				? array_values( array_filter( array_map( 'absint', $context['gallery_attachment_ids'] ) ) )
				: array(),
		);
	}

	/**
	 * Resolve reviews widget layout type for product based on running Gallery A/B test assignment.
	 *
	 * @param int    $product_id Product ID.
	 * @param string $fallback_widget_type Default/global widget type.
	 * @return string
	 */
	public function resolve_widget_type( $product_id, $fallback_widget_type ) {
		$fallback_widget_type = sanitize_key( (string) $fallback_widget_type );
		if ( ! in_array( $fallback_widget_type, array( 'grid', 'masonry', 'slider', 'list', 'reels', 'gallery' ), true ) ) {
			$fallback_widget_type = 'grid';
		}

		if ( ! $this->is_single_product_context() ) {
			return $fallback_widget_type;
		}

		$product_id   = absint( $product_id );
		$running_test = $this->get_running_gallery_test_for_product( $product_id );

		if ( empty( $running_test ) ) {
			return $fallback_widget_type;
		}

		$assigned_variant = $this->resolve_assigned_variant( $running_test, $product_id );
		if ( 'ugcify' !== $assigned_variant ) {
			return $fallback_widget_type;
		}

		$variation_defaults = isset( $running_test['variation_defaults'] ) && is_array( $running_test['variation_defaults'] )
			? $running_test['variation_defaults']
			: array();

		$distribution_mode = sanitize_key( isset( $variation_defaults['gallery_distribution_mode'] ) ? (string) $variation_defaults['gallery_distribution_mode'] : 'global' );
		if ( 'custom' !== $distribution_mode ) {
			return $fallback_widget_type;
		}

		$gallery_layout = sanitize_key( isset( $variation_defaults['gallery_layout'] ) ? (string) $variation_defaults['gallery_layout'] : '' );
		if ( in_array( $gallery_layout, array( 'grid', 'masonry', 'slider' ), true ) ) {
			return $gallery_layout;
		}

		return $fallback_widget_type;
	}

	/**
	 * Resolve UGC section position for product based on running Position A/B test assignment.
	 *
	 * @param int    $product_id Product ID.
	 * @param string $fallback_position Default position ('default').
	 * @return string
	 */
	public function resolve_ugcify_position( $product_id, $fallback_position = 'default' ) {
		$fallback_position = sanitize_key( (string) $fallback_position );
		if ( ! in_array( $fallback_position, array( 'default', 'before_related_products', 'after_related_products' ), true ) ) {
			$fallback_position = 'default';
		}

		if ( ! $this->is_single_product_context() ) {
			return $fallback_position;
		}

		$product_id   = absint( $product_id );
		$running_test = $this->get_running_position_test_for_product( $product_id );

		if ( empty( $running_test ) ) {
			return $fallback_position;
		}

		$assigned_variant = $this->resolve_assigned_variant( $running_test, $product_id );
		if ( 'ugcify' !== $assigned_variant ) {
			return $fallback_position;
		}

		$variation_defaults = isset( $running_test['variation_defaults'] ) && is_array( $running_test['variation_defaults'] )
			? $running_test['variation_defaults']
			: array();

		$custom_position = sanitize_key( isset( $variation_defaults['ugcify_position'] ) ? (string) $variation_defaults['ugcify_position'] : '' );
		if ( in_array( $custom_position, array( 'before_related_products', 'after_related_products' ), true ) ) {
			return $custom_position;
		}

		return $fallback_position;
	}

	/**
	 * Get runtime AB context for a product.
	 *
	 * @param int $product_id Product ID.
	 * @return array<string,mixed>
	 */
	private function get_ab_variant_context_for_product( $product_id ) {
		$product_id = absint( $product_id );

		if ( isset( $this->ab_runtime_context[ $product_id ] ) ) {
			return $this->ab_runtime_context[ $product_id ];
		}

		$context = array(
			'product_id'             => $product_id,
			'has_test'               => false,
			'test_id'                => '',
			'variant'                => 'original',
			'tab_key'                => '',
			'can_override_images'    => false,
			'featured_attachment_id' => 0,
			'gallery_attachment_ids' => array(),
		);

		// Avoid AB assignment writes in wp-admin screens, but keep AJAX/REST requests supported.
		if ( is_admin() && ! wp_doing_ajax() && ! ( function_exists( 'wp_is_json_request' ) && wp_is_json_request() ) ) {
			$this->ab_runtime_context[ $product_id ] = $context;
			return $context;
		}

		if ( $product_id < 1 ) {
			$this->ab_runtime_context[ $product_id ] = $context;
			return $context;
		}

		$running_test = $this->get_running_test_for_product( $product_id );
		if ( empty( $running_test ) ) {
			$this->ab_runtime_context[ $product_id ] = $context;
			return $context;
		}

		$is_image_test = $this->is_image_test( $running_test );
		$context['tab_key'] = isset( $running_test['tab_key'] )
			? sanitize_key( (string) $running_test['tab_key'] )
			: '';

		$context['has_test'] = true;
		$context['test_id']  = isset( $running_test['id'] ) ? (string) $running_test['id'] : '';
		$context['variant']  = $this->resolve_assigned_variant( $running_test, $product_id );

		if ( ! $is_image_test ) {
			$this->ab_runtime_context[ $product_id ] = $context;
			return $context;
		}

		if ( 'ugcify' !== $context['variant'] ) {
			$this->ab_runtime_context[ $product_id ] = $context;
			return $context;
		}

		$variation_defaults = $this->normalize_variation_defaults( $running_test );
		$variation2_images  = isset( $variation_defaults['variation2_images'] ) && is_array( $variation_defaults['variation2_images'] )
			? $variation_defaults['variation2_images']
			: array();

		if ( empty( $variation2_images ) ) {
			$this->ab_runtime_context[ $product_id ] = $context;
			return $context;
		}

		$featured_url = isset( $variation2_images[0]['url'] ) ? esc_url_raw( (string) $variation2_images[0]['url'] ) : '';
		$featured_id  = $featured_url ? absint( attachment_url_to_postid( $featured_url ) ) : 0;

		if ( $featured_id < 1 ) {
			$this->ab_runtime_context[ $product_id ] = $context;
			return $context;
		}

		$gallery_ids = array();
		if ( count( $variation2_images ) > 1 ) {
			foreach ( array_slice( $variation2_images, 1 ) as $variation_image ) {
				$image_url = '';
				if ( is_array( $variation_image ) && ! empty( $variation_image['url'] ) ) {
					$image_url = esc_url_raw( (string) $variation_image['url'] );
				}

				if ( '' === $image_url ) {
					continue;
				}

				$attachment_id = absint( attachment_url_to_postid( $image_url ) );
				if ( $attachment_id > 0 ) {
					$gallery_ids[] = $attachment_id;
				}
			}
		}

		$context['featured_attachment_id'] = $featured_id;
		$context['gallery_attachment_ids'] = array_values( array_unique( $gallery_ids ) );
		$context['can_override_images']    = true;

		$this->ab_runtime_context[ $product_id ] = $context;
		return $context;
	}

	/**
	 * Resolve currently running A/B test (image or gallery) for a product.
	 *
	 * @param int $product_id Product ID.
	 * @return array
	 */
	private function get_running_test_for_product( $product_id ) {
		$storage = get_option(
			self::AB_TESTS_OPTION_NAME,
			array(
				'items' => array(),
			)
		);

		$items      = isset( $storage['items'] ) && is_array( $storage['items'] ) ? $storage['items'] : array();
		$product_id = absint( $product_id );

		foreach ( $items as $item ) {
			if ( ! is_array( $item ) ) {
				continue;
			}

			$item_product_id = isset( $item['product_id'] ) ? absint( $item['product_id'] ) : 0;
			$item_status     = isset( $item['status'] ) ? sanitize_key( (string) $item['status'] ) : '';

			if ( $item_product_id !== $product_id || 'running' !== $item_status ) {
				continue;
			}

			return $item;
		}

		return array();
	}

	/**
	 * Determine whether a stored test payload is an image test.
	 *
	 * @param array $item Stored test payload.
	 * @return bool
	 */
	private function is_image_test( array $item ) {
		$item_tab_key   = isset( $item['tab_key'] ) ? sanitize_key( (string) $item['tab_key'] ) : '';
		$item_test_type = isset( $item['test_type'] ) ? strtolower( sanitize_text_field( (string) $item['test_type'] ) ) : '';

		return 'image' === $item_tab_key || 'image' === $item_test_type;
	}

	/**
	 * Check whether current request is a single product page.
	 *
	 * @return bool
	 */
	private function is_single_product_context() {
		return ! is_admin() && function_exists( 'is_product' ) && is_product();
	}

	/**
	 * Check whether current request supports featured image AB replacement.
	 *
	 * @return bool
	 */
	private function is_featured_image_context() {
		if ( is_admin() ) {
			return false;
		}

		if ( $this->is_single_product_context() ) {
			return true;
		}

		return ( function_exists( 'is_shop' ) && is_shop() )
			|| ( function_exists( 'is_product_taxonomy' ) && is_product_taxonomy() );
	}

	/**
	 * Resolve currently running gallery A/B test for a product.
	 *
	 * @param int $product_id Product ID.
	 * @return array
	 */
	private function get_running_gallery_test_for_product( $product_id ) {
		$storage = get_option(
			self::AB_TESTS_OPTION_NAME,
			array(
				'items' => array(),
			)
		);

		$items      = isset( $storage['items'] ) && is_array( $storage['items'] ) ? $storage['items'] : array();
		$product_id = absint( $product_id );

		foreach ( $items as $item ) {
			if ( ! is_array( $item ) ) {
				continue;
			}

			$item_product_id  = isset( $item['product_id'] ) ? absint( $item['product_id'] ) : 0;
			$item_status      = isset( $item['status'] ) ? sanitize_key( (string) $item['status'] ) : '';
			$item_tab_key     = isset( $item['tab_key'] ) ? sanitize_key( (string) $item['tab_key'] ) : '';
			$item_test_type   = isset( $item['test_type'] ) ? strtolower( sanitize_text_field( (string) $item['test_type'] ) ) : '';
			$is_gallery_test  = 'gallery' === $item_tab_key || 'layout' === $item_test_type;

			if ( $item_product_id !== $product_id || 'running' !== $item_status || ! $is_gallery_test ) {
				continue;
			}

			return $item;
		}

		return array();
	}

	/**
	 * Resolve currently running position A/B test for a product.
	 *
	 * @param int $product_id Product ID.
	 * @return array
	 */
	private function get_running_position_test_for_product( $product_id ) {
		$storage = get_option(
			self::AB_TESTS_OPTION_NAME,
			array(
				'items' => array(),
			)
		);

		$items      = isset( $storage['items'] ) && is_array( $storage['items'] ) ? $storage['items'] : array();
		$product_id = absint( $product_id );

		foreach ( $items as $item ) {
			if ( ! is_array( $item ) ) {
				continue;
			}

			$item_product_id = isset( $item['product_id'] ) ? absint( $item['product_id'] ) : 0;
			$item_status     = isset( $item['status'] ) ? sanitize_key( (string) $item['status'] ) : '';
			$item_tab_key    = isset( $item['tab_key'] ) ? sanitize_key( (string) $item['tab_key'] ) : '';
			$item_test_type  = isset( $item['test_type'] ) ? strtolower( sanitize_text_field( (string) $item['test_type'] ) ) : '';
			$is_position_test = 'ugcify-position' === $item_tab_key || 'position' === $item_test_type;

			if ( $item_product_id !== $product_id || 'running' !== $item_status || ! $is_position_test ) {
				continue;
			}

			return $item;
		}

		return array();
	}

	/**
	 * Normalize variation defaults for backward compatibility.
	 *
	 * @param array $item Stored A/B item.
	 * @return array
	 */
	private function normalize_variation_defaults( array $item ) {
		$variation_defaults = isset( $item['variation_defaults'] ) && is_array( $item['variation_defaults'] )
			? $item['variation_defaults']
			: array();

		$normalized_images = array();
		$seen              = array();

		if ( isset( $variation_defaults['variation2_images'] ) && is_array( $variation_defaults['variation2_images'] ) ) {
			foreach ( $variation_defaults['variation2_images'] as $variation_image ) {
				if ( is_array( $variation_image ) && ! empty( $variation_image['url'] ) ) {
					$image_url = esc_url_raw( (string) $variation_image['url'] );
					$source    = isset( $variation_image['source'] ) ? sanitize_key( (string) $variation_image['source'] ) : '';
				} elseif ( is_string( $variation_image ) ) {
					$image_url = esc_url_raw( $variation_image );
					$source    = '';
				} else {
					$image_url = '';
					$source    = '';
				}

				if ( '' === $image_url || isset( $seen[ $image_url ] ) ) {
					continue;
				}

				$seen[ $image_url ]  = true;
				$normalized_images[] = array(
					'url'    => $image_url,
					'source' => in_array( $source, array( 'ugcify', 'product' ), true ) ? $source : '',
				);
			}
		}

		if ( empty( $normalized_images ) && ! empty( $variation_defaults['ugcify_image'] ) ) {
			$legacy_image = esc_url_raw( (string) $variation_defaults['ugcify_image'] );
			if ( '' !== $legacy_image ) {
				$normalized_images[] = array(
					'url'    => $legacy_image,
					'source' => 'ugcify',
				);
			}
		}

		$variation_defaults['variation2_images'] = $normalized_images;

		if ( ! empty( $normalized_images ) && empty( $variation_defaults['ugcify_image'] ) ) {
			$variation_defaults['ugcify_image'] = $normalized_images[0]['url'];
		}

		return $variation_defaults;
	}

	/**
	 * Resolve AB variant assignment for current visitor and persist it in a cookie.
	 *
	 * @param array $test Test payload.
	 * @param int   $product_id Product ID.
	 * @return string
	 */
	private function resolve_assigned_variant( array $test, $product_id ) {
		$test_id   = isset( $test['id'] ) ? sanitize_key( (string) $test['id'] ) : '';
		$cookie_id = self::AB_COOKIE_PREFIX . $test_id . '_' . absint( $product_id );

		if ( isset( $_COOKIE[ $cookie_id ] ) ) {
			$stored_variant = sanitize_key( wp_unslash( $_COOKIE[ $cookie_id ] ) );
			if ( in_array( $stored_variant, array( 'original', 'ugcify' ), true ) ) {
				return $stored_variant;
			}
		}

		$distribution = isset( $test['traffic_distribution'] ) && is_array( $test['traffic_distribution'] )
			? $test['traffic_distribution']
			: array();

		$ugcify_share = isset( $distribution['ugcify'] ) ? (int) $distribution['ugcify'] : 50;
		$ugcify_share = max( 0, min( 100, $ugcify_share ) );

		$bucket  = random_int( 1, 100 );
		$variant = $bucket <= $ugcify_share ? 'ugcify' : 'original';
		
		$this->set_assignment_cookie( $cookie_id, $variant );

		return $variant;
	}

	/**
	 * Set storefront assignment cookie.
	 *
	 * @param string $cookie_key Cookie key.
	 * @param string $variant Variant value.
	 * @return void
	 */
	private function set_assignment_cookie( $cookie_key, $variant ) {
		$expires = time() + self::AB_COOKIE_TTL;

		if ( headers_sent() ) {
			$_COOKIE[ $cookie_key ] = $variant;
			return;
		}

		setcookie( $cookie_key, $variant, $expires, COOKIEPATH ? COOKIEPATH : '/', COOKIE_DOMAIN, is_ssl(), true );
		if ( COOKIEPATH !== SITECOOKIEPATH ) {
			setcookie( $cookie_key, $variant, $expires, SITECOOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
		}

		$_COOKIE[ $cookie_key ] = $variant;
	}
}
