<?php
/**
 * Routes all the hooks to their respective actions and filters.
 *
 * @package StockSyncWithGoogleSheetForWooCommerce
 * @since 1.0.0
 */
// Namespace.
namespace StockSyncWithGoogleSheetForWooCommerce;

use Error;

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

if ( ! class_exists('\StockSyncWithGoogleSheetForWooCommerce\CRON') ) {

	/**
	 * Routes all the hooks to their respective actions and filters.
	 *
	 * @package StockSyncWithGoogleSheetForWooCommerce
	 * @since 1.0.0
	 */
	class CRON extends Base {
		/**
		 * The single instance of the class.
		 *
		 * @var Hooks
		 */
		public static $instance = null;
		

		/**
		 * Initializes the class.
		 */
		public static function init() {
			if ( ! self::$instance ) {
				self::$instance = new self();
			}
			self::$instance->add_actions();
		}
		/**
		 * Actions to be added for the plugin.
		 *
		 * @since 1.0.0
		 */
		public function add_actions() {
            add_filter( 'cron_schedules', [$this, 'ssgsw_add_custom_cron_schedules'] );
            add_action('ssgsw_schedule_mode', [$this, 'set_cron']);
            add_action('ssgsw_schedule_sync_mode', [$this, 'check_sync_directions']);
            add_action('ssgsw_process_data_chunk', [$this, 'ssgsw_process_data_chunk'], 10, 1);
            add_action('ssgsw_process_data_chunk_formula', [$this, 'ssgsw_process_data_chunk'], 10, 1);
            add_action('admin_init', [$this, 'clar_event']);
            add_action('ssgsw_wc_to_sheet_sync_batch', [$this, 'ssgsw_process_product_batch' ], 10, 1);
            add_action('ssgsw_schedule_formula_mode', [$this, 'ssgsw_execute_formula_mode'], 10, 2);
            add_action('ssgsw_schedule_sync_formula', [$this, 'ssgsw_schedule_formula_sync'], 10, 1);
		}
        /**
         * Handles the processing of arguments for scheduling a formula synchronization task.
         * * This function extracts 'is_vendor' and 'token_id' from the passed arguments array.
         * It is typically used as the callback for a scheduled WordPress Cron event or
         * an action hook where these parameters are expected.
         * * @param array $args An associative array expected to contain scheduling parameters.
         * Expected keys:
         * - 'is_vendor' (bool): Determines if the synchronization is vendor-specific. Defaults to false.
         * - 'token_id' (int|string): The identifier for the formula or schedule to be synced. Defaults to false.
         * @return void
         */
        public function ssgsw_schedule_formula_sync($args) {
            $is_vendor = isset($args['is_vendor']) ? $args['is_vendor'] : false;
            $token_id = isset($args['token_id']) ? $args['token_id'] : false;
            ssgsw_formula_sync_request( $token_id, $is_vendor);
        }
        /**
         * Executes the logic for scheduling or activating a specific formula mode.
         * * This function is typically hooked into the 'ssgsw_schedule_formula_mode' action.
         * It processes the scheduling request based on whether the action is initiated 
         * by a vendor or a general user, and identifies the target schedule/token.
         *
         * @param bool $is_vendor True if the action is being performed by a vendor, false otherwise.
         * @param int|string $token_id The unique ID or token associated with the formula schedule.
         * @return void
         */
        public function ssgsw_execute_formula_mode($is_vendor, $token_id) {
            // Clears any previously scheduled cron events for this hook.
            $args = [
                'is_vendor' => $is_vendor,
                'token_id'  => $token_id
            ];
            wp_clear_scheduled_hook( 'ssgsw_schedule_sync_formula', [$args] );
            if(!ssgsw_is_license_valid()) {
                return;
            }

            // Get the selected sync mode from the options.
            $enabled = ssgsw_get_option( 'formula_active', true );
            if(!$is_vendor) {
                $sync_mode = ssgsw_get_option( 'selected_sync_mode', 'live-sync' );
                if($sync_mode == 'perodic-sync') {
                    return;
                }
                if($sync_mode == 'live-sync') {
                    $sync_direction = ssgsw_get_option('live_sync_direction', 'bidirectional');
                    if( $sync_direction == 'woocommerce-to-sheets') {
                        return;
                    }
                }
            }
            // If Live Sync is enabled, no periodic sync is needed, so we return.
            if (!$enabled ) {
                return;
            }
     
            // Get the chosen periodic sync recurrence from the options.
            $cron_sync_recurrence = ssgsw_get_option( 'cron_formula_perdic_sync', '10' );
            $perdic_sync_day = ssgsw_get_option( 'perdic_formula_sync_day', 'sunday' );
        

            // Maps the option value to the corresponding cron schedule name.
            switch ( $cron_sync_recurrence ) {
                case '10':
                    $cron_schedule_name = 'every_10_minutes';
                    break;
                case '30':
                    $cron_schedule_name = 'every_30_minutes';
                    break;
                case 'hourly':
                    $cron_schedule_name = 'hourly';
                    break;
                case 'daily':
                    $cron_schedule_name = 'daily';
                    break;
                case 'weekly':
                    $cron_schedule_name = 'weekly';
                    break;
                case 'monthly':
                    $cron_schedule_name = 'monthly';
                    break;
                default:
                    // Fallback to daily if an invalid recurrence is found.
                    $cron_schedule_name = 'daily';
                    break;
            }

            // Determine the start timestamp for the event.
            // For minute-based intervals, we start from the current time.
            if ( in_array( $cron_sync_recurrence, array('10', '30', 'hourly' ) ) ) {
                $timestamp = current_time( 'timestamp', true );
            } else {
                // For daily, weekly, or monthly, we use a specific time (e.g., 4 AM).
                $sync_time_hour = intval( ssgsw_get_option( 'perdic_formula_sync_time', 4 ) );
              
                $timestamp      = $this->next_sync_timestamp( $sync_time_hour, 0, $cron_sync_recurrence, $perdic_sync_day );
            }
            $scheduling_args = [$args];
            // Check if the event is already scheduled using the hook name AND the exact arguments
            if ( ! wp_next_scheduled( 'ssgsw_schedule_sync_formula', $scheduling_args ) ) {
                // Schedule the event only if it does not already exist with these specific arguments
                wp_schedule_event( $timestamp, $cron_schedule_name, 'ssgsw_schedule_sync_formula', $scheduling_args );
            }
           
        }
        /**
         * Determines the sync direction and initiates the corresponding process.
         *
         * This function retrieves the configured periodic sync direction from the options.
         * Based on the value, it decides whether to sync product data from WooCommerce to Google Sheets
         * or from Google Sheets to WooCommerce.
         *
         * @since 1.0.0
         *
         * @return void
         */
        public function check_sync_directions() {
            $sync_directions = ssgsw_get_option( 'peroidic_sync_direction', 'woocommerce-to-sheets' );
            if ('woocommerce-to-sheets' == $sync_directions) {
                $this->ssgsw_process_product_batch([]);
            } else {
                $this->sync_sheet_to_wordpress();
          
            }
        }
        /**
         * Processes a batch of products for syncing from WooCommerce to Google Sheets.
         *
         * This function handles the synchronization process in batches to prevent timeouts and memory
         * issues on large stores. It iterates through products, syncing them to the sheet
         * in chunks of a defined batch size. If the process is successful, it updates the
         * offset and index for the next batch. In case of consecutive errors, it advances
         * to the next batch to prevent getting stuck.
         *
         * For very large data sets, it uses a single event cron job to schedule the
         * next batch, effectively handing off the processing to the background and
         * preventing a single request from running for too long.
         *
         * @param array $offset_info An array containing batch processing information.
         * - `totalItems` (int)   : The total number of items to process.
         * - `offset` (int)       : The current starting offset for the batch query.
         * - `index_number` (int) : The index number to start writing to in the sheet.
         * - `batch_size` (int)   : The number of items to process per batch.
         * - `errorCount` (int)   : The number of consecutive errors encountered.
         * @return void
         *
         * @since 1.0.0
         */
        public function ssgsw_process_product_batch($offset_info = [] ) {
            $product = new Product();
            $totalItems = isset($offset_info['totalItems']) ? $offset_info['totalItems'] : ssgsw_count_all_product();
            $offset =  isset($offset_info['offset']) ? $offset_info['offset'] : 0;
            $index_number =  isset($offset_info['index_number']) ? $offset_info['index_number'] : 1;
            $batch_size = isset($offset_info['batch_size']) ? $offset_info['batch_size'] : 1000;
            $errorCount =  isset($offset_info['errorCount']) ? $offset_info['errorCount'] : 0;
            $temp_offset = 0;
            while ($offset < $totalItems) {
                $update = $product->sync_batch_all($offset, $batch_size, $index_number);
                if (is_array($update) && $update['success']) {
                    $index_number += $update['count'];
                    $offset += $batch_size;
                    $temp_offset += $batch_size;
                } else {
                    $errorCount++;
                    if ($errorCount > 3) {
                        $offset += $batch_size;
                        $temp_offset += $batch_size;
                    }
                }
            
                if ($offset < $totalItems && $temp_offset > 3999 ) {

                    wp_schedule_single_event(time() + 5, 'ssgsw_wc_to_sheet_sync_batch', [
                        [
                        'totalItems' => $totalItems,
                        'offset'     => $offset,
                        'index_number' => $index_number,
                        'batch_size'   => $batch_size,
                        'errorCount'   => $errorCount
                        ]
                    ] );
                    return;
                }
            }
            
        }
        /**
         * Clears the scheduled periodic sync event under specific conditions.
         *
         * This function is designed to be hooked into an action that runs regularly
         * (e.g., a short interval cron job or a page load). It checks the current
         * plugin settings and license status. If the 'perodic-sync' mode is
         * active but the license is invalid, it will clear the scheduled
         * 'ssgsw_schedule_sync_mode' cron hook to prevent further syncs.
         *
         * @return void Returns nothing.
         */
        public function clar_event() {
            $selected_sync_mode = ssgsw_get_option('selected_sync_mode','live-sync');
             $peroidic_sync_direction = ssgsw_get_option('peroidic_sync_direction','woocommerce-to-sheets');
            if ($selected_sync_mode == 'perodic-sync' && !ssgsw_is_license_valid()) {
                if ('sheets-to-woocommerce' === $peroidic_sync_direction) {
                    ssgsw_update_option('peroidic_sync_direction', 'woocommerce-to-sheets');
                    ssgsw_update_option('cron_perdic_sync', 10);
                    $this->set_cron();
                }   
            }
        }
        /**
         * Processes a chunk of sheet data for product updates.
         *
         * This function is designed to be triggered by a single scheduled cron event. It receives
         * an array of data, validates it, and then passes the data to the Product class for
         * processing and updating products in the WordPress database.
         *
         * @param array $args The arguments passed to the cron event, which should contain
         * the data chunk to be processed. The expected structure is an
         * array where the first element is the data chunk itself.
         *
         * @return void
         */
        public function ssgsw_process_data_chunk($args) {
             if (empty($args)) {
                return;
            }
            
            $products = [];
            
            if (isset($args['transient_key'])) {
                $products = get_transient($args['transient_key']);
                // Delete transient if retrieval was successful to keep DB clean
                if ($products !== false) {
                    delete_transient($args['transient_key']);
                }
            } elseif (isset($args['products'])) {
                $products = $args['products'];
            }
            
            if (empty($products)) {
                return;
            }
            // error_log(print_r($products, true));

            $product = new Product();
            $product->cron_product_update($products);
        }
        /**
         * Synchronizes data from a Google Sheet to WordPress.
         *
         * This function handles the entire process of fetching data from a Google Sheet,
         * including a complex merge of unformatted values and image formulas. It
         * first fetches all data as values, then separately fetches image URLs via
         * their formulas, and finally merges them into a single, clean dataset.
         * The function also includes logic to dynamically find image columns
         * based on a list of potential header names.
         * * @return void Returns nothing. The function is intended to handle side-effects
         * like processing data, saving to a transient, or logging errors.
         */
        public function sync_sheet_to_wordpress() {
            $new_sheet = new Sheet();
			$image_formulas = $new_sheet->get_formula_value();
			if ( empty($image_formulas) ) {
				$image_formulas = $new_sheet->get_formula_value();
			}
            
            $unformatted_values = $new_sheet->get_sheet_values();
			if ( empty($unformatted_values) ) {
				$unformatted_values = $new_sheet->get_sheet_values();
			}
            

            $image_names = ['Image'];
            $keys = [];
            if (isset($image_formulas[0])) {
                $keys = array_shift($unformatted_values);
                $key2 = array_shift($image_formulas);
            }
            if (empty($keys)) {
                return false;
            }
            if(is_array($unformatted_values) && empty($unformatted_values)) {
                return false;
            }
            if(is_array($image_formulas) && empty($image_formulas)) {
                return false;
            }
            // Use array_map() to iterate over both arrays at the same time.
            $master_data = array_map(function( $unformatted_row, $image_row) use ($keys, $image_names) {
                // Loop through each image column index found.
                $formatted_row = [];
                foreach ($keys as $new_key => $value) {
                    $key = ssgsw_format_sheet_key_function($value);
                    if (in_array($value, $image_names)) {
                        if (isset($image_row[$new_key]) && strpos($image_row[$new_key], '=IMAGE(') === 0) {
                            $image_formula = $image_row[$new_key];
                            $image_url = '';
                            preg_match('/=IMAGE\("(.*?)"\)/', $image_formula, $matches);
                            $image_url = isset($matches[1]) ? $matches[1] : '';
                            // Replace the value in the unformatted data with the extracted URL.
                            $formatted_row[$key] = $image_url;
                        } else {
                            $formatted_row[$key] = '';
                        }
                    } else {
                        $formatted_row[$key] = isset($unformatted_row[$new_key]) ? $unformatted_row[$new_key] : '';
                    }
                }
                return $formatted_row;
            }, $unformatted_values, $image_formulas);
            if(!is_array($master_data)) {
                return false;
            }
            if(empty($master_data)) {
                return false;
            }
            $chunk_size = 2500;
            $total_data_count = count($master_data);
            // print_r($total_data_count);
            $total_chunks = ceil($total_data_count / $chunk_size);
            $delay_time = 5;
            $base_time = time();
            for($i = 0; $i < $total_chunks; $i++) {
                 $chunk = array_slice($master_data, $i * $chunk_size, $chunk_size);
                 
                 // Store chunk in transient to avoid hitting cron argument size limits
                 $transient_key = 'ssgsw_chunk_sync_' . $base_time . '_' . $i . '_' . uniqid();
                 set_transient($transient_key, $chunk, HOUR_IN_SECONDS);

                 wp_schedule_single_event(
                    $base_time + $delay_time,
                    'ssgsw_process_data_chunk',
                    [
                        [
                            'transient_key' => $transient_key
                        ]
                    ]
                );
                $delay_time += 15;
            }
        }

        /**
         * Adds custom recurrence schedules to the WordPress cron system.
         *
         * This function hooks into the `cron_schedules` filter to introduce new time intervals
         * that are not available by default in WordPress. This allows events to be scheduled
         * for specific, shorter periods like every 5, 10, or 30 minutes, as well as a
         * 'monthly' schedule if needed.
         *
         * @param array $schedules An array of existing cron schedules.
         * @return array The filtered array with new custom schedules added.
         */
        public function ssgsw_add_custom_cron_schedules( $schedules ) {

            // Adds an 'every_10_minutes' schedule.
            $schedules['every_10_minutes'] = array(
                'interval' => 10 * MINUTE_IN_SECONDS,
                'display'  => __( 'Every 10 Minutes', 'stock-sync-with-google-sheet-for-woocommerce' ),
            );

            // Adds an 'every_30_minutes' schedule.
            $schedules['every_30_minutes'] = array(
                'interval' => 30 * MINUTE_IN_SECONDS,
                'display'  => __( 'Every 30 Minutes', 'stock-sync-with-google-sheet-for-woocommerce' ),
            );

            // Adds a 'monthly' schedule (if not already defined).
            $schedules['monthly'] = array(
                'interval' => MONTH_IN_SECONDS,
                'display'  => __( 'Once Monthly', 'stock-sync-with-google-sheet-for-woocommerce' ),
            );

            return $schedules;
        }
        /**
         * Sets up or updates the periodic sync cron job.
         *
         * This function handles the core logic for scheduling a recurring sync event
         * based on user-selected settings. It first clears any existing schedules for the
         * 'ssgsw_schedule_sync_mode' hook to prevent duplicate events. It then checks if
         * 'Live Sync' mode is active; if so, no periodic sync is needed, and the function
         * returns.
         *
         * If 'Periodic Sync' is enabled, the function determines the selected recurrence
         * type (e.g., 'daily', 'weekly', or a numeric interval like 5 or 30 minutes) and
         * calculates the appropriate start time. It then schedules the new event using
         * `wp_schedule_event` with the correct interval and a start timestamp that ensures
         * the first run occurs at the intended time.
         *
         * @return void
         */
        public function set_cron() {
            // Clears any previously scheduled cron events for this hook.
            wp_clear_scheduled_hook( 'ssgsw_schedule_sync_mode' );


            // Get the selected sync mode from the options.
            $enabled = ssgsw_get_option( 'selected_sync_mode', 'live-sync' );
            $sync_directions = ssgsw_get_option( 'peroidic_sync_direction', 'woocommerce-to-sheets' );
            
            // If Live Sync is enabled, no periodic sync is needed, so we return.
            if ( 'live-sync' === $enabled ) {
                return;
            }
            if (!ssgsw_is_license_valid()) {
               if ('sheets-to-woocommerce' == $sync_directions) {
                    return;
               }
            }

            // Get the chosen periodic sync recurrence from the options.
            $cron_sync_recurrence = ssgsw_get_option( 'cron_perdic_sync', '10' );
            $perdic_sync_day = ssgsw_get_option( 'perdic_sync_day', 'sunday' );
        

            // Maps the option value to the corresponding cron schedule name.
            switch ( $cron_sync_recurrence ) {
                case '10':
                    $cron_schedule_name = 'every_10_minutes';
                    break;
                case '30':
                    $cron_schedule_name = 'every_30_minutes';
                    break;
                case 'hourly':
                    $cron_schedule_name = 'hourly';
                    break;
                case 'daily':
                    $cron_schedule_name = 'daily';
                    break;
                case 'weekly':
                    $cron_schedule_name = 'weekly';
                    break;
                case 'monthly':
                    $cron_schedule_name = 'monthly';
                    break;
                default:
                    // Fallback to daily if an invalid recurrence is found.
                    $cron_schedule_name = 'daily';
                    break;
            }

            // Determine the start timestamp for the event.
            // For minute-based intervals, we start from the current time.
            if ( in_array( $cron_sync_recurrence, array('10', '30', 'hourly' ) ) ) {
                $timestamp = current_time( 'timestamp', true );
            } else {
                // For daily, weekly, or monthly, we use a specific time (e.g., 4 AM).
                $sync_time_hour = intval( ssgsw_get_option( 'perdic_sync_time', 4 ) );
              
                $timestamp      = $this->next_sync_timestamp( $sync_time_hour, 0, $cron_sync_recurrence, $perdic_sync_day );
            }
            
            // Schedule the recurring event.
            if ( ! wp_next_scheduled( 'ssgsw_schedule_sync_mode' ) ) {
                wp_schedule_event( $timestamp, $cron_schedule_name, 'ssgsw_schedule_sync_mode' );
            }
        }
        /**
         * Calculates the timestamp for the next scheduled sync event in UTC.
         *
         * This function determines the correct timestamp for the next sync based on the
         * chosen recurrence type (e.g., daily, weekly) and a specified hour and minute.
         * For numeric recurrence types (e.g., 5, 10, 30 minutes), it returns the current
         * UTC timestamp, as the scheduling logic for these intervals is handled
         * by the caller function.
         *
         * @param int    $hour            The target hour for the sync in 24-hour format (0-23). Defaults to 15.
         * @param int    $minute          The target minute for the sync (0-59). Defaults to 0.
         * @param string|int $recurrence_type A string like 'daily', 'weekly', 'monthly', or an integer
         * representing a custom interval in minutes (e.g., '5' for 5 minutes).
         * Defaults to 'daily'.
         * @return int The Unix timestamp (in UTC) for the next scheduled sync event.
         */
	    public function next_sync_timestamp($hour = 15, $minute = 0, $recurrence_type = 'daily', $sync_day = 'sunday') {
            $now_utc = current_time('timestamp', true); // Get current UTC timestamp.

            // If the recurrence is a numeric value, handle it separately.
            if (is_numeric($recurrence_type)) {
                return $now_utc;
            }

            // Base timestamp for today at the specified hour and minute (UTC).
            $target_today_timestamp = mktime(
                $hour,
                $minute,
                0,
                gmdate('n', $now_utc),
                gmdate('j', $now_utc),
                gmdate('Y', $now_utc)
            );

            $next_timestamp = $target_today_timestamp;

            switch ($recurrence_type) {
                case 'hourly':
                    // If the target time for the current hour has passed, schedule for the next hour.
                    if ($next_timestamp <= $now_utc) {
                        $next_timestamp = strtotime('+1 hour', $target_today_timestamp);
                    }
                    break;
                case 'daily':
                    // If the target time for today has passed, schedule for tomorrow.
                    if ($next_timestamp <= $now_utc) {
                        $next_timestamp = strtotime('+1 day', $target_today_timestamp);
                    }
                    break;
                case 'weekly':
                    // If the target time for the current day of the week has passed, schedule for next week.
                        $target_day_timestamp = strtotime("$sync_day $hour:$minute:00", $now_utc);
                        // If the target time for the current week has already passed, schedule for next week.
                        if ($target_day_timestamp <= $now_utc) {
                            $next_timestamp = strtotime("+1 week", $target_day_timestamp);
                        } else {
                            $next_timestamp = $target_day_timestamp;
                        }
                    break;
                case 'monthly':
                    // For monthly, we schedule for the 1st of the month at the specified time.
                    $target_monthly_timestamp = mktime(
                        $hour,
                        $minute,
                        0,
                        gmdate('n', $now_utc), // Current month
                        1, // Always the 1st of the month
                        gmdate('Y', $now_utc)
                    );

                    if ($target_monthly_timestamp <= $now_utc) {
                        $next_timestamp = strtotime('+1 month', $target_monthly_timestamp);
                    } else {
                        $next_timestamp = $target_monthly_timestamp;
                    }
                    break;
                default:
                    // Fallback for any unknown recurrence type, effectively daily.
                    if ($next_timestamp <= $now_utc) {
                        $next_timestamp = strtotime('+1 day', $target_today_timestamp);
                    }
                    break;
            }
            return $next_timestamp;
        }
	
    }
	// Initiate the class.
	CRON::init();
}
