<?php
/**
 * Analytics Event Tracker.
 *
 * Hooks into WooCommerce actions to record add-to-cart, order-placed, and purchased
 * events into the preco_analytics_events table via AnalyticsTable.
 *
 * Only items that originated from a Preco recommendation (i.e. have the
 * _preco_page_source order item meta) are counted — native WooCommerce adds
 * that have no Preco context are silently ignored.
 *
 * @package PRECO
 * @since 1.0.0
 */

namespace PRECO\Includes\Analytics;

defined( 'ABSPATH' ) || exit;

/**
 * Analytics class.
 */
class Analytics {

	/**
	 * Register WooCommerce hooks.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {
		// Copy page source from cart item data to order line item meta at checkout.
		add_action( 'woocommerce_checkout_create_order_line_item', array( $this, 'copy_page_source_to_order_item' ), 10, 4 );

		// Track order-placed event when the order is first created at checkout.
		add_action( 'woocommerce_checkout_order_processed', array( $this, 'on_order_placed' ), 10 );
		add_action( 'woocommerce_store_api_checkout_order_processed', array( $this, 'on_order_placed' ), 10 );

		// Track purchased event only when the order status reaches 'completed'.
		// This avoids counting orders that were placed but never paid or were later cancelled.
		add_action( 'woocommerce_order_status_completed', array( $this, 'on_order_completed' ) );

		// Reverse the purchased total when a completed order is later refunded (fully or partially).
		add_action( 'woocommerce_order_refunded', array( $this, 'on_order_refunded' ), 10, 2 );
	}

	// -------------------------------------------------------------------------
	// Hook callbacks
	// -------------------------------------------------------------------------

	/**
	 * Persist the Preco page source from cart item data into the order line item meta
	 * so it is available after the cart is cleared (e.g. on payment complete).
	 *
	 * @param \WC_Order_Item_Product $item          Order line item.
	 * @param string                 $cart_item_key Cart item key.
	 * @param array                  $values        Cart item data.
	 * @param \WC_Order              $order         Order object.
	 * @return void
	 */
	public function copy_page_source_to_order_item( $item, $cart_item_key, $values, $order ) {
		$source = $values['preco_page_source'] ?? '';
		if ( ! empty( $source ) && 'none' !== $source ) {
			$item->update_meta_data( '_preco_page_source', array( 'value' => sanitize_text_field( $source ) ) );
		}
	}

	/**
	 * Record order-placed events when a new order is created at checkout.
	 * Only items that came through a Preco recommendation (have _preco_page_source) are counted.
	 * Value = 1 per unique page source per order (counts number of orders from each page, not item quantities).
	 *
	 * Example: one order with items from product-page, product-archive, shop-page
	 *   → product-page  +1
	 *   → product-archive +1
	 *   → shop-page     +1
	 * A second order on the same date with the same pages → each page accumulates to 2.
	 * Items without preco_page_source or with 'none' are silently ignored.
	 *
	 * @param int $order_id Order ID.
	 * @return void
	 */
	public function on_order_placed( $order_id ) {
		$order = wc_get_order( $order_id );
		if ( ! $order ) {
			return;
		}

		$today      = gmdate( 'Y-m-d' );
		$seen_pages = array();

		foreach ( $order->get_items() as $item ) {
			/** Order line item. @var \WC_Order_Item_Product $item */
			$meta        = $item->get_meta( '_preco_page_source' );
			$page_source = is_array( $meta ) ? ( $meta['value'] ?? '' ) : (string) $meta;

			// Skip items that did not originate from a Preco recommendation.
			if ( empty( $page_source ) || 'none' === $page_source ) {
				continue;
			}

			$page = AnalyticsTable::resolve_page( sanitize_text_field( $page_source ) );

			// Count each page source only once per order.
			if ( isset( $seen_pages[ $page ] ) ) {
				continue;
			}

			$seen_pages[ $page ] = true;
			AnalyticsTable::record( $page, AnalyticsTable::TYPE_ORDER_PLACED, 1, $today );
		}
	}

	/**
	 * Record purchased events when an order status transitions to 'completed'.
	 * Only items that came through a Preco recommendation (have _preco_page_source) are counted.
	 * Value = line item total (price × quantity, excluding tax, consistent with WC Analytics).
	 *
	 * Uses an order meta flag to prevent double-recording if the status somehow
	 * toggles to completed more than once.
	 *
	 * @param int $order_id Order ID.
	 * @return void
	 */
	public function on_order_completed( $order_id ) {
		$order = wc_get_order( $order_id );
		if ( ! $order ) {
			return;
		}

		// Guard against duplicate recordings.
		if ( $order->get_meta( '_preco_analytics_purchased_recorded' ) ) {
			return;
		}

		$today   = gmdate( 'Y-m-d' );
		$has_any = false;

		foreach ( $order->get_items() as $item ) {
			/** Order line item. @var \WC_Order_Item_Product $item */
			$meta        = $item->get_meta( '_preco_page_source' );
			$page_source = is_array( $meta ) ? ( $meta['value'] ?? '' ) : (string) $meta;

			// Skip items that did not originate from a Preco recommendation.
			if ( empty( $page_source ) || 'none' === $page_source ) {
				continue;
			}

			$page       = AnalyticsTable::resolve_page( sanitize_text_field( $page_source ) );
			$line_total = (float) $item->get_total(); // excl. tax.

			if ( $line_total > 0 ) {
				AnalyticsTable::record( $page, AnalyticsTable::TYPE_PURCHASED, $line_total, $today );
				$has_any = true;
			}
		}

		// Mark as recorded only if at least one Preco item was processed.
		if ( $has_any ) {
			$order->update_meta_data( '_preco_analytics_purchased_recorded', '1' );
			// Remember the date the purchase was recorded under so a later refund
			// reverses the total on the same day it was originally attributed to.
			$order->update_meta_data( '_preco_analytics_purchased_date', $today );
			$order->save_meta_data();
		}
	}

	/**
	 * Reverse the purchased total for items refunded from a completed order.
	 * Only orders whose purchase we actually recorded (via on_order_completed) are touched,
	 * so refunds on orders that never reached 'completed' — and therefore were never
	 * counted — are correctly ignored.
	 *
	 * Refund line items are matched back to the original order item via WooCommerce's
	 * own `_refunded_item_id` meta so each refunded amount is attributed to the same
	 * page source the original sale was recorded under.
	 *
	 * Guarded per-refund (not per-order) so multiple partial refunds on the same order
	 * are each reversed exactly once.
	 *
	 * @param int $order_id  ID of the order that was refunded.
	 * @param int $refund_id ID of the refund (a WC_Order_Refund) that was created.
	 * @return void
	 */
	public function on_order_refunded( $order_id, $refund_id ) {
		$order = wc_get_order( $order_id );
		if ( ! $order || ! $order->get_meta( '_preco_analytics_purchased_recorded' ) ) {
			return;
		}

		$refund = wc_get_order( $refund_id );
		if ( ! $refund || $refund->get_meta( '_preco_analytics_refund_processed' ) ) {
			return;
		}

		$date = $order->get_meta( '_preco_analytics_purchased_date' );
		if ( empty( $date ) ) {
			$date = gmdate( 'Y-m-d' );
		}

		foreach ( $refund->get_items() as $refund_item ) {
			/** Refund line item. @var \WC_Order_Item_Product $refund_item */
			$original_item_id = absint( $refund_item->get_meta( '_refunded_item_id' ) );
			$original_item    = $original_item_id ? $order->get_item( $original_item_id ) : false;
			if ( ! $original_item ) {
				continue;
			}

			$meta        = $original_item->get_meta( '_preco_page_source' );
			$page_source = is_array( $meta ) ? ( $meta['value'] ?? '' ) : (string) $meta;

			// Skip items that did not originate from a Preco recommendation.
			if ( empty( $page_source ) || 'none' === $page_source ) {
				continue;
			}

			$page           = AnalyticsTable::resolve_page( sanitize_text_field( $page_source ) );
			$refunded_total = abs( (float) $refund_item->get_total() ); // refund item totals are stored negative.

			if ( $refunded_total > 0 ) {
				AnalyticsTable::record( $page, AnalyticsTable::TYPE_PURCHASED, -$refunded_total, $date );
			}
		}

		$refund->update_meta_data( '_preco_analytics_refund_processed', '1' );
		$refund->save_meta_data();
	}
}
