<?php
/**
 * File Path: includes/import/class-mbs-amazon-paapi.php
 *
 * Amazon Product Advertising API v5 — HTTP Client
 *
 * Signs and executes authenticated requests to Amazon PA-API v5 using AWS
 * Signature Version 4. Returns structured book-metadata arrays consumed by
 * MBS_Importer_Amazon::fetch().
 *
 * Credentials are decrypted by the caller (MBS_Importer_Amazon::fetch()) and
 * passed in as plain strings. This class does NOT store, cache, log, or
 * re-transmit them anywhere other than webservices.amazon.com (SECURITY.md §4).
 *
 * Public surface: one static method — lookup().
 *
 * @package    My_Book_Showroom
 * @subpackage Includes/Import
 * @since      1.0.0
 */

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

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

	// ── PA-API v5 path / target / timeout constants ────────────────────────────

	/** @var string PA-API GetItems path (lookup by ASIN). */
	private const PATH_GET_ITEMS = '/paapi5/getitems';

	/** @var string PA-API SearchItems path (lookup by ISBN-13 keyword). */
	private const PATH_SEARCH_ITEMS = '/paapi5/searchitems';

	/** @var string X-Amz-Target header value for GetItems. */
	private const TARGET_GET_ITEMS = 'com.amazon.paapi5.v1.ProductAdvertisingAPIv1.GetItems';

	/** @var string X-Amz-Target header value for SearchItems. */
	private const TARGET_SEARCH_ITEMS = 'com.amazon.paapi5.v1.ProductAdvertisingAPIv1.SearchItems';

	/** @var int wp_remote_post() timeout in seconds. */
	private const TIMEOUT = 15;

	// ── Public API ─────────────────────────────────────────────────────────────

	/**
	 * Look up a single book by ASIN or ISBN-13 via PA-API v5.
	 *
	 * Returns a metadata array keyed to what MBS_Importer_Amazon::normalise_candidate()
	 * expects, or FALSE when the item is not found on Amazon, or a WP_Error on any
	 * network, credential, or API failure.
	 *
	 * SECURITY: $access_key and $secret_key are NEVER logged. See SECURITY.md §4.
	 *
	 * @param string $code        ASIN (10 alphanumeric) or ISBN-13 (13 digits).
	 * @param string $access_key  Decrypted PA-API Access Key — caller responsibility.
	 * @param string $secret_key  Decrypted PA-API Secret Key — caller responsibility.
	 * @param string $partner     Amazon Associate tag.
	 * @return array|false|\WP_Error
	 *   array  — hit; contains at minimum 'title'; all other keys are optional.
	 *   false  — clean miss (item not found on Amazon; not an error).
	 *   WP_Error — network failure, bad credentials, malformed response, etc.
	 */
	public static function lookup(
		string $code,
		string $access_key,
		string $secret_key,
		string $partner
	): array|false|\WP_Error {

		// ── 1. Validate inputs ─────────────────────────────────────────────────

		$code       = trim( $code );
		$access_key = trim( $access_key );
		$secret_key = trim( $secret_key );
		$partner    = trim( $partner );

		if ( empty( $access_key ) || empty( $secret_key ) || empty( $partner ) ) {
			return new \WP_Error(
				'mbs_paapi_no_credentials',
				__( 'Amazon PA-API credentials are missing.', 'my-book-showroom' )
			);
		}

		if ( empty( $code ) ) {
			return new \WP_Error(
				'mbs_paapi_empty_code',
				__( 'No ASIN or ISBN provided.', 'my-book-showroom' )
			);
		}

		// ── 2. Detect code type ────────────────────────────────────────────────

		$normalized_isbn = MBS_ISBN_Helper::normalize_isbn( $code );
		$is_isbn13       = false !== $normalized_isbn;
		$is_asin         = ! $is_isbn13 && MBS_Amazon_PAAPI_Parser::is_asin( $code );

		if ( ! $is_isbn13 && ! $is_asin ) {
			return new \WP_Error(
				'mbs_paapi_invalid_code',
				sprintf(
					/* translators: %s: the unrecognized code */
					__( '"%s" is not a valid ASIN (10 alphanumeric) or ISBN-13 (13 digits).', 'my-book-showroom' ),
					esc_html( $code )
				)
			);
		}

		// ── 3. Build request body ──────────────────────────────────────────────

		$timestamp = time();

		if ( $is_asin ) {
			$path   = self::PATH_GET_ITEMS;
			$target = self::TARGET_GET_ITEMS;
			$body   = MBS_Amazon_PAAPI_Request::build_get_items_body( strtoupper( $code ), $partner );
		} else {
			$path   = self::PATH_SEARCH_ITEMS;
			$target = self::TARGET_SEARCH_ITEMS;
			$body   = MBS_Amazon_PAAPI_Request::build_search_items_body( $normalized_isbn, $partner );
		}

		// ── 4. Build signed headers ────────────────────────────────────────────

		$headers = MBS_Amazon_PAAPI_Request::build_signed_headers(
			$access_key,
			$secret_key,
			$path,
			$target,
			$body,
			$timestamp
		);

		// ── 5. Execute request ─────────────────────────────────────────────────

		$url      = 'https://' . MBS_Amazon_PAAPI_Request::HOST . $path;
		$response = wp_remote_post(
			$url,
			array(
				'headers' => $headers,
				'body'    => $body,
				'timeout' => self::TIMEOUT,
			)
		);

		if ( is_wp_error( $response ) ) {
			return new \WP_Error(
				'mbs_paapi_network_error',
				$response->get_error_message()
			);
		}

		// ── 6. Check HTTP status ───────────────────────────────────────────────

		$http_code = wp_remote_retrieve_response_code( $response );
		$raw_body  = wp_remote_retrieve_body( $response );

		if ( 200 !== (int) $http_code ) {
			$decoded = json_decode( $raw_body, true );
			$message = isset( $decoded['Errors'][0]['Message'] )
				? $decoded['Errors'][0]['Message']
				: sprintf(
					/* translators: %d: HTTP status code */
					__( 'PA-API returned HTTP %d.', 'my-book-showroom' ),
					$http_code
				);

			return new \WP_Error( 'mbs_paapi_http_error', $message );
		}

		// ── 7. Decode JSON ─────────────────────────────────────────────────────

		$data = json_decode( $raw_body, true );

		if ( ! is_array( $data ) ) {
			return new \WP_Error(
				'mbs_paapi_parse_error',
				__( 'PA-API response could not be decoded as JSON.', 'my-book-showroom' )
			);
		}

		// ── 8. Check for API-level errors ──────────────────────────────────────

		if ( ! empty( $data['Errors'] ) ) {
			$err_code = $data['Errors'][0]['Code'] ?? '';
			// ItemNotAccessible / ItemNotFound → treat as a clean miss.
			if ( in_array( $err_code, array( 'ItemNotAccessible', 'ItemNotFound' ), true ) ) {
				return false;
			}

			return new \WP_Error(
				'mbs_paapi_api_error',
				$data['Errors'][0]['Message'] ?? __( 'Unknown PA-API error.', 'my-book-showroom' )
			);
		}

		// ── 9. Extract item ────────────────────────────────────────────────────

		if ( $is_asin ) {
			$items = $data['ItemsResult']['Items'] ?? array();
		} else {
			$items = $data['SearchResult']['Items'] ?? array();
		}

		if ( empty( $items ) || ! is_array( $items ) ) {
			return false; // Clean miss.
		}

		return MBS_Amazon_PAAPI_Parser::parse_item( $items[0], $is_isbn13 ? $normalized_isbn : '' );
	}
}
