<?php

/**
 * Handles the frontend AJAX endpoints used by the inline upsell widget - adding to cart, live price previews, and dynamic widget re-rendering.
 *
 * @link       https://nudgesells.com
 * @since      1.0.0
 *
 * @package    NudgeSells
 * @subpackage NudgeSells/includes
 */

if (! defined('ABSPATH')) {
	exit;
}

/**
 * NudgeSells_Ajax
 *
 * Registers and handles the wc_ajax endpoints the storefront widget calls for add-to-cart, price preview, and re-rendering.
 *
 * @since      1.0.0
 * @package    NudgeSells
 * @subpackage NudgeSells/includes
 * @author     NudgeSells <support@nudgesells.com>
 */
class NudgeSells_Ajax
{

	/**
	 * Verify.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	private function verify()
	{
		check_ajax_referer('nudgesells_frontend', 'nonce');
	}

	/**
	 * Add to cart.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function add_to_cart()
	{
		$this->verify();

		$host_id      = absint($_POST['host_id'] ?? 0);
		$host_qty     = max(1, absint($_POST['host_qty'] ?? 1));
		$host_variation_id = absint($_POST['host_variation_id'] ?? 0);
		$host_variation_attrs_raw = isset($_POST['host_variation_attributes']) ? wp_unslash($_POST['host_variation_attributes']) : '{}';
		$host_variation_attributes = json_decode($host_variation_attrs_raw, true);
		if (! is_array($host_variation_attributes)) {
			$host_variation_attributes = array();
		}
		$host_variation_attributes = array_map('sanitize_text_field', $host_variation_attributes);
		$upgrade_id   = absint($_POST['upgrade_id'] ?? 0);
		$upgrade_variation_id = absint($_POST['upgrade_variation_id'] ?? 0);
		$upgrade_variation_attrs_raw = isset($_POST['upgrade_variation_attributes']) ? wp_unslash($_POST['upgrade_variation_attributes']) : '{}';
		$upgrade_variation_attributes = json_decode($upgrade_variation_attrs_raw, true);
		if (! is_array($upgrade_variation_attributes)) {
			$upgrade_variation_attributes = array();
		}
		$upgrade_variation_attributes = array_map('sanitize_text_field', $upgrade_variation_attributes);
		$addons_raw   = isset($_POST['addons']) ? wp_unslash($_POST['addons']) : '[]';
		$addons       = json_decode($addons_raw, true);
		if (! is_array($addons)) {
			$addons = array();
		}

		$host_product = wc_get_product($host_id);
		if (! $host_product) {
			wp_send_json_error(array('message' => __('Invalid product.', 'nudgesells')));
		}

		if ($host_product->is_type(array('variable', 'variable-subscription')) && ! $upgrade_id) {
			if (! $host_variation_id) {
				wp_send_json_error(array('message' => __('Please choose product options before adding to cart.', 'nudgesells')));
			}
			$variation_check = wc_get_product($host_variation_id);
			if (! $variation_check || $variation_check->get_parent_id() !== $host_id) {
				wp_send_json_error(array('message' => __('That product option is no longer available.', 'nudgesells')));
			}
		} else {
			$host_variation_id = 0;
			$host_variation_attributes = array();
		}

		if ($upgrade_id) {
			$upgrade_product_check = wc_get_product($upgrade_id);
			if ($upgrade_product_check && $upgrade_product_check->is_type(array('variable', 'variable-subscription'))) {
				if (! $upgrade_variation_id) {
					wp_send_json_error(array('message' => __('Please choose options for the upgrade before adding to cart.', 'nudgesells')));
				}
				$upgrade_variation_check = wc_get_product($upgrade_variation_id);
				if (! $upgrade_variation_check || $upgrade_variation_check->get_parent_id() !== $upgrade_id) {
					wp_send_json_error(array('message' => __('That upgrade option is no longer available.', 'nudgesells')));
				}
			} else {
				$upgrade_variation_id = 0;
				$upgrade_variation_attributes = array();
			}
		} else {
			$upgrade_variation_id = 0;
			$upgrade_variation_attributes = array();
		}

		$rules = NudgeSells_Rules_Repository::get_rules($host_id, $host_variation_id);

		if ($rules['offer_id'] && ! NudgeSells_Offer_Repository::meets_min_qty($rules['offer_id'], $host_qty)) {
			$rules['addons']   = array();
			$rules['upgrades'] = array();
		}

		$errors = array();

		$addon_rules_by_id = array();
		foreach ($rules['addons'] as $r) {
			$addon_rules_by_id[$r['product_id']] = $r;
		}

		$desired_role       = $upgrade_id ? 'upgrade' : 'host';
		$desired_product_id = $upgrade_id ? $upgrade_id : $host_id;
		$desired_variation  = $upgrade_id ? $upgrade_variation_id : $host_variation_id;
		$desired_addon_signature = $this->get_requested_addon_signature($addons, $addon_rules_by_id);

		$candidate_keys = $this->find_all_existing_lines(array(
			'nudgesells_host_id' => $host_id,
			'nudgesells_role'    => array('host', 'upgrade'),
		));

		$existing_main_key = null;
		foreach ($candidate_keys as $key) {
			$candidate_group = WC()->cart->cart_contents[$key]['nudgesells_group'];
			if ($this->get_group_addon_signature($candidate_group) === $desired_addon_signature) {
				$existing_main_key = $key;
				break;
			}
		}

		$reuse_existing = false;
		if ($existing_main_key) {
			$existing_item  = WC()->cart->cart_contents[$existing_main_key];
			$same_role      = $existing_item['nudgesells_role'] === $desired_role;
			$same_product   = (int) $existing_item['product_id'] === (int) $desired_product_id;
			$same_variation = (int) ($existing_item['variation_id'] ?? 0) === (int) $desired_variation;
			$reuse_existing = $same_role && $same_product && $same_variation;
		}

		if ($reuse_existing) {

			$group_id = WC()->cart->cart_contents[$existing_main_key]['nudgesells_group'];
			$new_qty  = WC()->cart->cart_contents[$existing_main_key]['quantity'] + $host_qty;
			WC()->cart->set_quantity($existing_main_key, $new_qty, false);
		} else {
			if ($existing_main_key) {

				$group_id = WC()->cart->cart_contents[$existing_main_key]['nudgesells_group'];
				NudgeSells_Cart_Pricing::$suppress_cascade = true;
				WC()->cart->remove_cart_item($existing_main_key);
				NudgeSells_Cart_Pricing::$suppress_cascade = false;
			} else {
				$group_id = 'nsg_' . wp_generate_password(12, false);
			}

			if ($upgrade_id) {
				$upgrade_rule = null;
				foreach ($rules['upgrades'] as $r) {
					if ((int) $r['product_id'] === $upgrade_id) {
						$upgrade_rule = $r;
						break;
					}
				}
				if (! $upgrade_rule) {
					wp_send_json_error(array('message' => __('That upgrade option is no longer available.', 'nudgesells')));
				}
				$added = WC()->cart->add_to_cart($upgrade_id, $host_qty, $upgrade_variation_id, $upgrade_variation_attributes, array(
					'nudgesells_group'               => $group_id,
					'nudgesells_role'                => 'upgrade',
					'nudgesells_host_id'             => $host_id,
					'nudgesells_upgrade_product_id'  => $upgrade_id,
					'nudgesells_upgrade_rule'        => $upgrade_rule,
				));
				if (! $added) {
					wp_send_json_error(array('message' => __('Could not add the upgraded product to your cart.', 'nudgesells')));
				}
			} else {
				$added = WC()->cart->add_to_cart($host_id, $host_qty, $host_variation_id, $host_variation_attributes, array(
					'nudgesells_group'   => $group_id,
					'nudgesells_role'    => 'host',
					'nudgesells_host_id' => $host_id,
				));
				if (! $added) {
					wp_send_json_error(array('message' => __('Could not add this product to your cart.', 'nudgesells')));
				}
			}
		}

		foreach ($addons as $addon) {
			$product_id   = absint($addon['product_id'] ?? 0);
			$variation_id = absint($addon['variation_id'] ?? 0);
			$qty          = max(1, absint($addon['qty'] ?? 1));

			if (! isset($addon_rules_by_id[$product_id])) {
				continue;
			}

			$product = wc_get_product($variation_id ? $variation_id : $product_id);
			if (! $product || ! $product->is_purchasable()) {
				/* translators: %s: product name */
				$errors[] = sprintf(__('"%s" is not currently available.', 'nudgesells'), $product ? $product->get_name() : $product_id);
				continue;
			}

			$existing_addon_key = $this->find_existing_line(array(
				'nudgesells_group'            => $group_id,
				'nudgesells_role'             => 'addon',
				'nudgesells_addon_product_id' => $product_id,
				'variation_id'         => $variation_id,
			));

			if ($existing_addon_key) {
				$new_qty = WC()->cart->cart_contents[$existing_addon_key]['quantity'] + $qty;
				WC()->cart->set_quantity($existing_addon_key, $new_qty, false);
				continue;
			}

			$variation_attributes = array();
			if ($variation_id) {
				$variation_obj = wc_get_product($variation_id);
				if ($variation_obj instanceof WC_Product_Variation) {

					foreach ($variation_obj->get_attributes() as $attr_key => $attr_value) {
						$variation_attributes['attribute_' . $attr_key] = $attr_value;
					}
				}
			}

			$added = WC()->cart->add_to_cart($product_id, $qty, $variation_id, $variation_attributes, array(
				'nudgesells_group'             => $group_id,
				'nudgesells_role'              => 'addon',
				'nudgesells_host_id'           => $host_id,
				'nudgesells_addon_product_id'  => $product_id,
				'nudgesells_addon_rule'        => $addon_rules_by_id[$product_id],
			));

			if (! $added) {
				/* translators: %s: product name */
				$errors[] = sprintf(__('Could not add "%s" to your cart.', 'nudgesells'), $product->get_name());
			}
		}

		WC()->cart->calculate_totals();

		if (! empty($errors)) {
			wp_send_json_error(array('message' => implode(' ', $errors)));
		}

		wp_send_json_success(array(
			'cart_hash'  => WC()->cart->get_cart_hash(),
			'cart_count' => WC()->cart->get_cart_contents_count(),
			'cart_url'   => wc_get_cart_url(),
		));
	}

	/**
	 * Find existing line.
	 *
	 * @param array $match
	 *
	 * @return mixed
	 * @since 1.0.0
	 */
	private function find_existing_line(array $match)
	{
		$keys = $this->find_all_existing_lines($match);
		return $keys ? $keys[0] : null;
	}

	/**
	 * Find all existing lines.
	 *
	 * @param array $match
	 *
	 * @return mixed
	 * @since 1.0.0
	 */
	private function find_all_existing_lines(array $match)
	{
		$keys = array();
		foreach (WC()->cart->get_cart() as $key => $item) {
			$is_match = true;
			foreach ($match as $field => $value) {
				$item_value = $item[$field] ?? null;
				if (is_array($value)) {
					if (! in_array($item_value, $value, true)) {
						$is_match = false;
						break;
					}
				} elseif ($item_value != $value) {
					$is_match = false;
					break;
				}
			}
			if ($is_match) {
				$keys[] = $key;
			}
		}
		return $keys;
	}

	/**
	 * Get group addon signature.
	 *
	 * @param mixed $group_id
	 *
	 * @return mixed
	 * @since 1.0.0
	 */
	private function get_group_addon_signature($group_id)
	{
		$parts = array();
		foreach (WC()->cart->get_cart() as $item) {
			if (($item['nudgesells_group'] ?? null) === $group_id && 'addon' === ($item['nudgesells_role'] ?? null)) {
				$parts[] = absint($item['nudgesells_addon_product_id'] ?? 0) . ':' . absint($item['variation_id'] ?? 0);
			}
		}
		sort($parts);
		return implode('|', $parts);
	}

	/**
	 * Get requested addon signature.
	 *
	 * @param array $addons
	 * @param array $addon_rules_by_id
	 *
	 * @return mixed
	 * @since 1.0.0
	 */
	private function get_requested_addon_signature(array $addons, array $addon_rules_by_id)
	{
		$parts = array();
		foreach ($addons as $addon) {
			$product_id = absint($addon['product_id'] ?? 0);
			if (! isset($addon_rules_by_id[$product_id])) {
				continue;
			}
			$variation_id = absint($addon['variation_id'] ?? 0);
			$parts[] = $product_id . ':' . $variation_id;
		}
		sort($parts);
		return implode('|', $parts);
	}

	/**
	 * Price preview.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function price_preview()
	{
		$this->verify();

		$host_id            = absint($_POST['host_id'] ?? 0);
		$host_qty           = max(1, absint($_POST['host_qty'] ?? 1));
		$host_variation_id  = absint($_POST['host_variation_id'] ?? 0);
		$upgrade_id           = absint($_POST['upgrade_id'] ?? 0);
		$upgrade_variation_id = absint($_POST['upgrade_variation_id'] ?? 0);
		$addons_raw = isset($_POST['addons']) ? wp_unslash($_POST['addons']) : '[]';
		$addons     = json_decode($addons_raw, true);
		if (! is_array($addons)) {
			$addons = array();
		}

		$rules = NudgeSells_Rules_Repository::get_rules($host_id, $host_variation_id);

		$addon_rules_by_id = array();
		foreach ($rules['addons'] as $r) {
			$addon_rules_by_id[$r['product_id']] = $r;
		}

		$selected = array();
		foreach ($addons as $i => $addon) {
			$product_id   = absint($addon['product_id'] ?? 0);
			$variation_id = absint($addon['variation_id'] ?? 0);
			$qty          = max(1, absint($addon['qty'] ?? 1));
			$product      = wc_get_product($variation_id ? $variation_id : $product_id);
			if (! $product) {
				continue;
			}
			$addon_rule = $addon_rules_by_id[$product_id] ?? null;
			$discount_value = $addon_rule['discount_value'] ?? 0;

			$resolved = NudgeSells_Pricing_Engine::resolve_effective_prices($product, $addon_rule['discount_type'] ?? 'percent', $discount_value);
			$selected['line_' . $i] = array(
				'product_id' => $product_id,
				'qty'        => $qty,
				'unit_price' => floatval($discount_value) > 0 ? $resolved['base'] : $resolved['final'],
			);
		}

		$result = NudgeSells_Pricing_Engine::calculate($rules, $selected);

		$main_original = 0.0;
		$main_final    = 0.0;
		$upgrade_product = null;
		$upgrade_rule     = null;

		if ($upgrade_id) {
			foreach ($rules['upgrades'] as $r) {
				if ((int) $r['product_id'] === $upgrade_id) {
					$upgrade_rule = $r;
					break;
				}
			}
			$upgrade_product = wc_get_product($upgrade_variation_id ? $upgrade_variation_id : $upgrade_id);
			if ($upgrade_rule && $upgrade_product) {
				$resolved       = NudgeSells_Pricing_Engine::resolve_effective_prices($upgrade_product, $upgrade_rule['discount_type'], $upgrade_rule['discount_value']);
				$main_original  = round($resolved['original'] * $host_qty, wc_get_price_decimals());
				$main_final     = round($resolved['final'] * $host_qty, wc_get_price_decimals());
			}
		} else {
			$host_product = wc_get_product($host_variation_id ? $host_variation_id : $host_id);
			if ($host_product) {
				$price          = (float) $host_product->get_price();
				$main_original  = round($price * $host_qty, wc_get_price_decimals());
				$main_final     = $main_original;
			}
		}

		$total          = $main_final;
		$total_original = $main_original;
		foreach ($result['lines'] as $line) {
			$total          += $line['line_total_final'];
			$total_original += round($line['unit_price_original'] * $line['qty'], wc_get_price_decimals());
		}
		$total_saved = max(0, $total_original - $total);

		$result['total_saved'] = $total_saved;
		$discount = NudgeSells_Pricing_Engine::evaluate_discount_message($rules, $result);
		$savings_note  = NudgeSells_Pricing_Engine::build_savings_note($rules, $result);
		$savings_note  = $this->apply_addon_count_display($savings_note, $result, $upgrade_id && $upgrade_rule ? $upgrade_product : null);

		wp_send_json_success(array(
			'total'                => $total,
			'total_display'        => wc_price($total),
			'total_original'       => $total_original,
			'total_original_display' => wc_price($total_original),
			'total_saved'          => $total_saved,
			'total_saved_display'  => wc_price($total_saved),
			'progress_note_html'   => $discount['message'],
			'savings_note_html'    => $savings_note,
			'discount_reached'     => $discount['reached'],
			'extra_discount_label' => $result['extra_discount_label'],
			'bundle_applied'       => $result['bundle_applied'],
			'lines'                => $result['lines'],
		));
	}

	/**
	 * Apply addon count display.
	 *
	 * Combines (or replaces) the savings note with a selection-count
	 * message, per the "Selection count" setting: hidden (unchanged),
	 * no_discount (count only shown when there's no savings note to
	 * show instead), or always (both together, count first). Counts
	 * both selected add-ons and a selected upgrade together.
	 *
	 * @param string          $savings_note
	 * @param array           $result
	 * @param WC_Product|null $upgrade_product A validly-selected upgrade product, if any.
	 *
	 * @return string
	 * @since 1.0.0
	 */
	private function apply_addon_count_display($savings_note, array $result, $upgrade_product = null)
	{
		$display_mode = NudgeSells_Admin_Settings::get('addon_count_display', 'no_discount');
		if ('hidden' === $display_mode) {
			return $savings_note;
		}

		$count = count($result['lines']) + ($upgrade_product ? 1 : 0);
		if ($count < 1) {
			return $savings_note;
		}

		if ('no_discount' === $display_mode) {
			if ($savings_note) {
				return $savings_note;
			}
		}

		$count_text = $this->build_addon_count_text($count, $result, $upgrade_product);

		if ('always' === $display_mode && $savings_note) {
			/* translators: 1: selection count text (e.g. "2 selected" or a single product's title), 2: savings message */
			return sprintf(__('%1$s - %2$s', 'nudgesells'), $count_text, $savings_note);
		}

		return $count_text;
	}

	/**
	 * Build addon count text.
	 *
	 * With exactly one add-on or upgrade selected, shows that product's
	 * own title rather than "1 selected" -- a specific product name
	 * reads more naturally than a generic count of one.
	 *
	 * @param int         $count
	 * @param array       $result
	 * @param WC_Product|null $upgrade_product
	 *
	 * @return string
	 * @since 1.0.0
	 */
	private function build_addon_count_text($count, array $result, $upgrade_product = null)
	{
		if (1 === $count) {
			if ($upgrade_product) {
				return $upgrade_product->get_name();
			}
			$line    = reset($result['lines']);
			$product = $line ? wc_get_product($line['product_id']) : null;
			if ($product) {
				return $product->get_name();
			}
		}

		$label = NudgeSells_Admin_Settings::get('addon_count_label', 'selected');
		/* translators: 1: number of add-ons/upgrades selected, 2: customizable label, e.g. "selected" or "add-ons" */
		return sprintf(__('%1$d %2$s', 'nudgesells'), $count, $label);
	}

	/**
	 * Render widget.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function render_widget()
	{
		$this->verify();

		$host_id           = absint($_POST['host_id'] ?? 0);
		$host_variation_id = absint($_POST['host_variation_id'] ?? 0);
		$host_qty          = max(1, absint($_POST['host_qty'] ?? 1));

		$is_initial_load   = ! empty($_POST['initial']);

		$html = NudgeSells_Public::get_widget_markup($host_id, $host_variation_id, $host_qty, $is_initial_load);

		wp_send_json_success(array('html' => $html));
	}
}
