<?php
/**
 * @file    includes/import/class-mbs-pa-api-sync.php
 * Scheduled PA-API price/rating sync — writes to mbs_pa_api_cache via DAO.
 * @package MyBookShowroom
 * @since   1.1.0
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class MBS_PA_API_Sync { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	/**
	 * Iterate every published mbs_book, query stale ASINs against PA-API,
	 * and write results to cache.
	 *
	 * SECURITY: Credentials are decrypted in-memory via MBS_Encryption::decrypt().
	 * They are NEVER passed to error_log(), stored raw, or sent to www.dmrstech.com.
	 * On PA-API error only the WP_Error code is logged — never the request payload.
	 *
	 * @return void
	 */
	public function run(): void {

		// PRO gate — sync is a PRO feature.
		if ( ! mbs_is_pro() ) {
			return;
		}

		// ── 1. Decrypt PA-API credentials ──────────────────────────────────────────
		// Credentials stay in local variables only. Never logged, never transmitted
		// to www.dmrstech.com. (SECURITY.md §4, Sprint 15 Rule R7)

		$access_key = mbs_decrypt_option( 'mbs_setting_import_amazon_api_key_encrypted' );
		$secret_key = mbs_decrypt_option( 'mbs_setting_import_amazon_api_secret_encrypted' );
		$partner = (string) get_option( 'mbs_setting_amazon_partner_tag', '' );

		if ( empty( $access_key ) || empty( $secret_key ) || empty( $partner ) ) {
			// Credentials absent — skip silently. No error thrown per spec.
			return;
		}

		// ── 2. Collect all published mbs_book post IDs ───────────────────────────

		$post_ids = get_posts( array(
			'post_type'      => MBS_POST_TYPE,
			'post_status'    => 'publish',
			'posts_per_page' => -1,
			'fields'         => 'ids',
		) );

		if ( empty( $post_ids ) ) {
			return;
		}

		// ── 3. Gather unique ASINs across all format rows ────────────────────────

		$seen_asins = array();

		foreach ( $post_ids as $post_id ) {
			$rows = MBS_Format_Row_DAO::get_by_post( (int) $post_id );
			foreach ( $rows as $row ) {
				$asin = isset( $row->asin ) ? trim( (string) $row->asin ) : '';
				if ( '' !== $asin && ! isset( $seen_asins[ $asin ] ) ) {
					$seen_asins[ $asin ] = true;
				}
			}
		}

		if ( empty( $seen_asins ) ) {
			return;
		}

		// ── 4. Sync each stale ASIN ────────────────────────────────────────────────────

		$total         = 0;
		$failure_count = 0;

		foreach ( array_keys( $seen_asins ) as $asin ) {

			if ( ! MBS_PA_API_Cache_DAO::is_stale( $asin ) ) {
				continue;
			}

			++$total;

			$result = MBS_Amazon_PAAPI::lookup( $asin, $access_key, $secret_key, $partner );
			usleep( 1100000 ); // V8-D-002: Amazon PA-API rate limit: ~1 req/sec. 1.1s provides 10% buffer.

			if ( is_wp_error( $result ) ) {
				// Log error CODE only — never the payload, never credentials.
				// (SECURITY.md §4, Sprint 15 Rule R7)
				MBS_Telemetry_Failure_Tracker::record_import_failure( 'paapi_sync' ); // F-006
				++$failure_count;
				continue;
			}

			if ( false === $result ) {
				// Clean miss — item not found on Amazon; not an error.
				continue;
			}

			// Map lookup() result keys to cache columns.
			$cache_data = array();

			if ( isset( $result['price'] ) && '' !== (string) $result['price'] ) {
				$cache_data['price_amount']   = (float) $result['price'];
				$cache_data['price_currency'] = 'USD'; // US marketplace only.
			}

			// star_rating, review_count, availability are not in the current
			// PA-API Resources list — stored as NULL until a future sprint adds them.

			if ( ! empty( $cache_data ) ) {
				MBS_PA_API_Cache_DAO::upsert( $asin, $cache_data );
			}
		}

		// V8-D-004: Surface admin notice when entire sync batch fails.
		if ( $total > 0 && $failure_count >= $total ) {
			set_transient( 'mbs_pa_api_sync_all_failed', array(
				'count'     => $failure_count,
				'timestamp' => time(),
			), HOUR_IN_SECONDS );
		}
	}
}
