<?php
/**
 * File to tracking EDD functionality.
 */

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

/**
 * Class to handle EDD tracking.
 */
class SimpleStats_Tracking_EDD extends SimpleStats_Ecommerce_Tracking {

	/**
	 * Add hooks to the method.
	 */
	protected function hooks() {
		// Add Order hooks.
		add_action( 'edd_update_payment_status', array( $this, 'add_order' ), 10, 3 );

		// Refund hook (EDD 3.0+).
		add_action( 'edd_refund_order', array( $this, 'add_refund' ), 10, 3 );

		// Capture the visitor session onto the order at checkout (EDD 3.0+) so
		// attribution survives a later admin-side status change.
		add_action( 'edd_built_order', array( $this, 'store_session' ), 10, 1 );

		// Add-to-cart event.
		add_action( 'edd_post_add_to_cart', array( $this, 'add_to_cart' ), 10, 3 );

		// Reached-checkout event (EDD checkout page).
		add_action( 'template_redirect', array( $this, 'reached_checkout' ) );
	}

	/**
	 * Reached-checkout callback. Fires on the EDD checkout page and sends a
	 * non-blocking begin_checkout event for the funnel. Skipped on the success
	 * (confirmation) page and when the cart is empty.
	 */
	public function reached_checkout() {
		if ( ! function_exists( 'edd_is_checkout' ) || ! edd_is_checkout() ) {
			return;
		}

		if ( function_exists( 'edd_is_success_page' ) && edd_is_success_page() ) {
			return;
		}

		if ( function_exists( 'edd_get_cart_quantity' ) && edd_get_cart_quantity() < 1 ) {
			return;
		}

		$this->send_request(
			array(
				'type'     => 'begin_checkout',
				'checkout' => array(
					'platform'    => 'edd',
					'occurred_at' => gmdate( 'c' ),
				),
			)
		);
	}

	/**
	 * Purchase order callback.
	 *
	 * @param $payment_id int
	 * @param $new_status string
	 */
	public function add_order( $payment_id, $new_status, $old_status ) {
		// If not a completed or published payment, then return.
		if ( 'publish' !== $new_status && 'edd_subscription' !== $new_status && 'complete' !== $new_status ) {
			return;
		}

		$skip_renewals = apply_filters( 'simplestats_ecommerce_skip_renewals', true );
		if ( 'edd_subscription' === $new_status && $skip_renewals ) {
			return;
		}

		$meta = edd_get_order_meta( $payment_id, '_simplestats_is_in_app', true );

		if ( 'yes' === $meta ) {
			return;
		}

		$transaction = $this->build_transaction( $payment_id );
		if ( $transaction ) {
			$this->send_request(
				array(
					'type'        => 'transaction',
					'transaction' => $transaction,
					'session'     => $this->session_from_order( $payment_id ),
				)
			);
		}

		edd_add_order_meta( $payment_id, '_simplestats_is_in_app', 'yes' );
	}

	/**
	 * Refund callback (EDD 3.0+).
	 *
	 * @param int  $order_id     Parent order id.
	 * @param int  $refund_id    Refund id.
	 * @param bool $all_refunded Whether the whole order was refunded.
	 */
	public function add_refund( $order_id, $refund_id, $all_refunded = false ) {
		if ( ! function_exists( 'edd_get_order' ) ) {
			return;
		}

		$order  = edd_get_order( $order_id );
		$refund = edd_get_order( $refund_id );
		if ( ! $order || ! $refund ) {
			return;
		}

		$transaction = array(
			'external_id'        => (string) $refund_id,
			'parent_external_id' => (string) $order_id,
			'platform'           => 'edd',
			'kind'               => 'refund',
			'status'             => 'refunded',
			'currency'           => $order->currency,
			'value'              => (float) abs( $refund->total ),
			'customer_hash'      => $this->hash_email( $order->email ),
			'occurred_at'        => gmdate( 'c' ),
			'items'              => array(),
		);

		$this->send_request(
			array(
				'type'        => 'transaction',
				'transaction' => $transaction,
				'session'     => $this->session_from_order( $order_id ),
			)
		);
	}

	/**
	 * Add-to-cart callback. Sends a non-blocking cart_event to the app.
	 *
	 * @param int   $download_id Download id.
	 * @param array $options     Cart item options (may contain price_id, quantity).
	 * @param array $items       Full cart items array.
	 */
	public function add_to_cart( $download_id, $options, $items ) {
		$price_id = ( isset( $options['price_id'] ) && '' !== $options['price_id'] ) ? $options['price_id'] : null;

		list( $external_item_id, $name, $variant ) = $this->item_identity( $download_id, $price_id );

		$quantity = isset( $options['quantity'] ) ? (int) $options['quantity'] : 1;
		if ( $quantity < 1 ) {
			$quantity = 1;
		}

		$this->send_request(
			array(
				'type' => 'cart_event',
				'cart' => array(
					'platform'         => 'edd',
					'external_item_id' => $external_item_id,
					'name'             => $name,
					'variant'          => $variant,
					'quantity'         => $quantity,
					'occurred_at'      => gmdate( 'c' ),
				),
			)
		);
	}

	/**
	 * Capture the visitor session UUIDs onto the order at checkout time.
	 *
	 * @param int $order_id Order id (EDD 3.0).
	 */
	public function store_session( $order_id ) {
		$session = $this->read_session_cookies();
		if ( empty( $session ) || ! function_exists( 'edd_update_order_meta' ) ) {
			return;
		}

		edd_update_order_meta( $order_id, self::META_VISITOR_UUID, $session['visitor_uuid'] );
		edd_update_order_meta( $order_id, self::META_SESSION_UUID, $session['session_uuid'] );
		if ( ! empty( $session['event_uuid'] ) ) {
			edd_update_order_meta( $order_id, self::META_EVENT_UUID, $session['event_uuid'] );
		}
	}

	/**
	 * Read the session UUIDs previously captured on an order.
	 *
	 * @param int $order_id Order id (or parent order id, for refunds).
	 * @return array Empty when no session was captured.
	 */
	private function session_from_order( $order_id ) {
		if ( ! function_exists( 'edd_get_order_meta' ) ) {
			return array();
		}

		$visitor = edd_get_order_meta( $order_id, self::META_VISITOR_UUID, true );
		if ( empty( $visitor ) ) {
			return array();
		}

		return array(
			'visitor_uuid' => $visitor,
			'session_uuid' => edd_get_order_meta( $order_id, self::META_SESSION_UUID, true ),
			'event_uuid'   => edd_get_order_meta( $order_id, self::META_EVENT_UUID, true ),
		);
	}

	/**
	 * Resolve the variant-unique identity for an EDD download + price option.
	 *
	 * @param int      $download_id Download id.
	 * @param int|null $price_id    Price option id, or null for a single-price download.
	 * @return array{0:string,1:string,2:string|null} [external_item_id, name, variant label|null]
	 */
	private function item_identity( $download_id, $price_id ) {
		$download_id = (int) $download_id;
		$has_price   = null !== $price_id && '' !== $price_id;

		$external_item_id = $has_price ? $download_id . ':' . $price_id : (string) $download_id;
		$variant          = ( $has_price && function_exists( 'edd_get_price_option_name' ) )
			? edd_get_price_option_name( $download_id, $price_id )
			: null;

		return array( $external_item_id, get_the_title( $download_id ), $variant );
	}

	/**
	 * Build the normalized purchase payload from an EDD order.
	 *
	 * @param int $payment_id Order/payment id.
	 * @return array|null Null when EDD 3.0 order APIs are unavailable.
	 */
	private function build_transaction( $payment_id ) {
		if ( ! function_exists( 'edd_get_order' ) ) {
			return null;
		}

		$order = edd_get_order( $payment_id );
		if ( ! $order ) {
			return null;
		}

		$items    = array();
		$position = 0;
		foreach ( $order->get_items() as $line ) {
			$quantity   = (int) $line->quantity;
			$unit_price = $quantity > 0 ? (float) $line->subtotal / $quantity : (float) $line->subtotal;

			$price_id = ( isset( $line->price_id ) && '' !== $line->price_id && null !== $line->price_id ) ? $line->price_id : null;
			list( $external_item_id, $name, $variant ) = $this->item_identity( $line->product_id, $price_id );
			if ( '' === $name ) {
				$name = $line->product_name;
			}

			$items[] = array(
				'external_item_id' => $external_item_id,
				'name'             => $name,
				'variant'          => $variant,
				'quantity'         => $quantity,
				'unit_price'       => $unit_price,
				'discount'         => (float) $line->discount,
				'position'         => $position,
			);

			$position++;
		}

		return array(
			'external_id'    => (string) $payment_id,
			'platform'       => 'edd',
			'kind'           => 'purchase',
			'status'         => 'completed',
			'currency'       => $order->currency,
			'value'          => (float) $order->total,
			'subtotal'       => (float) $order->subtotal,
			'tax'            => (float) $order->tax,
			'discount_total' => (float) $order->discount,
			'customer_hash'  => $this->hash_email( $order->email ),
			'occurred_at'    => $order->date_created ? gmdate( 'c', strtotime( $order->date_created ) ) : gmdate( 'c' ),
			'items'          => $items,
		);
	}
}
