<?php
/**
 * Cache warming.
 *
 * The first request in a given currency has to fetch that currency's exchange rate from the API,
 * which costs a couple of seconds. A scheduled job keeps the rates (and each product's price data)
 * primed so no visitor ever pays that cost.
 *
 * Rather than warming all 52 currencies — most of which never see traffic — we warm the default
 * currency plus the ones visitors have actually been served recently.
 *
 * @package BW_2Checkout_Pricing
 */

defined( 'ABSPATH' ) || exit;

const BW_2CHECKOUT_PRICING_WARM_HOOK = 'bw_2checkout_pricing_warm';

/**
 * Record that a currency was served, so the warmer knows to keep it primed.
 * Writes at most once a day per currency.
 *
 * @param string $currency ISO code.
 * @return void
 */
function bw_2checkout_pricing_note_currency( $currency ) {
	$currency = strtoupper( (string) $currency );
	if ( '' === $currency ) {
		return;
	}
	$seen = get_option( 'bw_2checkout_pricing_seen_currencies', array() );
	if ( ! is_array( $seen ) ) {
		$seen = array();
	}
	$now = time();
	if ( isset( $seen[ $currency ] ) && ( $now - (int) $seen[ $currency ] ) < DAY_IN_SECONDS ) {
		return;
	}
	$seen[ $currency ] = $now;

	// Forget currencies that haven't been seen in a month so the job doesn't grow forever.
	foreach ( $seen as $code => $ts ) {
		if ( ( $now - (int) $ts ) > 30 * DAY_IN_SECONDS ) {
			unset( $seen[ $code ] );
		}
	}
	update_option( 'bw_2checkout_pricing_seen_currencies', $seen, false );
}

/**
 * Currencies the warmer should keep primed: the default plus recently served ones.
 *
 * @return string[]
 */
function bw_2checkout_pricing_warm_currencies() {
	$list = array( strtoupper( (string) bw_2checkout_pricing_get_setting( 'default_currency', 'USD' ) ) );

	$seen = get_option( 'bw_2checkout_pricing_seen_currencies', array() );
	if ( is_array( $seen ) ) {
		$list = array_merge( $list, array_keys( $seen ) );
	}

	$allowed = bw_2checkout_pricing_allowed_currencies();
	$list    = array_values( array_unique( array_filter( $list ) ) );
	if ( $allowed ) {
		$list = array_values( array_intersect( $list, $allowed ) );
	}
	return $list;
}

/**
 * Prime the caches for every configured product in every currency worth warming.
 *
 * @return array{products:int,currencies:int,ok:int,failed:int}
 */
function bw_2checkout_pricing_warm_cache() {
	$products   = bw_2checkout_pricing_get_products();
	$currencies = bw_2checkout_pricing_warm_currencies();
	$pricing    = new BW_2Checkout_Pricing_Pricing();

	$ok     = 0;
	$failed = 0;
	foreach ( array_keys( $products ) as $pid ) {
		foreach ( $currencies as $currency ) {
			if ( $pricing->warm( $pid, $currency ) ) {
				++$ok;
			} else {
				++$failed;
			}
		}
	}

	update_option(
		'bw_2checkout_pricing_last_warm',
		array(
			'time'       => time(),
			'ok'         => $ok,
			'failed'     => $failed,
			'currencies' => $currencies,
		),
		false
	);

	return array(
		'products'   => count( $products ),
		'currencies' => count( $currencies ),
		'ok'         => $ok,
		'failed'     => $failed,
	);
}
add_action( BW_2CHECKOUT_PRICING_WARM_HOOK, 'bw_2checkout_pricing_warm_cache' );

/**
 * Keep the hourly warm job scheduled. Runs cheaply when everything is already cached — it only
 * does real work once an entry has expired.
 *
 * @return void
 */
function bw_2checkout_pricing_schedule_warm() {
	if ( ! wp_next_scheduled( BW_2CHECKOUT_PRICING_WARM_HOOK ) ) {
		wp_schedule_event( time() + 300, 'hourly', BW_2CHECKOUT_PRICING_WARM_HOOK );
	}
}
add_action( 'init', 'bw_2checkout_pricing_schedule_warm' );

/**
 * Clear the schedule when the plugin is deactivated.
 *
 * @return void
 */
function bw_2checkout_pricing_unschedule_warm() {
	$ts = wp_next_scheduled( BW_2CHECKOUT_PRICING_WARM_HOOK );
	if ( $ts ) {
		wp_unschedule_event( $ts, BW_2CHECKOUT_PRICING_WARM_HOOK );
	}
}
