<?php
/**
 * MBS_Look_Inside_Epub_Ajax
 *
 * AJAX handler for EPUB Look Inside extraction (PRO tier).
 * Action:      mbs_extract_epub_look_inside
 * Nonce:       mbs_look_inside_nonce  (shared with DOCX handler)
 * Capability:  edit_post (post-specific)
 * Tier gate:   mbs_is_pro()
 *
 * @package    My_Book_Showroom
 * @subpackage Admin
 * @since      4.21
 */

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

/**
 * Class MBS_Look_Inside_Epub_Ajax
 *
 * Validates an .epub upload, parses Look Inside candidates via MBS_EPUB_Parser,
 * and returns the first candidate's clean HTML and word count.
 *
 * @since 4.21
 */
class MBS_Look_Inside_Epub_Ajax {

	/**
	 * Register the AJAX hook.
	 */
	public function __construct() {
		add_action( 'wp_ajax_mbs_extract_epub_look_inside', array( $this, 'handle' ) );
	}

	/**
	 * Handle the mbs_extract_epub_look_inside AJAX request.
	 *
	 * Sends a JSON response and exits.
	 *
	 * @return void
	 */
	public function handle(): void {
		check_ajax_referer( 'mbs_look_inside_nonce', 'nonce' );

		$post_id = absint( $_POST['post_id'] ?? 0 );
		if ( $post_id < 1 ) {
			wp_send_json_error( [ 'message' => 'Invalid post ID.' ], 400 );
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( [ 'message' => 'Insufficient permissions.' ], 403 );
		}

		if ( ! mbs_is_pro() ) {
			wp_send_json_error( [ 'message' => 'Look Inside EPUB import requires a PRO license.' ], 403 );
		}

		// Step 1: Check file upload error code.
		$uploaded = $_FILES['file'] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- input sanitized via sanitize_* helpers or validated before use; see inline sanitization
		if ( ! $uploaded || UPLOAD_ERR_OK !== (int) $uploaded['error'] ) {
			$code = (int) ( $uploaded['error'] ?? -1 );
			wp_send_json_error( [ 'message' => sprintf( 'File upload failed (code %d).', $code ) ] );
		}

		// Step 2: Validate file extension.
		$ext = strtolower( pathinfo( $uploaded['name'], PATHINFO_EXTENSION ) );
		if ( 'epub' !== $ext ) {
			wp_send_json_error( [ 'message' => 'Only .epub files are accepted.' ] );
		}

		// Step 2b: Verify file content matches EPUB MIME type (not just extension).
		$mime_check = wp_check_filetype_and_ext(
			$uploaded['tmp_name'], // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- F-011: a server-side filesystem path; sanitize_text_field()/wp_unslash() secure nothing and wp_unslash() would corrupt a Windows path. Integrity enforced by is_uploaded_file() in Step 4.
			sanitize_file_name( wp_unslash( $uploaded['name'] ) ),
			array( 'epub' => 'application/epub+zip' )
		);
		if ( empty( $mime_check['ext'] ) || 'epub' !== $mime_check['ext'] ) {
			wp_send_json_error( [ 'message' => __( 'Invalid file type. Only EPUB files are accepted.', 'my-book-showroom' ) ] );
		}

		// Step 3: Overwrite check -- runs before is_uploaded_file so the confirmation
		// prompt can be triggered even in test environments where tmp_name is synthetic.
		$confirmed = sanitize_text_field( wp_unslash( $_POST['confirmed_overwrite'] ?? '' ) );
		if ( get_post_meta( $post_id, 'mbs_look_inside_text', true ) && 'true' !== $confirmed ) {
			wp_send_json_success( [ 'overwrite_required' => true ] );
		}

		// Step 4: Validate file origin and size.
		if ( ! is_uploaded_file( $uploaded['tmp_name'] ) ) {
			wp_send_json_error( [ 'message' => 'File upload security check failed.' ] );
		}
		if ( (int) $uploaded['size'] > MBS_LOOK_INSIDE_EPUB_MAX_BYTES ) {
			wp_send_json_error( [ 'message' => 'File exceeds the 10 MB limit.' ] );
		}

		// Step 5: Stage uploaded file in unique temp path.
		if ( ! wp_mkdir_p( get_temp_dir() . 'mbs-temp-import/' ) ) {
			wp_send_json_error( [ 'message' => 'Could not create temp directory.' ] );
		}
		$uid       = get_current_user_id();
		// SA-L2: Use random token instead of time() to prevent same-second filename collision.
		$rand      = wp_generate_password( 16, false );
		$temp_path = get_temp_dir() . 'mbs-temp-import/epub_' . $uid . '_' . $rand . '.epub';
		if ( ! move_uploaded_file( $uploaded['tmp_name'], $temp_path ) ) { // phpcs:ignore Generic.PHP.ForbiddenFunctions.Found -- base64_decode() used to decode pre-encoded look-inside/archive chunk data stored by plugin; not applied to raw user input
			wp_send_json_error( [ 'message' => 'Could not stage uploaded file.' ] );
		}

		// Step 6: Parse EPUB to extract Look Inside candidates.
		$result = ( new MBS_EPUB_Parser() )->parse( $temp_path );

		// Step 7: Always unlink temp file, including on parse failure.
		wp_delete_file( $temp_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged

		if ( ! $result['ok'] ) {
			wp_send_json_error( [ 'message' => $result['error'] ] );
		}

		// Step 8: Pick the first candidate -- highest-confidence chapter/body content.
		// HTML is pre-sanitized by MBS_EPUB_Look_Inside_Extractor (word limit already applied).
		$candidates = $result['look_inside_candidates'] ?? [];
		if ( empty( $candidates ) ) {
			wp_send_json_error( [ 'message' => 'No suitable Look Inside content was found in this EPUB.' ] );
		}

		$candidate  = $candidates[0];
		$html       = $candidate['html'] ?? '';
		$word_count = str_word_count( wp_strip_all_tags( $html ) );
		$truncated  = false !== strpos( $html, 'Excerpt truncated at' );

		// Step 9: Return clean HTML, word count, truncation flag, and candidate label.
		wp_send_json_success( [
			'html'       => $html,
			'word_count' => $word_count,
			'truncated'  => $truncated,
			'label'      => $candidate['label'] ?? '',
		] );
	}
}
