<?php
/**
 * File Path: includes/import/class-mbs-amazon-paapi-request.php
 *
 * Amazon PA-API v5 — Request Builder & AWS Sig v4 Signer
 *
 * Pure-static class that constructs JSON request bodies and computes the
 * full AWS Signature Version 4 Authorization header set required by
 * PA-API v5. Extracted from MBS_Amazon_PAAPI (R-04, 2026-04-25).
 *
 * SECURITY: $secret_key is used only inside build_signed_headers() for HMAC
 * key derivation. It is never written to any log, option, or transient.
 * See SECURITY.md §4.
 *
 * @package    My_Book_Showroom
 * @subpackage Includes/Import
 * @since      1.0.0
 */

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

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

	// ── PA-API v5 endpoint constants ───────────────────────────────────────────

	/** @var string Amazon host — credentials go HERE and nowhere else. */
	public const HOST = 'webservices.amazon.com';

	/** @var string AWS region for PA-API. */
	private const REGION = 'us-east-1';

	/** @var string AWS service name used in Sig v4 signing. */
	private const SERVICE = 'ProductAdvertisingAPI';

	/**
	 * Response groups (Resources) requested from PA-API v5.
	 *
	 * Keep this list minimal — each group adds latency. The fields extracted in
	 * MBS_Amazon_PAAPI_Parser::parse_item() must be covered by at least one entry here.
	 *
	 * @var string[]
	 */
	private const RESOURCES = array(
		'Images.Primary.Large',
		'ItemInfo.ByLineInfo',
		'ItemInfo.Classifications',
		'ItemInfo.ContentInfo',
		'ItemInfo.ExternalIds',
		'ItemInfo.ManufactureInfo',
		'ItemInfo.Title',
		'Offers.Listings.Price',
	);

	// ── Public static methods ──────────────────────────────────────────────────

	/**
	 * Build the JSON body for a PA-API v5 GetItems request (ASIN lookup).
	 *
	 * @param string $asin    Uppercase 10-char ASIN.
	 * @param string $partner Associate tag.
	 * @return string JSON-encoded body.
	 */
	public static function build_get_items_body( string $asin, string $partner ): string {
		return (string) wp_json_encode(
			array(
				'ItemIds'     => array( $asin ),
				'Resources'   => self::RESOURCES,
				'PartnerTag'  => $partner,
				'PartnerType' => 'Associates',
				'Marketplace' => 'www.amazon.com',
			)
		);
	}

	/**
	 * Build the JSON body for a PA-API v5 SearchItems request (ISBN-13 lookup).
	 *
	 * PA-API v5 GetItems only accepts ASINs; an ISBN lookup must go through
	 * SearchItems using the ISBN digits as the Keywords value.
	 *
	 * @param string $isbn13  13-digit ISBN string (digits only).
	 * @param string $partner Associate tag.
	 * @return string JSON-encoded body.
	 */
	public static function build_search_items_body( string $isbn13, string $partner ): string {
		return (string) wp_json_encode(
			array(
				'Keywords'    => $isbn13,
				'SearchIndex' => 'Books',
				'ItemCount'   => 1,
				'Resources'   => self::RESOURCES,
				'PartnerTag'  => $partner,
				'PartnerType' => 'Associates',
				'Marketplace' => 'www.amazon.com',
			)
		);
	}

	/**
	 * Build the complete set of HTTP headers including the AWS Sig v4 Authorization.
	 *
	 * Implements AWS Signature Version 4 exactly as required by PA-API v5.
	 * Reference: https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
	 *
	 * SECURITY: $secret_key is used only for HMAC derivation and is never written
	 * to any log, option, or transient inside this method. See SECURITY.md §4.
	 *
	 * @param string $access_key  Decrypted PA-API Access Key.
	 * @param string $secret_key  Decrypted PA-API Secret Key.
	 * @param string $path        Request path (e.g. '/paapi5/getitems').
	 * @param string $target      X-Amz-Target value for this operation.
	 * @param string $body        JSON-encoded request body.
	 * @param int    $timestamp   Unix timestamp for the request.
	 * @return array<string,string> Headers to pass to wp_remote_post().
	 */
	public static function build_signed_headers(
		string $access_key,
		string $secret_key,
		string $path,
		string $target,
		string $body,
		int $timestamp
	): array {

		$datetime = gmdate( 'Ymd\THis\Z', $timestamp );
		$date     = gmdate( 'Ymd', $timestamp );

		// ── Step 1: Canonical headers (sorted alphabetically by header name) ───
		// PA-API v5 requires these five headers to be signed.

		$canonical_headers =
			'content-encoding:amz-1.0' . "\n" .
			'content-type:application/json; charset=utf-8' . "\n" .
			'host:' . self::HOST . "\n" .
			'x-amz-date:' . $datetime . "\n" .
			'x-amz-target:' . $target . "\n";

		$signed_headers = 'content-encoding;content-type;host;x-amz-date;x-amz-target';

		// ── Step 2: Canonical request ──────────────────────────────────────────

		$payload_hash = hash( 'sha256', $body );

		$canonical_request = implode( "\n", array(
			'POST',
			$path,
			'',                   // No query string.
			$canonical_headers,   // Already ends with \n — produces blank line after.
			$signed_headers,
			$payload_hash,
		) );

		// ── Step 3: String to sign ─────────────────────────────────────────────

		$credential_scope = implode( '/', array( $date, self::REGION, self::SERVICE, 'aws4_request' ) );

		$string_to_sign = implode( "\n", array(
			'AWS4-HMAC-SHA256',
			$datetime,
			$credential_scope,
			hash( 'sha256', $canonical_request ),
		) );

		// ── Step 4: Signing key (derived from secret — binary HMAC chain) ──────

		$k_date    = hash_hmac( 'sha256', $date,           'AWS4' . $secret_key, true );
		$k_region  = hash_hmac( 'sha256', self::REGION,    $k_date,              true );
		$k_service = hash_hmac( 'sha256', self::SERVICE,   $k_region,            true );
		$k_signing = hash_hmac( 'sha256', 'aws4_request',  $k_service,           true );

		// ── Step 5: Signature (hex, not binary) ────────────────────────────────

		$signature = hash_hmac( 'sha256', $string_to_sign, $k_signing );

		// ── Step 6: Authorization header ──────────────────────────────────────

		$authorization = sprintf(
			'AWS4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s',
			$access_key,
			$credential_scope,
			$signed_headers,
			$signature
		);

		return array(
			'Authorization'    => $authorization,
			'Content-Encoding' => 'amz-1.0',
			'Content-Type'     => 'application/json; charset=utf-8',
			'Host'             => self::HOST,
			'X-Amz-Date'      => $datetime,
			'X-Amz-Target'    => $target,
		);
	}
}
