<?php
/**
 * SSP Purchase Proxy
 *
 * Routes purchase conversion events through the SweetCode Cloud (SSP) proxy
 * instead of sending them directly to each ad platform. Collects event data
 * from all active CAPI classes, bundles them into a single HTTP request, and
 * sends it to the SSP /v1/sync/purchase-events endpoint.
 *
 * Falls back to direct per-platform sends if the SSP request fails.
 *
 * @since 1.57.0
 */

namespace SweetCode\Pixel_Manager;

use SweetCode\Pixel_Manager\Admin\Environment;
use SweetCode\Pixel_Manager\Pixels\Facebook\Facebook_CAPI;
use SweetCode\Pixel_Manager\Pixels\TikTok\TikTok_EAPI;
use SweetCode\Pixel_Manager\Pixels\Pinterest\Pinterest_APIC;
use SweetCode\Pixel_Manager\Pixels\Snapchat\Snapchat_CAPI;
use SweetCode\Pixel_Manager\Pixels\Reddit\Reddit_CAPI;

defined('ABSPATH') || exit; // Exit if accessed directly

class SSP_Purchase_Proxy {

	private static $instance;

	/**
	 * Map of SSP destination keys to their CAPI class names.
	 *
	 * @var array<string, string>
	 */
	private static $destination_map = [
		'facebook'  => Facebook_CAPI::class,
		'tiktok'    => TikTok_EAPI::class,
		'pinterest' => Pinterest_APIC::class,
		'snapchat'  => Snapchat_CAPI::class,
		'reddit'    => Reddit_CAPI::class,
	];

	public static function get_instance() {
		if ( is_null( self::$instance ) ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	public function __construct() {
		$this->register_hooks();
	}

	/**
	 * Hook into the same WooCommerce order status hooks that the S2S classes use.
	 *
	 * We register with a lower priority (20) so that identifier capture
	 * (hooked at default priority 10) completes first.
	 *
	 * @since 1.57.0
	 */
	private function register_hooks() {

		if ( ! Environment::is_woocommerce_active() ) {
			return;
		}

		foreach ( S2S::get_purchase_trigger_hooks() as $hook ) {
			add_action( $hook, [ $this, 'handle_purchase_event' ], 20 );
		}
	}

	/**
	 * Handle a purchase event by collecting data from all active CAPI classes
	 * and sending them bundled to the SSP.
	 *
	 * @param int|\WC_Order $order_id_or_order Order ID or order object.
	 *
	 * @since 1.57.0
	 */
	public function handle_purchase_event( $order_id_or_order ) {

		$order = $order_id_or_order instanceof \WC_Order
			? $order_id_or_order
			: wc_get_order( $order_id_or_order );

		if ( ! $order ) {
			return;
		}

		$destinations = $this->collect_destination_payloads( $order );

		if ( empty( $destinations ) ) {
			return;
		}

		$payload = [
			'event'        => 'purchase',
			'order_id'     => $order->get_id(),
			'destinations' => $destinations,
		];

		$success = $this->send_to_ssp( $payload );

		if ( $success ) {
			$this->mark_hits_sent( $order, array_keys( $destinations ) );
		} else {
			// Fallback: send directly to each platform
			$this->fallback_direct_send( $order, array_keys( $destinations ) );
		}
	}

	/**
	 * Iterate over all active CAPI classes and collect their event data.
	 *
	 * Each class's build_ssp_purchase_event_data() returns null if the order
	 * shouldn't be processed for that platform (already sent, missing config, etc.).
	 *
	 * @param \WC_Order $order
	 *
	 * @return array<string, array> Keyed by destination name.
	 * @since 1.57.0
	 */
	private function collect_destination_payloads( $order ) {

		$active_checks = [
			'facebook'  => 'is_facebook_capi_active',
			'tiktok'    => 'is_tiktok_eapi_active',
			'pinterest' => 'is_pinterest_apic_active',
			'snapchat'  => 'is_snapchat_capi_active',
			'reddit'    => 'is_reddit_capi_active',
		];

		$destinations = [];

		foreach ( self::$destination_map as $key => $class ) {

			// Check if the integration is active
			if ( ! isset( $active_checks[ $key ] ) || ! call_user_func( [ Options::class, $active_checks[ $key ] ] ) ) {
				continue;
			}

			// Check if the purchase hit was already sent for this order
			$hit_key = $class::get_purchase_hit_key();
			if ( $order->get_meta( $hit_key ) ) {
				continue;
			}

			$event_data = $class::build_ssp_purchase_event_data( $order );

			if ( $event_data ) {
				$destinations[ $key ] = $event_data;
			}
		}

		return $destinations;
	}

	/**
	 * Send the bundled purchase payload to the SSP.
	 *
	 * Uses a blocking request so we can determine success/failure
	 * and decide whether to fall back to direct sends.
	 *
	 * @param array $payload
	 *
	 * @return bool True if SSP accepted the payload.
	 * @since 1.57.0
	 */
	private function send_to_ssp( $payload ) {

		$sync_token = Options::get_ssp_sync_token();

		if ( empty( $sync_token ) ) {
			Logger::warning( 'SSP Purchase Proxy: No sync token available.' );
			return false;
		}

		$url = Options::get_ssp_purchase_events_url();

		$response = wp_remote_post(
			$url,
			[
				'headers'  => [
					'Authorization' => 'Bearer ' . $sync_token,
					'Content-Type'  => 'application/json',
				],
				'body'     => wp_json_encode( $payload ),
				'timeout'  => 15,
				'blocking' => true,
			]
		);

		if ( is_wp_error( $response ) ) {
			Logger::warning( 'SSP Purchase Proxy: Request failed — ' . $response->get_error_message() );
			return false;
		}

		$code = wp_remote_retrieve_response_code( $response );

		if ( 200 !== $code ) {
			$body = wp_remote_retrieve_body( $response );
			Logger::warning( 'SSP Purchase Proxy: HTTP ' . $code . ' — ' . $body );
			return false;
		}

		$body = json_decode( wp_remote_retrieve_body( $response ), true );

		if ( empty( $body['success'] ) ) {
			Logger::warning( 'SSP Purchase Proxy: Unexpected response — ' . wp_json_encode( $body ) );
			return false;
		}

		Logger::info(
			'SSP Purchase Proxy: Successfully dispatched purchase for order '
			. $payload['order_id'] . ' (' . ( isset( $body['data']['dispatched'] ) ? $body['data']['dispatched'] : '?' ) . ' destinations)'
		);

		return true;
	}

	/**
	 * Mark order meta for each destination that was successfully dispatched
	 * through the SSP.
	 *
	 * @param \WC_Order    $order
	 * @param array<string> $destination_keys
	 *
	 * @since 1.57.0
	 */
	private function mark_hits_sent( $order, $destination_keys ) {

		foreach ( $destination_keys as $key ) {
			if ( isset( self::$destination_map[ $key ] ) ) {
				$class   = self::$destination_map[ $key ];
				$hit_key = $class::get_purchase_hit_key();
				$order->update_meta_data( $hit_key, true );
			}
		}

		$order->save();
	}

	/**
	 * Fallback: send purchase events directly to each platform
	 * using the original S2S send_purchase_hit() methods.
	 *
	 * This bypasses the S2S base class guard (send_purchase_hit_order_id)
	 * by calling send_purchase_hit() directly on each CAPI class.
	 *
	 * @param \WC_Order    $order
	 * @param array<string> $destination_keys Keys of destinations that had payloads.
	 *
	 * @since 1.57.0
	 */
	private function fallback_direct_send( $order, $destination_keys ) {

		Logger::info( 'SSP Purchase Proxy: Falling back to direct sends for order ' . $order->get_id() );

		foreach ( $destination_keys as $key ) {
			if ( isset( self::$destination_map[ $key ] ) ) {
				$class = self::$destination_map[ $key ];

				try {
					$class::send_purchase_hit( $order );
				} catch ( \Exception $e ) {
					Logger::warning(
						'SSP Purchase Proxy: Fallback send for ' . $key
						. ' failed — ' . $e->getMessage()
					);
				}
			}
		}
	}
}
