<?php

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

/**
 * ExactMetrics PPC Tracking - WooCommerce tracking class.
 *
 * @access public
 * @since 9.7.0
 *
 * @package ExactMetrics_PPC_Tracking
 * @subpackage Ecommerce_Providers
 * @author  David Paternina
 */

class ExactMetrics_Ads_Tracking_Ecommerce_Woo extends ExactMetrics_Ads_Tracking_Ecommerce_Tracking {
	/**
	 * WooCommerce order
	 *
	 * @var $order WC_Order
	 */
	protected $order;

	/**
	 * Tracks cart item keys that have already had add_to_cart events sent
	 * in the current request to prevent duplicate tracking.
	 *
	 * @var array
	 */
	private $tracked_add_to_cart_items = array();

	/**
	 * @inheritdoc
	 */
	public function do_conversion_checks() {
		return function_exists( 'is_order_received_page' ) && is_order_received_page();
	}

	/**
	 * @inheritdoc
	 */
	public function is_active() {
		return class_exists( 'WooCommerce' ) && apply_filters( 'exactmetrics_ppc_tracking_track_conversion_woocommerce', true );
	}

	/**
	 * @inheritdoc
	 */
	public function try_to_get_order() {
		$order_id = absint( get_query_var( 'order-received' ) );

		if ( empty( $order_id ) ) {
			return false;
		}

		$this->order = wc_get_order( $order_id );
		return true;
	}

	/**
	 * @inheritDoc
	 */
	function get_order_total() {
		return empty( $this->order ) ? null : $this->order->get_total();
	}

	/**
	 * @inheritDoc
	 */
	function get_order_currency() {
		return empty( $this->order ) ? null : $this->order->get_currency();
	}

	/**
	 * @inheritDoc
	 */
	function get_order_number() {
		return empty( $this->order ) ? null : $this->order->get_id();
	}

	/**
	 * @inheritDoc
	 */
	public function already_tracked_frontend( $provider_id ) {
		if ( empty( $this->order ) ) {
			return true;
		}

		$tracked = $this->order->get_meta( "_exactmetrics_ppc_tracking_conversion_tracked_{$provider_id}" );

		return ! empty( $tracked ) && 'yes' === $tracked;
	}

	/**
	 * @inheritDoc
	 */
	public function mark_order_tracked_frontend( $provider_id ) {
		if ( empty( $this->order ) ) {
			return;
		}

		$this->order->add_meta_data( "_exactmetrics_ppc_tracking_conversion_tracked_{$provider_id}", 'yes', true );
		$this->order->save_meta_data();
	}

	/**
	 * @inheritdoc
	 */
	public function get_name() {
		return 'WooCommerce';
	}

	/**
	 * Get order meta specific to WooCommerce.
	 * Improved compatibility by not calling get_post_meta directly.
	 *
	 * @param int    $order_id The order to get meta for.
	 * @param string $key The key to get meta by.
	 *
	 * @return array|mixed|string
	 */
	public function get_order_meta( $order_id, $key ) {
		$order = wc_get_order( $order_id );

		return $order->get_meta( $key );
	}

	/**
	 * Store extra order data specific for WooCommerce.
	 *
	 * @param int   $order_id The order id to add meta to.
	 * @param array $data The array of key > value pairs to store.
	 *
	 * @return void
	 */
	public function store_extra_data( $order_id, $data ) {
		$order = wc_get_order( $order_id );

		foreach ( $data as $key => $value ) {
			$order->update_meta_data( $key, $value );
		}

		$order->save();
	}

	/**
	 * @return void
	 */
	public function add_server_hooks() {
		add_action( 'woocommerce_add_to_cart', [ $this, 'server_event_add_to_cart' ], 10, 6 );
		add_action( 'woocommerce_after_single_product_summary', [ $this, 'server_event_view_content' ] );
		add_action( 'woocommerce_after_checkout_form', [ $this, 'server_event_begin_checkout' ] );

		add_action('woocommerce_order_status_processing', [$this, 'server_event_purchase'], 10, 2);
		add_action('woocommerce_order_status_completed', [$this, 'server_event_purchase'], 10, 2);

		add_action('woocommerce_checkout_update_order_meta', [$this, 'server_order_saved'], 10, 2);
	}

	/**
	 * Send Add To Cart event to all ad providers
	 *
	 * @param $cart_id
	 * @param $product_id
	 * @param $request_quantity
	 * @param $variation_id
	 * @param $variation
	 * @param $cart_item_data
	 *
	 * @return void
	 * @throws Exception
	 */
	public function server_event_add_to_cart( $cart_id = '', $product_id = 0, $request_quantity = 0, $variation_id = 0, $variation = [], $cart_item_data = [] ) {
		// Prevent duplicate tracking for the same cart item in the same request.
		if ( ! empty( $cart_id ) && in_array( $cart_id, $this->tracked_add_to_cart_items, true ) ) {
			return;
		}
		if ( ! empty( $cart_id ) ) {
			$this->tracked_add_to_cart_items[] = $cart_id;
		}

		$product = $this->get_product($product_id, $variation_id);

		if ( empty( $product ) || is_wp_error( $product ) ) {
			return;
		}

		$data = [
			'product_id' => $product->get_id(),
			'quantity'   => $request_quantity,
			'name'       => $product->get_name(),
			'price'      => $product->get_price(),
			'categories' => $this->get_product_categories( $product ),
			'currency'   => $this->get_currency(),
			'user_ip'    => WC_Geolocation::get_ip_address(),
			'user_agent' => $this->get_user_agent()
		];

		$data = array_merge( $data, $this->get_server_user_data() );

		$this->send_event_to_ad_providers( 'add_to_cart', $data, $this );
	}

	/**
	 * Send View Content event to all ad providers
	 *
	 * @return void
	 * @throws Exception
	 */
	public function server_event_view_content() {
		// Don't do it on feeds
		if ( is_feed() ) {
			return;
		}

		$product_id = get_the_ID();

		$product = $this->get_product($product_id);

		$data = [
			'product_id' => $product->get_id(),
			'name'       => $product->get_name(),
			'price'      => $product->get_price(),
			'categories' => $this->get_product_categories( $product ),
			'currency'   => $this->get_currency(),
			'user_ip'    => WC_Geolocation::get_ip_address(),
			'user_agent' => $this->get_user_agent()
		];

		$data = array_merge( $data, $this->get_server_user_data() );

		$this->send_event_to_ad_providers( 'view_content', $data, $this );
	}

	/**
	 * Send Begin Checkout event to all ad providers
	 *
	 * @param $checkout
	 *
	 * @return void
	 * @throws Exception
	 */
	public function server_event_begin_checkout() {
		$data = $this->get_checkout_data();

		$user_data = [
			'user_ip'    => WC_Geolocation::get_ip_address(),
			'user_agent' => $this->get_user_agent()
		];

		$data = array_merge( $data, $user_data );
		$data = array_merge( $data, $this->get_server_user_data() );

		$this->send_event_to_ad_providers( 'begin_checkout', $data, $this );
	}

	/**
	 * Send Purchase event to all ad providers
	 * @param $order_id
	 * @param $order
	 *
	 * @return void
	 */
	public function server_event_purchase( $order_id, $order ) {
		$skip_renewals = apply_filters( 'exactmetrics_ppc_tracking_skip_renewals', true );
		if ( function_exists( 'wcs_order_contains_subscription' ) && wcs_order_contains_subscription( $order_id, 'renewal' ) && $skip_renewals ) {
			return;
		}

		// Build purchase event data from order.
		$data = [
			'order_id'   => $order_id,
			'num_items'  => $order->get_item_count(),
			'currency'   => $order->get_currency(),
			'total'      => $order->get_total(),
			'products'   => [],
			'user_id'    => $order->get_user_id(),
			'user_ip'    => $order->get_customer_ip_address(),
			'phone'      => $order->get_billing_phone(),
			'email'      => $order->get_billing_email(),
			'zip'        => $order->get_billing_postcode(),
			'city'       => $order->get_billing_city(),
			'state'      => $order->get_billing_state(),
			'country'    => $order->get_billing_country(),
			'user_agent' => $order->get_customer_user_agent(),
			'first_name' => $order->get_billing_first_name(),
			'last_name'  => $order->get_billing_last_name(),
		];

		foreach ( $order->get_items() as $item ) {
			$product = $item->get_product();

			if ( empty( $product ) || is_wp_error( $product ) ) {
				continue;
			}

			$data['products'][] = [
				'id'         => $product->get_id(),
				'quantity'   => $item->get_quantity(),
				'name'       => $product->get_name(),
				'discount'   => $item->get_subtotal() - $item->get_total(),
				'price'      => $product->get_price(),
				'categories' => $this->get_product_categories( $product ),
			];
		}

		$this->send_event_to_ad_providers( 'purchase', $data, $this );
	}

	/**
	 * Get the product object.
	 * @param $product_id
	 * @param $variation_id
	 *
	 * @return false|WC_Product|null
	 */
	public function get_product($product_id = 0, $variation_id = 0) {
		if ( ! empty( $variation_id ) ) {
			$product = wc_get_product( $variation_id );
		} else {
			$product = wc_get_product( $product_id );
		}

		return $product;
	}

	/**
	 * Get checkout data specific to WooCommerce.
	 *
	 * @return array
	 */
	public function get_checkout_data() {
		if ( ! function_exists( 'wc_get_product' ) ) {
			return [];
		}

		$data = [
			'num_items' => WC()->cart->get_cart_contents_count(),
			'currency'  => $this->get_currency(),
			'total'     => WC()->cart->get_cart_contents_total(),
			'products'  => [],
		];

		$coupon = WC()->cart->get_applied_coupons();
		if ( ! empty( $coupon ) && ! empty( $coupon[0] ) ) {
			$data['coupon'] = $coupon[0];
		}

		foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
			/**
			 * @var WC_Product $product
			 */
			$product = $cart_item['data'];

			$data['products'][] = [
				'id'         => $product->get_id(),
				'quantity'   => $cart_item['quantity'],
				'name'       => $product->get_name(),
				'discount'   => $cart_item['line_subtotal'] - $cart_item['line_total'],
				'price'      => $product->get_price(),
				'categories' => $this->get_product_categories( $product ),
			];
		}

		return $data;
	}

	/**
	 * Get the product categories.
	 *
	 * @param WC_Product $product The product to grab the categories from.
	 *
	 * @return array
	 */
	public function get_product_categories( $product ) {
		$product_categories = $product->get_category_ids();

		if ( empty( $product_categories ) ) {
			return [];
		}

		$categories = [];
		foreach ( $product_categories as $category_id ) {
			$category = get_term( $category_id );
			if ( ! empty( $category ) && ! is_wp_error( $category ) ) {
				$categories[] = $category->name;
			}
		}

		return $categories;
	}

	/**
	 * Get the WOO currency.
	 *
	 * @return string
	 */
	public function get_currency() {
		return get_woocommerce_currency();
	}

	/**
	 * @inheritdoc
	 */
	public function get_user_agent() {
		return wc_get_user_agent();
	}

	/**
	 * @inheritdoc
	 * @throws Exception
	 */
	public function get_server_user_data() {
		$data = [];

		if ( is_user_logged_in() ) {
			$customer           = new WC_Customer( get_current_user_id() );

			if ( empty( $customer ) || is_wp_error( $customer ) ) {
				return [];
			}

			$data['user_id']    = get_current_user_id();
			$data['email']      = $customer->get_billing_email();
			$data['city']       = $customer->get_billing_city();
			$data['state']      = $customer->get_billing_state();
			$data['country']    = $customer->get_billing_country();
			$data['zip']        = $customer->get_billing_postcode();
			$data['first_name'] = $customer->get_billing_first_name();
			$data['last_name']  = $customer->get_billing_last_name();
			$data['phone']      = $customer->get_billing_phone();
		}

		return $data;
	}

	/**
	 * @inheritDoc
	 */
	public function get_order_customer_info( $order_id ) {
		if ( empty( $this->order ) || ! $this->try_to_get_order() || ! method_exists($this->order, 'get_billing_email') ) {
			return [];
		}

		//  Order was loaded, or we were able to load just above.

		//  We need at least the email even if the user is not logged in (i.e. checked out as guest)
		$email = $this->order->get_billing_email();

		if ( empty( $email ) ) {
			return [];
		}

		$data = [
			'email'         => $email,
			'first_name'    => $this->order->get_billing_first_name(),
			'last_name'     => $this->order->get_billing_last_name(),
			'phone_number'  => $this->order->get_billing_phone(),
			'address'       => [
				'street'        => $this->order->get_billing_address_1(),
				'city'          => $this->order->get_billing_city(),
				'region'        => $this->order->get_billing_state(),
				'postal_code'   => $this->order->get_billing_postcode(),
				'country'       => $this->order->get_billing_country(),
			]
		];

		return $data;
	}
}
