<?php
defined( 'ABSPATH' ) or die( 'No script kiddies please!' );

/**
 * ==============================================================================
 * 1. REGISTRATION OF CUSTOM CRON SCHEDULES (DYNAMIC INTERVAL)
 * ==============================================================================
 */

/**
 * Adds a custom cron interval based on the plugin's settings.
 *
 * This filter registers a new time interval ('smart_convert_currency_unit_conversion_dynamic_interval') in the WordPress
 * cron system. The duration is pulled from the database options. If the user
 * updates the frequency setting, this function ensures the cron system understands
 * the new interval (in seconds).
 *
 * @param array $schedules Existing WordPress cron schedules.
 * @return array Modified schedules with the new dynamic interval added.
 */
add_filter( 'cron_schedules', 'smart_convert_currency_unit_conversion_add_custom_cron_interval' );

function smart_convert_currency_unit_conversion_add_custom_cron_interval( $schedules ) {
	$options = get_option( 'smart_convert_currency_unit_conversion_option_name' );

	// Retrieve the interval in hours, defaulting to 24 if not set or invalid.
	$hours = isset( $options['smart_convert_currency_unit_conversion_update_interval'] ) ? absint( $options['smart_convert_currency_unit_conversion_update_interval'] ) : 24;

	// Safety check: prevent intervals smaller than 1 hour.
	if ( $hours < 1 ) {
		$hours = 24;
	}

	// Add the calculated interval to the WP system
	$schedules['smart_convert_currency_unit_conversion_dynamic_interval'] = array(
		'interval' => $hours * 3600, // Convert hours to seconds
		'display'  => sprintf(
			/* translators: %d: Number of hours between scheduled updates. */
			__( 'Every %d hours (SCP)', 'smart-convert-currency-unit-conversion' ),
			$hours
		),
	);

	return $schedules;
}

/**
 * ==============================================================================
 * 2. CRON SCHEDULING & EXECUTION HANDLERS
 * ==============================================================================
 */

// Hook for the main recurring event
add_action( 'smart_convert_currency_unit_conversion_update_currency_hook', 'smart_convert_currency_unit_conversion_handle_cron_execution' );

// Hook for the retry event (triggered only if the main event fails)
add_action( 'smart_convert_currency_unit_conversion_update_currency_retry_hook', 'smart_convert_currency_unit_conversion_handle_cron_execution' );

/**
 * Wrapper function for Cron execution to handle updates, logging, and retries.
 * * This serves as the main entry point for the Cron job. It attempts to update
 * the currency rates and handles the outcome:
 * 1. Success: Logs the success (if debug is on) and clears any pending retry hooks.
 * 2. Failure: Logs the specific error and schedules a "retry" event 60 minutes later
 * to ensure the site doesn't stay with outdated rates for too long.
 *
 * @return void
 */
function smart_convert_currency_unit_conversion_handle_cron_execution() {
	// === GDPR / WP REPO COMPLIANCE GUARD ===
	// Strictly check for user consent before initiating any external connection via CRON.
	$options = get_option( 'smart_convert_currency_unit_conversion_option_name' );
	if ( empty( $options['smart_convert_currency_unit_conversion_enable_external_data'] ) || $options['smart_convert_currency_unit_conversion_enable_external_data'] !== '1' ) {
		// Silent exit. Do NOT schedule retries, do NOT log errors.
		// The user has not consented to external data fetching.
		return;
	}

	// Attempt the update
	$result = smart_convert_currency_unit_conversion_update_currency_csv();

	// Check if the result is a WP_Error object or a failure array
	if ( is_wp_error( $result ) || ( is_array( $result ) && $result['success'] === false ) ) {
		// Extract the error message
		$msg = is_wp_error( $result ) ? $result->get_error_message() : 'Unknown error';

		// Only write to the error log if WP_DEBUG is enabled
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			error_log( "SCP Cron: Update failed ($msg). Scheduling retry in 60 mins." );
		}

		// Schedule an emergency retry if one is not already pending
		if ( ! wp_next_scheduled( 'smart_convert_currency_unit_conversion_update_currency_retry_hook' ) ) {
			wp_schedule_single_event( time() + 3600, 'smart_convert_currency_unit_conversion_update_currency_retry_hook' );
		}
	} else {
		// Success logic:
		// Clear any existing retry hooks since the update succeeded.
		wp_clear_scheduled_hook( 'smart_convert_currency_unit_conversion_update_currency_retry_hook' );

		// Log success details if debugging is enabled.
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			error_log( "SCP Cron: Update success via source: " . ( $result['source'] ?? 'unknown' ) );
		}
	}
}

/**
 * Plugin Activation Hook: Sets up the initial Cron schedule.
 * * This logic ensures that:
 * 1. The recurring job is scheduled for the "next day" between 00:01 and 05:00.
 * This random offset prevents server load spikes (thundering herd problem) if
 * many users update or install the plugin simultaneously.
 * 2. An immediate, asynchronous single event is scheduled (10 seconds later) so
 * the user sees data immediately after activation without waiting for the nightly run.
 */
register_activation_hook( dirname( dirname( __FILE__ ) ) . '/index.php', function() {
	$tz       = new DateTimeZone( wp_timezone_string() );
	$tomorrow = new DateTime( 'now', $tz );
	$tomorrow->modify( '+1 day' )->setTime( 0, 0, 0 );

	// Calculate a random offset between 00:01 and 05:00 for the FIRST scheduled recurring run.
	// Using wp_rand instead of rand for better randomness and standard compliance.
	$seconds   = wp_rand( 60, 5 * 60 * 60 );
	$first_run = $tomorrow->getTimestamp() + $seconds;

	// Clear any existing hooks to prevent duplicates during reactivation.
	wp_clear_scheduled_hook( 'smart_convert_currency_unit_conversion_update_currency_hook' );
	wp_clear_scheduled_hook( 'smart_convert_currency_unit_conversion_update_currency_retry_hook' );

	// Schedule the recurring event using our dynamic interval
	wp_schedule_event( $first_run, 'smart_convert_currency_unit_conversion_dynamic_interval', 'smart_convert_currency_unit_conversion_update_currency_hook' );

	// Schedule an immediate single run (asynchronous) to populate data right now.
	if ( ! wp_next_scheduled( 'smart_convert_currency_unit_conversion_update_currency_hook' ) ) {
		wp_schedule_single_event( time() + 10, 'smart_convert_currency_unit_conversion_update_currency_hook' );
	}
} );

/**
 * Watchdog / Self-Healing Mechanism.
 * * Runs on WordPress 'init'. Checks if the main currency update hook is scheduled.
 * If missing (e.g., inadvertently cleared by another plugin or a settings save),
 * this function reschedules it.
 * * It attempts to maintain the "nightly" update rhythm by targeting 00:01 - 05:00
 * for the next run, rather than starting the interval immediately from the current time.
 */
add_action( 'init', function () {
	if ( ! wp_next_scheduled( 'smart_convert_currency_unit_conversion_update_currency_hook' ) ) {
		// Prepare timestamp for tomorrow at midnight
		$tz       = new DateTimeZone( wp_timezone_string() );
		$tomorrow = new DateTime( 'now', $tz );
		$tomorrow->modify( '+1 day' )->setTime( 0, 0, 0 );

		// Apply random offset (00:01 - 05:00) using wp_rand
		$seconds   = wp_rand( 60, 5 * 60 * 60 );
		$first_run = $tomorrow->getTimestamp() + $seconds;

		// Reschedule the event
		wp_schedule_event( $first_run, 'smart_convert_currency_unit_conversion_dynamic_interval', 'smart_convert_currency_unit_conversion_update_currency_hook' );
	}
} );

/**
 * Plugin Deactivation Hook.
 * * Cleans up the scheduler by removing all custom cron jobs associated with this plugin.
 */
register_deactivation_hook( dirname( dirname( __FILE__ ) ) . '/index.php', function() {
	wp_clear_scheduled_hook( 'smart_convert_currency_unit_conversion_update_currency_hook' );
	wp_clear_scheduled_hook( 'smart_convert_currency_unit_conversion_update_currency_retry_hook' );
} );


/**
 * ==============================================================================
 * 3. CORE UPDATE LOGIC (CURRENCIES & UNITS)
 * ==============================================================================
 */

/**
 * Updates currency rates in the database.
 *
 * This function performs the following steps:
 * 1. Fetches CSV data using a robust fetcher (handles multiple sources/failover).
 * 2. Fetches EXISTING data from DB to use as fallback.
 * 3. Merges new data: If new rate is 0, keeps old rate. If > 0, updates.
 * 4. Starts a Database Transaction.
 * 5. Deletes old rates and inserts the merged (safe) dataset.
 * 6. Commits the transaction.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 * @return array|WP_Error Array with success details on success, WP_Error on failure.
 */
function smart_convert_currency_unit_conversion_update_currency_csv() {
	global $wpdb;
	$table = $wpdb->prefix . 'smart_convert_currency_unit_conversion_currencies';
	$table_escaped = '`' . esc_sql( $table ) . '`';

	// 1) Fetch CSV data using the Robust Fetcher with Failover mechanism.
	// The constant SMART_CONVERT_CURRENCY_SOURCES is defined in smart-convert-currency-unit-conversion-database.php.
	$fetch_result = smart_convert_currency_unit_conversion_fetch_csv_data_robust( SMART_CONVERT_CURRENCY_SOURCES );

	// Validate the result. If all sources failed, return an error.
	if ( empty( $fetch_result ) || ! isset( $fetch_result['data'] ) ) {
		return new WP_Error( 'smart_convert_currency_unit_conversion_csv_empty', 'Rates update failed. All sources (Redirect, Local, Direct) failed or returned invalid data.' );
	}

	$csv_data    = $fetch_result['data'];
	$source_used = $fetch_result['source'];

	// =============================================================================
	// 2. FETCH OLD DATA (SNAPSHOT)
	// =============================================================================
	// Before we delete anything, we must remember the current state of the database.
	// This allows us to fallback to old values if the new CSV contains zeros.
	
	// phpcs:ignore WordPress.DB.DirectDatabaseQuery
	// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is internal (prefix + constant suffix).
	$old_rows = $wpdb->get_results( "SELECT from_currency, to_currency, rate, symbol_before, symbol_after, updated_at FROM {$table_escaped}", ARRAY_A );
	
	$old_data_map = [];
	if ( ! empty( $old_rows ) ) {
		foreach ( $old_rows as $old_row ) {
			// Create a unique key for fast lookup: "FROM|TO"
			$key = $old_row['from_currency'] . '|' . $old_row['to_currency'];
			$old_data_map[ $key ] = $old_row;
		}
	}

	// =============================================================================
	// 3. PREPARE NEW DATA (MERGE LOGIC)
	// =============================================================================
	// We construct the final list of rows to insert in PHP memory first.
	// Logic:
	// - If new rate > 0: Use NEW data + NEW timestamp.
	// - If new rate == 0: Use OLD data + OLD timestamp (if exists).
	
	$final_rows_to_insert = [];
	$current_mysql_time   = current_time( 'mysql', 1 );

	foreach ( $csv_data as $new_row ) {
		$from = isset( $new_row['from'] ) ? sanitize_text_field( $new_row['from'] ) : '';
		$to   = isset( $new_row['to'] ) ? sanitize_text_field( $new_row['to'] ) : '';
		
		if ( empty( $from ) || empty( $to ) ) {
			continue;
		}

		$key = $from . '|' . $to;

		// Parse and normalize new values
		$new_rate = isset( $new_row['rate'] ) ? (float) str_replace( ',', '.', $new_row['rate'] ) : 0.0;

		// Determine which data to use
		if ( $new_rate > 0 ) {
			// SCENARIO 1 & 2: New data is valid.
			// Action: Use NEW data, update timestamp.
			// We use number_format to ensure 16 decimal places as a string, preventing PHP float truncation.
			$final_rows_to_insert[] = [
				'from_currency' => $from,
				'to_currency'   => $to,
				'rate'          => number_format( $new_rate, 16, '.', '' ),
				'symbol_before' => isset( $new_row['symbol_before'] ) ? sanitize_text_field( $new_row['symbol_before'] ) : '',
				'symbol_after'  => isset( $new_row['symbol_after'] ) ? sanitize_text_field( $new_row['symbol_after'] ) : '',
				'updated_at'    => $current_mysql_time, // New timestamp
			];
		} else {
			// SCENARIO 3 & 4: New data is invalid (0.00).
			// Action: Check if we have old data to salvage.
			
			if ( isset( $old_data_map[ $key ] ) ) {
				// We have old data. Keep it!
				// This covers Scenario 4 (Old valid -> New 0) and Scenario 3 (Old 0 -> New 0).
				$preserved_row = $old_data_map[ $key ];
				
				$final_rows_to_insert[] = [
					'from_currency' => $preserved_row['from_currency'],
					'to_currency'   => $preserved_row['to_currency'],
					'rate'          => number_format( (float) $preserved_row['rate'], 16, '.', '' ), // Re-format old data safely
					'symbol_before' => isset( $new_row['symbol_before'] ) ? sanitize_text_field( $new_row['symbol_before'] ) : $preserved_row['symbol_before'],
					'symbol_after'  => isset( $new_row['symbol_after'] ) ? sanitize_text_field( $new_row['symbol_after'] ) : $preserved_row['symbol_after'],
					'updated_at'    => $preserved_row['updated_at'], // KEEP OLD TIMESTAMP (Important for data transparency)
				];
				
				// Debug log for preserved rates (Optional)
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					// error_log( "Smart Convert: Preserved old rate for $from->$to due to zero in source." );
				}
			} else {
				// New is 0, Old doesn't exist.
				// Action: Skip completely. Do not insert 0.
				continue;
			}
		}
	}

	// =============================================================================
	// 4. EXECUTE DATABASE UPDATE
	// =============================================================================
	
	// phpcs:ignore WordPress.DB.DirectDatabaseQuery
	$wpdb->query( 'START TRANSACTION' );

	// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter
	$wpdb->query( "DELETE FROM $table" );

	$inserted = 0;
	$all_ok   = true;

	foreach ( $final_rows_to_insert as $row ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
            $res = $wpdb->insert(
			$table,
			$row,
			// Use %s for rate because we passed a pre-formatted string with high precision.
			// MySQL DECIMAL column handles the string-to-number conversion correctly.
			[ '%s', '%s', '%s', '%s', '%s', '%s' ]
		);

		if ( false === $res ) {
			$all_ok = false;
			break;
		}
		$inserted++;
	}

	// 5) Commit or Rollback based on success.
	if ( $all_ok && $inserted > 0 ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$wpdb->query( 'COMMIT' );

		// Update the timestamp option for UI display
		// Note: We use current time here as the "job run time", even if some individual rates are old.
		update_option( 'smart_convert_currency_unit_conversion_last_rates_update_time', strtotime( $current_mysql_time ) );

		// Return structured success data for Admin/Logs
		return [
			'success' => true,
			'source'  => $source_used,
			'rows'    => $inserted,
			'time'    => $current_mysql_time,
		];
	} else {
		// If something went wrong, revert all changes to keep previous data intact.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$wpdb->query( 'ROLLBACK' );
		return new WP_Error( 'smart_convert_currency_unit_conversion_db_insert_failed', 'DB Insert failed during transaction.' );
	}
}


// === UNIT IMPORT HANDLER (AUTO OR FORCED) ===
add_action( 'init', 'smart_convert_currency_unit_conversion_update_units_csv_once' );

/**
 * Imports unit conversion factors into the database.
 * * Typically runs once on 'init' to populate the table if it is empty.
 * Can be forced to overwrite existing data (e.g., during plugin updates or manual resets).
 *
 * @param bool $force If true, deletes existing data and forces a fresh import. Default false.
 * @return array|WP_Error|void Returns result array if update occurred, null if skipped.
 */
function smart_convert_currency_unit_conversion_update_units_csv_once( $force = false ) {
	global $wpdb;
	$table = $wpdb->prefix . 'smart_convert_currency_unit_conversion_units';

	// Optimization: Check for existence of DATA (rows).
	// If data exists and we are not forcing an update, exit early.
	if ( ! $force ) {
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter
		$count = $wpdb->get_var( "SELECT COUNT(*) FROM $table" );
		if ( $count > 0 ) {
			return; // Already populated, nothing to do.
		}
	}

	// === GDPR / WP REPO COMPLIANCE GUARD ===
	// If this is an automated check (not forced manually), ensure consent exists.
	if ( ! $force ) {
		$options = get_option( 'smart_convert_currency_unit_conversion_option_name' );
		if ( empty( $options['smart_convert_currency_unit_conversion_enable_external_data'] ) || $options['smart_convert_currency_unit_conversion_enable_external_data'] !== '1' ) {
			// Exit without error. The table remains empty until user consents via Onboarding/Settings.
			return;
		}
	}

	// Fetch data using the Robust Failover mechanism
	$fetch_result = smart_convert_currency_unit_conversion_fetch_csv_data_robust( SMART_CONVERT_UNITS_SOURCES );

	if ( $fetch_result && isset( $fetch_result['data'] ) && count( $fetch_result['data'] ) > 0 ) {
		$data = $fetch_result['data'];

		// Use Transaction if forcing update to ensure we safely replace old data
		if ( $force ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery
			$wpdb->query( 'START TRANSACTION' );
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter
			$wpdb->query( "DELETE FROM $table WHERE is_custom = 0" ); // Only delete builtin units
		}

		$inserted = 0;
		foreach ( $data as $row ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery
			$res = $wpdb->insert(
				$table,
				[
                    'unit_from'    => isset( $row['from'] ) ? sanitize_text_field( $row['from'] ) : '',
                    'unit_to'      => isset( $row['to'] ) ? sanitize_text_field( $row['to'] ) : '',
                    'factor'       => isset( $row['factor'] ) ? (float) str_replace( ',', '.', $row['factor'] ) : 1, // Float cast is validation
                    'offset'       => isset( $row['offset'] ) ? (float) str_replace( ',', '.', $row['offset'] ) : 0, // Float cast is validation
                    'symbol_after' => isset( $row['symbol_after'] ) ? sanitize_text_field( $row['symbol_after'] ) : '',
					'updated_at'   => current_time( 'mysql', 1 ),
					'is_custom'    => 0 // Mark as system unit
				]
			);
			if ( $res ) {
				$inserted++;
			}
		}

		if ( $force ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery
			$wpdb->query( 'COMMIT' );
		}

		return [
			'success' => true,
			'rows'    => $inserted,
			'source'  => $fetch_result['source'],
		];
	}

	return new WP_Error( 'units_fetch_failed', 'Could not fetch units from any source.' );
}