<?php
/**
 * Plugin Name:     RPSync
 * Plugin URI:      https://gitlab.ledevsimple.ca/wordpress/plugins/rpsync
 * Description:     Synchronize WooCommerce products from Retailpoint XML file.
 * Author:          lewebsimple
 * Author URI:      https://lewebsimple.ca
 * Text Domain:     rpsync
 * Domain Path:     /languages
 * Version:         0.5.5
 */

defined( 'ABSPATH' ) || exit;

require_once plugin_dir_path( __FILE__ ) . 'includes/class-rpsync-database.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-rpsync-dashboard.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-rpsync-settings.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-rpsync-woocommerce.php';

const RPSYNC_ACYNC_PROCESS = true;

class RPSync {
	/**
	 * Singleton instance
	 * @var RPSync
	 */
	private static $instance = null;

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

		return self::$instance;
	}

	/**
	 * @var RPSync_Database
	 */
	public $database;

	/**
	 * @var RPSync_Dashboard
	 */
	protected $dashboard;

	/**
	 * @var WC_Logger
	 */
	protected $logger;

	/**
	 * @var RPSync_Settings
	 */
	protected $settings;

	/**
	 * @var RPSync_WooCommerce
	 */
	public $woocommerce;

	public function __construct() {
		$this->database    = new RPSync_Database();
		$this->dashboard   = new RPSync_Dashboard();
		$this->settings    = new RPSync_Settings();
		$this->woocommerce = new RPSync_WooCommerce();

		// Initialize RPSync
		add_action( 'plugins_loaded', array( $this, 'init' ) );

		// CRON
		add_filter( 'cron_schedules', array( $this, 'cron_schedules' ) );

		// RPSync actions
		add_action( 'wp_ajax_process_xml', array( $this, 'process_xml' ) );
		add_action( 'rpsync_process_xml', array( $this, 'process_xml' ) );
		add_action( 'wp_ajax_process_numref', array( $this, 'process_numref' ) );
	}

	/**
	 * Initialize RPSync
	 */
	public function init() {
		$this->logger = wc_get_logger();
		if ( ! wp_next_scheduled( 'rpsync_process_xml' ) ) {
			wp_schedule_event( time(), 'rpsync_cron', 'rpsync_process_xml' );
		}
	}

	/**
	 * Log error message
	 *
	 * @param string $message
	 */
	public function error( string $message ) {
		if ( $this->logger ) {
			$this->logger->error( $message, array( 'source' => 'rpsync' ) );
		} else {
			error_log( $message );
		}
	}

	/**
	 * Log information message
	 *
	 * @param string $message
	 */
	public function info( string $message ) {
		if ( $this->logger ) {
			$this->logger->info( $message, array( 'source' => 'rpsync' ) );
		} else {
			error_log( $message );
		}
	}

	/**
	 * Custom CRON schedule for RPSync
	 *
	 * @param array $schedules
	 *
	 * @return array
	 */
	public function cron_schedules( array $schedules ) {
		$interval                 = $this->settings->get( 'sync_interval' ) ?: 60;
		$schedules['rpsync_cron'] = array(
			'interval' => $interval * 60,
			'display'  => sprintf( "Every %d minutes", $interval ),
		);

		return $schedules;
	}

	/**
	 * Process Retailpoint XML file and dispatch products to processing queue
	 * @return void
	 */
	public function process_xml() {
		// Check for XML file, notify if too old
		if ( ! file_exists( $this->settings->get( 'xml_path' ) ) ) {
			$this->error( "Le fichier XML de Retailpoint est manquant." );
			return;
		}

		// Check for outdated XML file
		$new_mtime  = filemtime( $this->settings->get( 'xml_path' ) );
		$last_mtime = get_transient( 'rpsync_xml_mtime' ) ?: $new_mtime;
		$now        = strtotime( 'now' );
		if ( $last_mtime == $new_mtime && ( $now - $last_mtime ) > 12 * HOUR_IN_SECONDS ) {
			$this->error( "Le fichier XML de Retailpoint n'a pas été changé depuis plus de 12 heures" );
		} else {
			set_transient( 'rpsync_xml_mtime', $new_mtime );
		}

		// Load XML file
		$start = microtime( true );
		if ( empty( $xml = simplexml_load_file( $this->settings->get( 'xml_path' ) ) ) ) {
			$this->error( "Le fichier XML de Retailpoint est corrompu" );
			return;
		}
		$this->info( sprintf( "Le fichier XML de Retailpoint a été chargé en %.3f secs", microtime( true ) - $start ) );

		// Dispatch Retailpoint XML products
		$count = array( 'dispatched' => 0, 'total' => 0, 'skipped' => 0 );
		$start = microtime( true );
		foreach ( $xml->product as $xml_product ) {
			$count['total'] ++;

			// Extract product data from XML product, skip if missing numref
			if ( empty( $product_data = apply_filters( 'rpsync_extract_product_data', $this->extract_product_data( $xml_product ) ) ) ) {
				$count['skipped'] ++;
				continue;
			}

			// Handle extracted product data, enqueue async processing if md5 has changed
			if ( $this->handle_product_data( $product_data ) ) {
				$this->dispatch_process_numref( $product_data['numref'] );
				$count['dispatched'] ++;
			}
		}
		$this->info( sprintf( "RPSync a détecté %d changements sur %d produits en %.3f secs (%d ignorés).",
				$count['dispatched'], $count['total'], microtime( true ) - $start, $count['skipped'] )
		);
	}

	/**
	 * Process numref manually
	 *
	 * @param $numref
	 *
	 * @return void
	 */
	public function process_numref( $numref ) {
		if ( empty( $numref = $_GET['numref'] ) ) {
			wp_die( 'Veuillez spécifier le numref.' );
		}
		do_action( 'rpsync_process_product_data', $numref, 'rpsync' );
		wp_die( 'OK' );
	}

	/**
	 * Extract product data from XML product
	 *
	 * @param $xml_product
	 *
	 * @return array|false
	 */
	public function extract_product_data( $xml_product ) {
		$xml_product = (array) $xml_product;
		if ( empty( $xml_product['numref'] ) ) {
			return false;
		}
		$product_data = array(
			'numref' => $xml_product['numref'],
			'skus'   => array(),
		);
		foreach ( $xml_product['skus']->sku as $xml_sku ) {
			$xml_sku = (array) $xml_sku;
			if ( empty( $sku = (string) $xml_sku['codebar'] ) ) {
				return false;
			}
			$product_data['skus'][ $sku ] = array(
				'numref'          => (string) $xml_product['numref'],
				'sku'             => $sku,
				'title'           => (string) $xml_sku['desc'],
				'brand'           => (string) $xml_sku['Category'],
				'department'      => $xml_sku['Dept'] . ' / ' . $xml_sku['SubDept'],
				'season'          => (string) $xml_sku['Season'],
				'attributes'      => array_intersect_key( $xml_sku, array_flip( [ 'couleur', 'grandeur', 'largeur' ] ) ),
				'stock'           => (int) $xml_sku['qt'],
				'regular_price'   => $xml_sku['price'] ? number_format( $xml_sku['price'], 2, '.', '' ) : '',
				'sale_price'      => $xml_sku['Promo'] ? number_format( $xml_sku['Promo'], 2, '.', '' ) : '',
				'suggested_price' => $xml_sku['priceSugg'] ? number_format( $xml_sku['priceSugg'], 2, '.', '' ) : '',
			);
		}
		if ( ! empty( $product_data['skus'] ) ) {
			$product_data['title'] = reset( $product_data['skus'] )['title'];
		}
		$product_data['md5'] = md5( json_encode( $product_data['skus'] ) );

		return $product_data;
	}

	/**
	 * Handle extracted product data
	 *
	 * @param $product_data
	 *
	 * @return bool true if product_data should be processed, false otherwise
	 */
	public function handle_product_data( $product_data ) {
		// Check for matching md5 checksum
		if ( $product_data['md5'] === $this->database->get_md5( $product_data['numref'] ) ) {
			return false;
		}
		return $this->database->save_product_data( $product_data );
	}

	/**
	 * Dispatch numref for async processing
	 *
	 * @param $numref
	 *
	 * @return void
	 */
	public function dispatch_process_numref( $numref ) {
		$this->info( sprintf( "%s => Ajouté à la file de traitement", $numref ) );
		if ( RPSYNC_ACYNC_PROCESS ) {
			if ( ! as_has_scheduled_action( 'rpsync_process_product_data', array( 'numref' => $numref, 'rpsync' ) ) ) {
				as_enqueue_async_action( 'rpsync_process_product_data', array( 'numref' => $numref ), 'rpsync' );
			}
		} else {
			do_action( 'rpsync_process_product_data', $numref, 'rpsync' );
		}
	}

}

RPSync::get_instance();

function rpsync_register_script( $handle, $path, $deps = array() ) {
	if ( ! file_exists( $absolute_path = plugin_dir_path( __FILE__ ) . $path ) ) {
		return false;
	}
	$version = filemtime( $absolute_path );

	return wp_register_script( $handle, plugin_dir_url( __FILE__ ) . $path, $deps, $version );
}

function rpsync_register_style( $handle, $path, $deps = array() ) {
	if ( ! file_exists( $absolute_path = plugin_dir_path( __FILE__ ) . $path ) ) {
		return false;
	}
	$version = filemtime( $absolute_path );

	return wp_register_style( $handle, plugin_dir_url( __FILE__ ) . $path, $deps, $version );
}

// Helper: array_diff with recursive support
function rpsync_array_diff_recursive( $arr1, $arr2 ) {
	$outputDiff = [];
	foreach ( $arr1 as $key => $value ) {
		if ( array_key_exists( $key, $arr2 ) ) {
			if ( is_array( $value ) ) {
				$recursiveDiff = rpsync_array_diff_recursive( $value, $arr2[ $key ] );
				if ( count( $recursiveDiff ) ) {
					$outputDiff[ $key ] = $recursiveDiff;
				}
			} else if ( ! in_array( $value, $arr2 ) ) {
				$outputDiff[ $key ] = $value;
			}
		} else if ( ! in_array( $value, $arr2 ) ) {
			$outputDiff[ $key ] = $value;
		}
	}
	return $outputDiff;
}
