<?php
/**
 * File Path: includes/admin/tabs/imp-exp/class-mbs-tab-child-import-csv.php
 *
 * Import / Export Tab — CSV Import Child (FREE)
 *
 * AJAX handlers for CSV import: header parsing, column mapping, full import,
 * cover sideload trigger, and template download.
 *
 * handle_ajax_mbs_import_csv_headers() dispatches to two private format
 * handlers (XLSX, JSON) so each format's concerns are isolated. Both
 * handlers either terminate via wp_send_json_*() for direct-import paths,
 * or return a uniform ['path' => ..., 'images_dir' => ...] array that
 * feeds the common header-parsing tail. ZIP imports use the archive import
 * AJAX actions (mbs_archive_import_start/book/media) via MBS_Tab_Child_Archive.
 *
 * @package MyBookShowroom
 * @since   1.0.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_CSV {

	use MBS_Ajax_Import_Export_Guard;

	// -------------------------------------------------------------------------
	// Public AJAX handlers
	// -------------------------------------------------------------------------

	/**
	 * AJAX handler: parse uploaded file headers for the column-mapping step.
	 *
	 * Validates the upload, then dispatches to a format-specific private
	 * handler.  Handlers that can perform a direct import (multi-sheet XLSX,
	 * MBS catalog ZIP) terminate via wp_send_json_*() and never return here.
	 * Handlers that produce a CSV for the column-mapping UI return a uniform
	 * ['path', 'images_dir'] array which feeds the common tail below.
	 *
	 * @since  1.0.0
	 * @return void
	 */
	public function handle_ajax_mbs_import_csv_headers(): void {
		$this->verify_import_export_request();

		if ( empty( $_FILES['csv_file']['tmp_name'] ) ) {
			wp_send_json_error( array( 'message' => 'No file received.' ) );
		}

		$original_tmp = sanitize_text_field( $_FILES['csv_file']['tmp_name'] );

		if ( ! is_uploaded_file( $original_tmp ) ) {
			wp_send_json_error( array( 'message' => 'Invalid file upload.' ) );
		}

		// SEC-008: Validate file type by content inspection, not browser-supplied MIME.
		$original_name   = isset( $_FILES['csv_file']['name'] )
			? sanitize_file_name( wp_unslash( $_FILES['csv_file']['name'] ) )
			: '';
		$allowed_ext     = array( 'csv', 'xlsx', 'json' );
		$check           = wp_check_filetype_and_ext( $original_tmp, $original_name );
		$file_ext        = $check['ext'] ?? '';
		if ( ! in_array( $file_ext, $allowed_ext, true ) ) {
			wp_send_json_error( array( 'message' => 'Only .csv, .xlsx, and .json (MyBookTable) files are accepted.' ) );
		}

		// Resolve temp directory and ensure it exists.
		$persistent_dir = MBS_CSV_Temp_Manager::get_temp_dir();
		if ( ! is_dir( $persistent_dir ) ) {
			wp_mkdir_p( $persistent_dir );
		}

		// C-008: Clean up orphaned files from abandoned sessions.
		MBS_CSV_Temp_Manager::cleanup_stale_temp_files( $persistent_dir );

		$token = wp_generate_password( 32, false );

		$uploaded_filename = 'import_' . get_current_user_id() . '_' . $token . '.' . $file_ext;
		$uploaded_path     = $persistent_dir . $uploaded_filename;

		if ( ! copy( $original_tmp, $uploaded_path ) ) {
			wp_send_json_error( array( 'message' => 'Failed to prepare uploaded file.' ) );
		}

		// ── Dispatch to format handler ────────────────────────────────────────
		// Handlers that perform a direct import terminate via wp_send_json_*().
		// All others return ['path' => ..., 'images_dir' => ...].
		$affiliate_written = array();

		if ( 'xlsx' === $file_ext ) {
			$pipe = $this->handle_xlsx_upload( $uploaded_path, $persistent_dir );

		} elseif ( 'json' === $file_ext ) {
			$pipe              = $this->handle_json_upload( $uploaded_path, $persistent_dir );
			$affiliate_written = $pipe['affiliate_written'];

		} else {
			// CSV: use as-is.
			$pipe = array( 'path' => $uploaded_path, 'images_dir' => '' );
		}

		// ── Common tail: parse headers and send column-mapping response ───────
		$result = MBS_Parent_Importer_CSV::parse_headers( $pipe['path'] );

		if ( ! $result['success'] ) {
			// phpcs:ignore WordPress.PHP.NoSilencedErrors -- cleanup on failure
			wp_delete_file( $pipe['path'] );
			wp_send_json_error( array( 'message' => $result['error'] ) );
		}

		// Store session data (file path + images_dir) for the import handler.
		// 30-minute window matches the upload session length.
		set_transient(
			'mbs_csv_upload_' . get_current_user_id() . '_' . $token,
			array(
				'file'       => $pipe['path'],
				'images_dir' => $pipe['images_dir'],
				'source'     => 'json' === $file_ext ? 'mybooktable' : '',
			),
			1800
		);

		$response = array(
			'columns' => $result['columns'],
			'preview' => $result['preview'],
			'token'   => $token,
		);
		if ( ! empty( $affiliate_written ) ) {
			$response['affiliate_notice'] = $affiliate_written;
		}

		wp_send_json_success( $response );
	}

	/**
	 * AJAX handler: process pending cover image URLs for recently imported books.
	 *
	 * @since  1.0.0
	 * @return void
	 */
	public function handle_ajax_mbs_sideload_covers(): void {
		$this->verify_import_export_request();

		$result = MBS_Cover_Sideloader::process_pending();

		wp_send_json_success( $result );
	}

	/**
	 * AJAX handler: stream a pre-filled CSV import template to the browser.
	 *
	 * @since  1.0.0
	 * @return void
	 */
	public function handle_ajax_mbs_download_csv_template(): void {
		if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'mbs_download_csv_template' ) ) {
			wp_die( esc_html__( 'Invalid or expired link — please reload the Import/Export page and try again.', 'my-book-showroom' ), 403 );
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Permission denied.', 'my-book-showroom' ), 403 );
		}

		$columns = MBS_Child_Importer_CSV_Helpers::ACCEPTED_COLUMNS;

		$example = array(
			'title'            => 'My Novel',
			'teaser'           => 'A short description of the book.',
			'publisher'        => 'Acme Publishing',
			'pub_date'         => '2024-03-15',
			'coming_soon'      => '0',
			'isbn13'           => '9781234567890',
			'asin'             => 'B0EXAMPLE01',
			'format'           => 'paperback',
			'price'            => '14.99',
			'look_inside_text' => 'It was a dark and stormy night...',
			'author'           => 'Jane Author',
			'series'           => 'My Series',
			'series_number'    => '1',
			'page_length'      => '320',
			'cover_image_url'  => 'https://example.com/cover.jpg',
			'cover_image_file' => 'my-novel.jpg',
		);

		$example_row = array();
		foreach ( $columns as $col ) {
			$example_row[] = $example[ $col ] ?? '';
		}

		header( 'Content-Type: text/csv; charset=UTF-8' );
		header( 'Content-Disposition: attachment; filename="mbs-import-template.csv"' );
		header( 'Pragma: no-cache' );
		header( 'Expires: 0' );

		$fh = fopen( 'php://output', 'wb' );
		fwrite( $fh, "\xEF\xBB\xBF" ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- WP_Filesystem migration deferred to PC-FILESYS sprint; PHP native file ops are safe here (temp files in get_temp_dir(), never inside wp-content)
		fputcsv( $fh, $columns );
		fputcsv( $fh, $example_row );
		fclose( $fh ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- WP_Filesystem migration deferred to PC-FILESYS sprint; PHP native file ops are safe here (temp files in get_temp_dir(), never inside wp-content)
		exit;
	}

	/**
	 * AJAX handler: run the full CSV import using a previously uploaded file.
	 *
	 * @since  1.0.0
	 * @return void
	 */
	public function handle_ajax_mbs_import_csv(): void {
		$this->verify_import_export_request();

		$token = sanitize_text_field( wp_unslash( $_POST['token'] ?? '' ) );
		if ( empty( $token ) ) {
			wp_send_json_error( array( 'message' => 'Missing upload token.' ) );
		}

		$session = get_transient( 'mbs_csv_upload_' . get_current_user_id() . '_' . $token );
		if ( ! $session ) {
			wp_send_json_error( array( 'message' => 'Upload session expired. Please re-upload the file.' ) );
		}

		// Guard against the old string format for any in-flight sessions.
		if ( is_array( $session ) ) {
			$tmp        = $session['file'] ?? '';
			$images_dir = $session['images_dir'] ?? '';
		} else {
			$tmp        = (string) $session;
			$images_dir = '';
		}

		if ( empty( $tmp ) || ! file_exists( $tmp ) ) {
			wp_send_json_error( array( 'message' => 'Upload session expired. Please re-upload the file.' ) );
		}

		$column_map = array();
		if ( ! empty( $_POST['column_map'] ) ) {
			$decoded = json_decode( wp_unslash( $_POST['column_map'] ), true );
			if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
				foreach ( $decoded as $csv_col => $mbs_field ) {
					$column_map[ sanitize_text_field( $csv_col ) ] = sanitize_key( $mbs_field );
				}
			}
		}

		$force_insert_keys = array();
		if ( ! empty( $_POST['force_insert_keys'] ) ) {
			$decoded = json_decode( wp_unslash( $_POST['force_insert_keys'] ), true );
			if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
				$force_insert_keys = array_map( 'sanitize_text_field', $decoded );
			}
		}

		$options = array();
		if ( '' !== $images_dir ) {
			$options['images_dir'] = $images_dir;
		}

		$result = MBS_Parent_Importer_CSV::import( $tmp, $column_map, $force_insert_keys, $options );

		if ( ! $result['success'] ) {
			wp_send_json_error( array( 'message' => $result['error'] ?? 'Import failed.' ) );
		}

		delete_transient( 'mbs_csv_upload_' . get_current_user_id() . '_' . $token );
		// phpcs:ignore WordPress.PHP.NoSilencedErrors -- cleanup temp file
		wp_delete_file( $tmp );

		wp_send_json_success( array(
			'inserted'   => $result['inserted'],
			'skipped'    => $result['skipped'],
			'duplicates' => $result['duplicates'],
			'errors'     => $result['errors'],
			'source'     => $session['source'] ?? '',
		) );
	}

	// -------------------------------------------------------------------------
	// Private format handlers
	// -------------------------------------------------------------------------

	/**
	 * Handle an XLSX upload.
	 *
	 * Multi-sheet XLSX (MBS 7-tab catalog): imports directly and terminates via
	 * wp_send_json_*() — never returns to the dispatcher.
	 *
	 * Single-sheet XLSX: converts to CSV and returns the path for the common
	 * header-parsing tail.
	 *
	 * @since  1.0.0
	 * @param  string $uploaded_path  Absolute path to the copied upload file.
	 * @param  string $persistent_dir Absolute path to the persistent temp dir.
	 * @return array{path: string, images_dir: string}
	 */
	private function handle_xlsx_upload( string $uploaded_path, string $persistent_dir ): array {
		if ( MBS_XLSX_Reader::is_multi_sheet( $uploaded_path ) ) {
			$force  = ! empty( $_POST['force_overwrite'] );
			$result = MBS_Catalog_XLSX_Importer::import_from_path(
				$uploaded_path,
				array(
					'images_dir'      => '',
					'force_overwrite' => $force,
				)
			);
			wp_delete_file( $uploaded_path );
			$this->send_direct_import_response( $result );
		}

		// Single-sheet: convert to CSV for the column-mapping flow.
		$csv_path = MBS_XLSX_Reader::to_csv_path( $uploaded_path, $persistent_dir );
		// phpcs:ignore WordPress.PHP.NoSilencedErrors -- remove XLSX; keep only the CSV
		wp_delete_file( $uploaded_path );
		if ( is_wp_error( $csv_path ) ) {
			wp_send_json_error( array( 'message' => 'Could not read XLSX file: ' . $csv_path->get_error_message() ) );
		}

		return array( 'path' => $csv_path, 'images_dir' => '' );
	}

	/**
	 * Handle a JSON (MyBookTable) upload.
	 *
	 * Validates the JSON format, applies affiliate codes, converts to CSV, and
	 * returns the CSV path for the common header-parsing tail.
	 *
	 * @since  1.0.0
	 * @param  string $uploaded_path  Absolute path to the copied upload file.
	 * @param  string $persistent_dir Absolute path to the persistent temp dir.
	 * @return array{path: string, images_dir: string, affiliate_written: array}
	 */
	private function handle_json_upload( string $uploaded_path, string $persistent_dir ): array {
		if ( ! MBS_MyBookTable_Importer::is_mybooktable_json( $uploaded_path ) ) {
			// phpcs:ignore WordPress.PHP.NoSilencedErrors
			wp_delete_file( $uploaded_path );
			wp_send_json_error( array( 'message' => 'Unrecognized JSON format. Only MyBookTable export files are currently supported.' ) );
		}

		// Apply affiliate codes before unlinking the uploaded file.
		$affiliate_written = MBS_MyBookTable_Importer::apply_affiliate_settings( $uploaded_path );

		$csv_path = MBS_MyBookTable_Importer::to_csv_path( $uploaded_path, $persistent_dir );
		// phpcs:ignore WordPress.PHP.NoSilencedErrors
		wp_delete_file( $uploaded_path );
		if ( is_wp_error( $csv_path ) ) {
			wp_send_json_error( array( 'message' => $csv_path->get_error_message() ) );
		}

		return array(
			'path'              => $csv_path,
			'images_dir'        => '',
			'affiliate_written' => $affiliate_written,
		);
	}

	// -------------------------------------------------------------------------
	// Private helpers
	// -------------------------------------------------------------------------

	/**
	 * Send a direct-import JSON success/error response and terminate.
	 *
	 * Used by both the multi-sheet XLSX and MBS catalog ZIP paths, which bypass
	 * the column-mapping UI and import in a single step.
	 *
	 * @since  1.0.0
	 * @param  array $result  Return value from MBS_Catalog_XLSX_Importer::import_from_path().
	 * @return never
	 */
	private function send_direct_import_response( array $result ): never {
		if ( ! empty( $result['errors'] ) && 0 === $result['imported'] ) {
			wp_send_json_error( array( 'message' => implode( ' ', $result['errors'] ) ) );
		}

		wp_send_json_success( array(
			'multi_tab' => true,
			'imported'  => $result['imported'],
			'skipped'   => $result['skipped'],
			'errors'    => $result['errors'],
			'message'   => sprintf(
				'Import complete: %d imported, %d skipped as duplicates.',
				$result['imported'],
				$result['skipped']
			),
		) );
	}
}
