<?php
/**
 * File Path: includes/import/class-mbs-importer-google-books.php
 *
 * Google Books Importer (PRO)
 *
 * Fetches book metadata from the Google Books API by ISBN-13. Returns a
 * candidate array for admin confirmation before insertion, consistent with
 * the two-step fetch → confirm → insert flow used by all API importers.
 *
 * Architecture:
 *   fetch()  — fetches metadata, returns candidates array for Tab 5 UI.
 *   Tab 5 JS shows the candidate list; admin confirms selection.
 *   Tab 5 AJAX handler calls MBS_Importer_CSV::import_candidates() with
 *   the confirmed candidates — single canonical insert path for all sources.
 *
 * @package MyBookShowroom
 * @since   1.0.0
 */

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

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

	/** Maximum ISBNs per fetch call (spec: Story 7-3). */
	const MAX_ISBNS = 50;

	/** Google Books API base URL. */
	const API_BASE = 'https://www.googleapis.com/books/v1/volumes';

	/** HTTP request timeout in seconds. */
	const REQUEST_TIMEOUT = 15;

	/**
	 * Fetch book metadata from Google Books API for a list of ISBN-13 values.
	 *
	 * Returns a candidate list suitable for Tab 5 confirmation UI and
	 * subsequent insertion via MBS_Importer_CSV::import_candidates().
	 * Does NOT insert anything — insertion is the admin's next step.
	 *
	 * @param string[] $isbns Raw ISBN-13 values (may be unnormalised).
	 * @return array{
	 *   success: bool,
	 *   candidates: array[],
	 *   not_found: string[],
	 *   errors: string[],
	 *   error: string
	 * }
	 */
	public static function fetch( array $isbns ): array {
		$result = array(
			'success'    => false,
			'candidates' => array(),
			'not_found'  => array(),
			'errors'     => array(),
			'error'      => '',
		);

		// NEW-H1: Enforce the 50-ISBN batch limit.
		$isbns = array_slice( $isbns, 0, self::MAX_ISBNS );
		$isbns = array_filter( array_map( 'trim', $isbns ) );

		if ( empty( $isbns ) ) {
			$result['error'] = 'No ISBN-13 values provided.';
			return $result;
		}

		// API-KEY-1: Read optional Google Books API key for higher per-day quota.
		// Stored encrypted per Cardinal Rule 2; empty string = anonymous (quota-limited).
		$_gb_api_key_enc = get_option( 'mbs_setting_import_google_books_api_key_encrypted', '' );
		$_gb_api_key     = ( '' !== $_gb_api_key_enc ) ? (string) MBS_Encryption::decrypt( $_gb_api_key_enc ) : '';

		foreach ( $isbns as $raw_isbn ) {
			$normalized = MBS_ISBN_Helper::normalize_isbn( $raw_isbn );
			if ( ! $normalized ) {
				$result['errors'][] = $raw_isbn . ': Invalid ISBN-13.';
				continue;
			}

			$query_args = array( 'q' => 'isbn:' . $normalized );
			if ( '' !== $_gb_api_key ) {
				$query_args['key'] = $_gb_api_key;
			}
			$url = add_query_arg( $query_args, self::API_BASE );

			$response = wp_remote_get( $url, array( 'timeout' => self::REQUEST_TIMEOUT ) );

			if ( is_wp_error( $response ) ) {
				$result['errors'][] = $normalized . ': ' . $response->get_error_message();
				continue;
			}

			$http_code = wp_remote_retrieve_response_code( $response );
			if ( 200 !== $http_code ) {
				$result['errors'][] = $normalized . ': API returned HTTP ' . $http_code . '.';
				continue;
			}

			$body = json_decode( wp_remote_retrieve_body( $response ), true );

			if ( empty( $body['items'][0]['volumeInfo'] ) ) {
				$result['not_found'][] = $normalized;
				continue;
			}

			$candidate = self::map_volume( $body['items'][0]['volumeInfo'], $normalized );
			if ( null !== $candidate ) {
				$result['candidates'][] = $candidate;
			} else {
				$result['not_found'][] = $normalized;
			}
		}

		$result['success'] = true;
		return $result;
	}

	// =========================================================================
	// Private helpers
	// =========================================================================

	/**
	 * Map a Google Books volumeInfo object to a canonical MBS candidate array.
	 *
	 * The shape returned here must match what MBS_Importer_CSV::insert_book()
	 * expects so that all importers share one insert code path (NEW-C1/NEW-C3).
	 *
	 * @param array  $volume     volumeInfo sub-object from Google Books API.
	 * @param string $normalized Normalised ISBN-13 used in the lookup.
	 * @return array|null Candidate array, or null if title is absent.
	 */
	private static function map_volume( array $volume, string $normalized ): ?array {
		$title = trim( $volume['title'] ?? '' );
		if ( empty( $title ) ) {
			return null;
		}

		// Normalise pub_date: Google returns YYYY, YYYY-MM, or YYYY-MM-DD.
		$raw_date = $volume['publishedDate'] ?? '';
		$pub_date = '';
		if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $raw_date ) ) {
			$pub_date = $raw_date;
		} elseif ( preg_match( '/^(\d{4})-(\d{2})$/', $raw_date, $m ) ) {
			$pub_date = $m[1] . '-' . $m[2] . '-01';
		} elseif ( preg_match( '/^(\d{4})$/', $raw_date, $m ) ) {
			$pub_date = $m[1] . '-01-01';
		}

		// Cover URL — largest available thumbnail.
		$image_links = $volume['imageLinks'] ?? array();
		$cover_url   = $image_links['thumbnail']
			?? $image_links['smallThumbnail']
			?? '';

		return array(
			'_source'      => 'google_books',
			'title'        => $title,
			// author key matches insert_book() taxonomy path (not _mbs_primary_author).
			'author'       => trim( $volume['authors'][0] ?? '' ),
			'isbn13'       => $normalized,
			'publisher'    => trim( $volume['publisher'] ?? '' ),
			'pub_date'     => $pub_date,
			'format'       => 'paperback',  // Google Books does not provide format data.
			'page_length'  => (string) ( $volume['pageCount'] ?? '' ),
			// description → post_content per insert_book() F-2 path.
			'post_content' => sanitize_textarea_field( $volume['description'] ?? '' ),
			// cover_url → mbs_import_cover_url postmeta (Sprint 9 sideload).
			'cover_url'    => esc_url_raw( $cover_url ),
		);
	}
}
