<?php
/**
 * File Path: includes/admin/tabs/imp-exp/class-mbs-tab-child-import-competitors.php
 *
 * Import / Export Tab — Competitor Import Child
 *
 * Handles one-click competitor migration importers: MyBookTable (direct JSON),
 * WP Books Gallery, Genesis Author Pro, RS WP Books, GS Bookshowcase, Novelist.
 * All handlers are FREE-tier (competitor migration — no mbs_is_pro() gate).
 *
 * Split from MBS_Tab_Child_Import_API (REF-004, Session 137).
 *
 * @package MyBookShowroom
 * @since   1.1.0
 */

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

// phpcs:disable WordPress.Security.NonceVerification -- nonce verified in parent class verify_save() / check_admin_referer() before $_POST access; PHPCS cannot trace cross-method nonce verification
// phpcs:disable WordPress.Security.ValidatedSanitizedInput -- input sanitized at point of use (sanitize_text_field, absint, wp_kses_post, etc.); false positives from PHPCS not tracing sanitization helpers called before this method
class MBS_Tab_Child_Import_Competitors {

	use MBS_Ajax_Import_Export_Guard;

	/**
	 * AJAX handler: direct MyBookTable JSON import (Story 14-1b).
	 *
	 * Accepts a .json file upload, validates it as a MyBookTable export, then
	 * imports books and affiliate settings via MBS_Importer_MyBookTable::import().
	 * This replaces the old CSV-relay path for the Migrate panel, giving admins:
	 *   · No column-mapping step — field mapping is pre-defined.
	 *   · Full 6-setting affiliate import (vs. 2 in the old CSV-relay path).
	 *   · Direct DAO writes — better series hierarchy and multi-author support.
	 *
	 * Tier: FREE (competitor migration — no mbs_is_pro() gate).
	 *
	 * @return void
	 */
	public function handle_ajax_mbs_import_mybooktable(): void {
		$this->verify_import_export_request();

		$uploaded = $_FILES['file'] ?? null;
		if ( ! $uploaded || UPLOAD_ERR_OK !== (int) $uploaded['error'] ) {
			$err_code = (int) ( $uploaded['error'] ?? -1 );
			wp_send_json_error( array( 'message' => sprintf( 'File upload failed (code %d).', $err_code ) ) );
		}

		// Validate file extension before touching its content.
		// Accept both .json and extensionless files (MyBookTable exports are
		// JSON but ship with no file extension, so they're invisible in pickers
		// that filter on '.json' — UNREGFREE-10).
		$upload_name = isset( $uploaded['name'] )
			? sanitize_file_name( wp_unslash( $uploaded['name'] ) )
			: '';
		$_mbt_ext = strtolower( pathinfo( $upload_name, PATHINFO_EXTENSION ) );
		if ( '' !== $_mbt_ext && 'json' !== $_mbt_ext ) {
			wp_send_json_error( array( 'message' => 'Only .json files exported from MyBookTable are accepted.' ) );
		}

		if ( ! is_uploaded_file( $uploaded['tmp_name'] ) ) {
			wp_send_json_error( array( 'message' => 'Invalid file upload.' ) );
		}

		// SEC-011: Inspect file content — do not rely on extension alone.
		$check         = wp_check_filetype_and_ext(
			$uploaded['tmp_name'],
			$upload_name,
			array( 'json' => 'application/json' )
		);
		$is_valid_json = ( ! empty( $check['ext'] ) && 'json' === $check['ext'] );
		if ( ! $is_valid_json && function_exists( 'finfo_open' ) ) {
			// MyBookTable exports are extensionless, so wp_check_filetype_and_ext()
			// (which derives the type from the filename) reports no ext and would
			// reject them. Accept when finfo confirms a JSON/text payload; the
			// is_mybooktable_json() structural check below is the real gate.
			$finfo     = finfo_open( FILEINFO_MIME_TYPE );
			$real_mime = $finfo ? finfo_file( $finfo, $uploaded['tmp_name'] ) : '';
			if ( $finfo ) {
				finfo_close( $finfo );
			}
			$is_valid_json = in_array( $real_mime, array( 'application/json', 'text/json', 'text/plain' ), true );
		}
		if ( ! $is_valid_json ) {
			wp_send_json_error( array( 'message' => 'Only .json files exported from MyBookTable are accepted.' ) );
		}

		// Validate it is actually a MyBookTable export before any further work.
		if ( ! MBS_MyBookTable_Importer::is_mybooktable_json( $uploaded['tmp_name'] ) ) {
			wp_send_json_error( array( 'message' => 'Unrecognized JSON format. Only MyBookTable export files are supported.' ) );
		}

		// Copy to a persistent temp path so the importer can safely read the full file.
		$temp_dir = get_temp_dir() . 'mbs-temp-import/';
		wp_mkdir_p( $temp_dir );
		$temp_path = $temp_dir . 'mbt_direct_' . get_current_user_id() . '_' . wp_generate_password( 16, false ) . '.json';

		if ( ! copy( $uploaded['tmp_name'], $temp_path ) ) {
			wp_send_json_error( array( 'message' => 'Failed to stage the uploaded file for processing.' ) );
		}

		// Run the direct import (books + settings in one call).
		$result = MBS_Importer_MyBookTable::import( $temp_path );

		// Always clean up the temp file.
		// phpcs:ignore WordPress.PHP.NoSilencedErrors
		wp_delete_file( $temp_path );

		// If every book failed, surface the first error as a top-level failure.
		if ( 0 === $result['imported'] && 0 === $result['skipped'] && ! empty( $result['errors'] ) ) {
			wp_send_json_error( array(
				'message' => $result['errors'][0],
				'errors'  => $result['errors'],
			) );
		}

		wp_send_json_success( array(
			'imported'         => $result['imported'],
			'skipped'          => $result['skipped'],
			'settings_applied' => $result['settings_applied'],
			'errors'           => $result['errors'],
		) );
	}

	/**
	 * AJAX handler: Import a WXR export from WP Books Gallery.
	 *
	 * Accepts a .xml file, stages it to a temp path, and delegates to
	 * MBS_Importer_WP_Books_Gallery::import(). Cleans up temp file on exit.
	 * Tier: FREE (competitor migration -- no mbs_is_pro() gate).
	 *
	 * @return void
	 */
	public function handle_ajax_mbs_import_wp_books_gallery(): void {
		$this->verify_import_export_request();

		$temp_path = $this->stage_xml_upload( 'wp_books_gallery' );
		if ( is_wp_error( $temp_path ) ) {
			wp_send_json_error( array( 'message' => $temp_path->get_error_message() ) );
		}

		$result = MBS_Importer_WP_Books_Gallery::import( $temp_path );

		// phpcs:ignore WordPress.PHP.NoSilencedErrors
		wp_delete_file( $temp_path );

		$this->send_competitor_import_response( $result );
	}

	/**
	 * AJAX handler: Import a WXR export from Genesis / Osom Author Pro.
	 *
	 * Accepts a .xml file, stages it to a temp path, and delegates to
	 * MBS_Importer_Genesis_Author_Pro::import(). Cleans up temp file on exit.
	 * Tier: FREE (competitor migration -- no mbs_is_pro() gate).
	 *
	 * @return void
	 */
	public function handle_ajax_mbs_import_genesis_author_pro(): void {
		$this->verify_import_export_request();

		$temp_path = $this->stage_xml_upload( 'genesis_author_pro' );
		if ( is_wp_error( $temp_path ) ) {
			wp_send_json_error( array( 'message' => $temp_path->get_error_message() ) );
		}

		$result = MBS_Importer_Genesis_Author_Pro::import( $temp_path );

		// phpcs:ignore WordPress.PHP.NoSilencedErrors
		wp_delete_file( $temp_path );

		$this->send_competitor_import_response( $result );
	}

	/**
	 * AJAX handler for the RS WP Books Showcase WXR importer.
	 *
	 * @since  1.2.0
	 * @return void
	 */
	public function handle_ajax_mbs_import_rs_wp_books(): void {
		$this->verify_import_export_request();

		$temp_path = $this->stage_xml_upload( 'rs_wp_books' );
		if ( is_wp_error( $temp_path ) ) {
			wp_send_json_error( array( 'message' => $temp_path->get_error_message() ) );
		}

		$result = MBS_Importer_RS_WP_Books::import( $temp_path );

		// phpcs:ignore WordPress.PHP.NoSilencedErrors
		wp_delete_file( $temp_path );

		$this->send_competitor_import_response( $result );
	}

	/**
	 * AJAX handler: Import a WXR export from GS Books Showcase.
	 *
	 * Accepts a .xml file, stages it to a temp path, and delegates to
	 * MBS_Importer_GS_Bookshowcase::import(). Cleans up temp file on exit.
	 * Tier: FREE (competitor migration -- no mbs_is_pro() gate).
	 *
	 * @since  1.3.0
	 * @return void
	 */
	public function handle_ajax_mbs_import_gs_bookshowcase(): void {
		$this->verify_import_export_request();

		$temp_path = $this->stage_xml_upload( 'gs_bookshowcase' );
		if ( is_wp_error( $temp_path ) ) {
			wp_send_json_error( array( 'message' => $temp_path->get_error_message() ) );
		}

		$result = MBS_Importer_GS_Bookshowcase::import( $temp_path );

		// phpcs:ignore WordPress.PHP.NoSilencedErrors
		wp_delete_file( $temp_path );

		$this->send_competitor_import_response( $result );
	}

	/**
	 * AJAX handler: Import a WXR export from Novelist.
	 *
	 * Accepts a .xml file, stages it to a temp path, and delegates to
	 * MBS_Importer_Novelist::import(). Cleans up temp file on exit.
	 * Tier: FREE (competitor migration -- no mbs_is_pro() gate).
	 *
	 * @since  1.3.0
	 * @return void
	 */
	public function handle_ajax_mbs_import_novelist(): void {
		$this->verify_import_export_request();

		$temp_path = $this->stage_xml_upload( 'novelist' );
		if ( is_wp_error( $temp_path ) ) {
			wp_send_json_error( array( 'message' => $temp_path->get_error_message() ) );
		}

		$result = MBS_Importer_Novelist::import( $temp_path );

		// phpcs:ignore WordPress.PHP.NoSilencedErrors
		wp_delete_file( $temp_path );

		$this->send_competitor_import_response( $result );
	}

	// ── Shared private helpers ─────────────────────────────────────────────────

	/**
	 * Validate and stage an XML upload to a persistent temp path.
	 *
	 * @since  1.2.0
	 * @param  string $slug  Short identifier for the temp filename (no spaces).
	 * @return string|WP_Error  Absolute path to the staged file, or WP_Error on failure.
	 */
	private function stage_xml_upload( string $slug ) {
		$uploaded = $_FILES['file'] ?? null;
		if ( ! $uploaded || UPLOAD_ERR_OK !== (int) $uploaded['error'] ) {
			$err_code = (int) ( $uploaded['error'] ?? -1 );
			return new WP_Error(
				'upload_failed',
				sprintf( 'File upload failed (code %d).', $err_code )
			);
		}

		$upload_name = isset( $uploaded['name'] )
			? sanitize_file_name( wp_unslash( $uploaded['name'] ) )
			: '';
		if ( 'xml' !== strtolower( pathinfo( $upload_name, PATHINFO_EXTENSION ) ) ) {
			return new WP_Error( 'bad_extension', 'Only .xml WXR export files are accepted.' );
		}

		if ( ! is_uploaded_file( $uploaded['tmp_name'] ) ) {
			return new WP_Error( 'invalid_upload', 'Invalid file upload.' );
		}

		// SEC-011: Inspect file content — do not rely on extension alone.
		$is_valid_xml = self::is_accepted_xml_upload( $uploaded['tmp_name'], $upload_name );
		if ( ! $is_valid_xml ) {
			return new WP_Error( 'bad_content', 'Only .xml WXR export files are accepted.' );
		}

		$temp_dir  = get_temp_dir() . 'mbs-temp-import/';
		wp_mkdir_p( $temp_dir );
		$temp_path = $temp_dir . sanitize_key( $slug ) . '_' . get_current_user_id() . '_' . wp_generate_password( 16, false ) . '.xml';

		if ( ! copy( $uploaded['tmp_name'], $temp_path ) ) {
			return new WP_Error( 'stage_failed', 'Failed to stage the uploaded file for processing.' );
		}

		return $temp_path;
	}

	/**
	 * Send a standardised JSON response for a competitor import result.
	 *
	 * @since  1.2.0
	 * @param  array $result  Associative array with imported, skipped, errors[], notes[].
	 * @return void
	 */
	private function send_competitor_import_response( array $result ): void {
		if ( 0 === $result['imported'] && 0 === $result['skipped'] && ! empty( $result['errors'] ) ) {
			wp_send_json_error( array(
				'message' => $result['errors'][0],
				'errors'  => $result['errors'],
				'notes'   => $result['notes'] ?? array(),
			) );
		}

		wp_send_json_success( array(
			'imported' => $result['imported'],
			'skipped'  => $result['skipped'],
			'errors'   => $result['errors'],
			'notes'    => $result['notes'] ?? array(),
		) );
	}
}
