<?php
/**
 * Recommendations Sync
 *
 * @package   EDD\Pro\Recommendations
 * @copyright Copyright (c) 2025, Sandhills Development, LLC
 * @license   https://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since     3.6.2
 */

namespace EDD\Pro\Recommendations;

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

use EDD\EventManagement\SubscriberInterface;

/**
 * Recommendations Sync class
 *
 * @since 3.6.2
 */
class Sync implements SubscriberInterface {

	/**
	 * The API instance.
	 *
	 * @var API
	 */
	private $api;

	/**
	 * Constructor.
	 *
	 * @since 3.6.2
	 */
	public function __construct() {
		$this->api = new API();
	}

	/**
	 * Get the subscribed events.
	 *
	 * @since 3.6.2
	 * @return array
	 */
	public static function get_subscribed_events() {
		return array(
			'edd_save_download'                => array( 'sync_download', 10, 2 ),
			'edd_cart_recommendations_enabled' => array( 'process_recommendations_settings', 10, 2 ),
			'wp_delete_post'                   => array( 'delete_on_delete', 10, 2 ),
			'wp_trash_post'                    => 'delete_on_trash',
			'edd_sync_recommendations'         => 'sync_recommendations',
		);
	}

	/**
	 * Sync a download.
	 *
	 * @since 3.6.2
	 * @param int      $download_id The download ID.
	 * @param \WP_Post $download    The download object.
	 */
	public function sync_download( $download_id, $download ) {
		if ( 'publish' !== $download->post_status ) {
			return;
		}

		if ( edd_doing_autosave() || edd_doing_ajax() || isset( $_REQUEST['bulk_edit'] ) ) {
			return;
		}

		if ( isset( $download->post_type ) && 'revision' === $download->post_type ) {
			return;
		}

		if ( ! current_user_can( 'edit_product', $download_id ) ) {
			return;
		}

		if ( ! $this->api->can_make_request() ) {
			return;
		}

		if ( \EDD\Cron\Events\SingleEvent::next_scheduled( 'edd_sync_single_download', array( $download_id ) ) ) {
			return;
		}

		delete_post_meta( $download_id, '_edd_recommendations_synced' );
		delete_post_meta( $download_id, '_edd_cached_recommendations' );

		\EDD\Cron\Events\SingleEvent::add(
			time() + ( 1 * MINUTE_IN_SECONDS ),
			'edd_sync_single_download',
			array(
				$download_id,
			)
		);
	}

	/**
	 * Process recommendations settings changes.
	 *
	 * Handles enabling/disabling recommendations and deletion requests.
	 *
	 * @since 3.6.2
	 * @param bool  $enabled Whether the cart recommendations are enabled.
	 * @param array $input   The input array.
	 * @return bool Whether cart recommendations should be enabled.
	 */
	public function process_recommendations_settings( $enabled, $input ): bool {
		if ( edd_is_inactive_pro() ) {
			return false;
		}

		// Handle enabling recommendations.
		if ( ! empty( $input['cart_recommendations'] ) ) {
			$this->maybe_schedule_initial_sync();

			return true;
		}

		// Handle deletion request.
		if ( ! empty( $input['delete_recommendations'] ) ) {
			$this->handle_delete_request( $input );
		}

		return $enabled;
	}

	/**
	 * Sync recommendations.
	 *
	 * @since 3.6.2
	 * @param array $data The data.
	 * @return void
	 */
	public function sync_recommendations( $data ) {
		if ( ! current_user_can( 'manage_shop_settings' ) ) {
			return;
		}

		if ( empty( $data['_wpnonce'] ) || ! wp_verify_nonce( $data['_wpnonce'], 'edd_sync_recommendations' ) ) {
			return;
		}

		if ( \EDD\Cron\Events\SingleEvent::next_scheduled( 'edd_sync_all_products' ) ) {
			EDD()->notices->add_transient_notice(
				'edd_recommendations_sync_already_scheduled',
				__( 'Recommendations sync already scheduled.', 'easy-digital-downloads' ),
				'warning'
			);
			$this->redirect_to_settings();
		}

		\EDD\Cron\Events\SingleEvent::add(
			time() + 3 * MINUTE_IN_SECONDS,
			'edd_sync_all_products'
		);

		EDD()->notices->add_transient_notice(
			'edd_recommendations_sync_scheduled',
			__( 'Recommendations sync scheduled.', 'easy-digital-downloads' )
		);

		$this->redirect_to_settings();
	}

	/**
	 * Delete recommendations when a download is deleted.
	 *
	 * @since 3.6.2
	 * @param int      $post_id The post ID.
	 * @param \WP_Post $post The post object.
	 * @return void
	 */
	public function delete_on_delete( $post_id, $post ) {
		if ( 'download' !== $post->post_type ) {
			return;
		}

		$this->delete( $post_id );
	}

	/**
	 * Trash recommendations when a download is trashed.
	 *
	 * @since 3.6.2
	 * @param int $post_id The post ID.
	 * @return void
	 */
	public function delete_on_trash( $post_id ) {
		if ( 'download' !== get_post_type( $post_id ) ) {
			return;
		}

		$this->delete( $post_id );
	}

	/**
	 * Delete recommendations for a download.
	 *
	 * @param int $post_id The post ID.
	 * @return bool|WP_Error
	 */
	private function delete( $post_id ) {
		delete_post_meta( $post_id, '_edd_recommendations_synced' );
		delete_post_meta( $post_id, '_edd_cached_recommendations' );

		return $this->api->delete( array( $post_id ) );
	}

	/**
	 * Redirect to the recommendations settings page.
	 *
	 * @since 3.6.2
	 * @return void
	 */
	private function redirect_to_settings(): void {
		edd_redirect(
			edd_get_admin_url(
				array(
					'page'    => 'edd-settings',
					'tab'     => 'gateways',
					'section' => 'cart',
				)
			)
		);
	}

	/**
	 * Handle the delete recommendations request.
	 *
	 * @since 3.6.2
	 * @param array $input The input array.
	 * @return void
	 */
	private function handle_delete_request( array $input ) {
		if ( ! current_user_can( 'manage_shop_settings' ) ) {
			return;
		}

		if ( empty( $input['delete_recommendations_nonce'] ) || ! wp_verify_nonce( $input['delete_recommendations_nonce'], 'edd_delete_recommendations' ) ) {
			return;
		}

		$deleted = $this->api->delete();
		if ( is_wp_error( $deleted ) ) {
			EDD()->notices->add_transient_notice(
				'edd_recommendations_delete_failed',
				$deleted->get_error_message(),
				'error'
			);

			return;
		}

		if ( empty( $deleted['success'] ) ) {
			EDD()->notices->add_transient_notice(
				'edd_recommendations_delete_failed',
				__( 'Failed to remove product data from the remote service.', 'easy-digital-downloads' ),
				'error'
			);

			return;
		}

		// Clear local data.
		$query = new Query();
		$query->clear_cache();

		// Bulk delete all recommendations post meta.
		global $wpdb;
		$wpdb->query(
			$wpdb->prepare(
				"DELETE pm FROM {$wpdb->postmeta} pm
				INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID
				WHERE p.post_type = %s
				AND pm.meta_key IN (%s, %s)",
				'download',
				'_edd_recommendations_synced',
				'_edd_cached_recommendations'
			)
		);

		EDD()->notices->add_transient_notice(
			'edd_recommendations_deleted',
			sprintf(
				/* translators: %d: number of products deleted */
				__( 'Product data removed from the recommendations service for %d products.', 'easy-digital-downloads' ),
				$deleted['deleted_count']
			),
			'success'
		);
	}

	/**
	 * Maybe schedule the initial sync.
	 *
	 * @since 3.6.2
	 * @return void
	 */
	private function maybe_schedule_initial_sync(): void {
		if ( edd_has_upgrade_completed( 'cart_recommendations_initial_sync' ) ) {
			return;
		}

		if ( \EDD\Cron\Events\SingleEvent::next_scheduled( 'edd_ingest_products' ) ) {
			return;
		}

		\EDD\Cron\Events\SingleEvent::add(
			time() + ( 3 * MINUTE_IN_SECONDS ),
			'edd_ingest_products'
		);
	}
}
