<?php

/**
 * Card Oracle Demo Data functionality
 *
 * Handles the creation and management of demo data for the Card Oracle plugin.
 * Provides sample readings, cards, positions, and descriptions for testing and
 * demonstration purposes.
 *
 * @author  Christopher Graham
 * @package Card_Oracle/Admin/DemoData
 * @version 1.0
 * @since   1.1.2
 */

// Prevent redeclaration during unit tests.
if ( class_exists( 'CardOracleDemoData' ) ) {
	return;
}

/**
 * Card Oracle Demo Data Class.
 *
 * Manages the creation of demo data including tarot readings, cards, positions,
 * and their associated descriptions. Also handles media attachment and featured
 * image assignment for demo cards.
 *
 * @since 1.1.2
 * @package Card_Oracle/Admin
 * @author  Christopher Graham <support@chillichalli.com>
 */
class CardOracleDemoData {

	/**
	 * Copy a plugin asset image to the WordPress uploads directory.
	 *
	 * Uses filesystem copy() to avoid binary corruption that can occur when
	 * reading image data into a PHP string via file_get_contents().
	 *
	 * @since 1.2.1
	 * @param string $source_path Absolute path to the source image file.
	 * @param string $filename    Desired filename in the uploads directory.
	 * @return array|false Array with 'file', 'url', and 'type' on success, false on failure.
	 */
	private function copy_image_to_uploads( $source_path, $filename ) {
		$upload_dir = wp_upload_dir();

		if ( ! empty( $upload_dir['error'] ) ) {
			return false;
		}

		$dest_path = trailingslashit( $upload_dir['path'] ) . $filename;
		$dest_url  = trailingslashit( $upload_dir['url'] ) . $filename;

		// Direct filesystem copy preserves binary data integrity.
		if ( ! copy( $source_path, $dest_path ) ) {  // @codingStandardsIgnoreLine
			return false;
		}

		// Set file permissions to match the uploads directory.
		$stat  = stat( dirname( $dest_path ) );
		$perms = $stat['mode'] & 0000666;
		chmod( $dest_path, $perms );  // @codingStandardsIgnoreLine

		$file_type = wp_check_filetype( $dest_path, null );

		return array(
			'file' => $dest_path,
			'url'  => $dest_url,
			'type' => $file_type['type'],
		);
	}

	/**
	 * Attach demo data images as featured images to posts.
	 *
	 * Processes demo card images by uploading them to the WordPress media library
	 * and setting them as featured images for demo card posts. Handles duplicate
	 * image detection and metadata generation.
	 *
	 * @since 0.13.0
	 * @global CO_Logging $co_logs Global logging instance for error tracking
	 *
	 * @param int    $post_id    The ID of the post to attach the image to.
	 * @param string $image_name The filename/path of the image relative to plugin assets.
	 *
	 * @return bool True if the image was successfully attached, false otherwise.
	 */
	private function feature_image( $post_id, $image_name ) {
		global $co_logs;

		// Ensure WordPress image functions are available (file absent in unit tests).
		if ( file_exists( ABSPATH . 'wp-admin/includes/image.php' ) ) {
			require_once ABSPATH . 'wp-admin/includes/image.php';
		}

		$filename         = 'card-oracle-' . basename( $image_name );
		$image            = CARD_ORACLE_DIR . $image_name;
		$title            = get_the_title( $post_id );
		$attachment_title = 'Marseille - ' . $title;

		if ( file_exists( $image ) ) {
			if ( post_exists( $attachment_title ) ) {
				$page            = get_page_by_title( $attachment_title, OBJECT, 'attachment' );
				$attachment_id   = $page->ID;
				$destination     = get_attached_file( $attachment_id );
				$attachment_data = wp_generate_attachment_metadata( $attachment_id, $destination );
			} else {
				$upload = $this->copy_image_to_uploads( $image, $filename );

				if ( ! $upload ) {
					$co_logs->add( 'Feature Image', 'Upload failed for ' . $filename, null, 'error' );
					return false;
				}

				// Attachment attributes for the file.
				$attachment = array(
					'post_mime_type' => $upload['type'],
					'post_title'     => sanitize_text_field( $attachment_title ),
					'post_content'   => $title . ' image.',
					'post_status'    => 'inherit',
				);
				// Insert and return attachment id.
				$attachment_id = wp_insert_attachment( $attachment, $upload['file'], $post_id );
				// Insert and return attachment metadata.
				$attachment_data = wp_generate_attachment_metadata( $attachment_id, $upload['file'] );
			}

			// Update and return attachment metadata.
			wp_update_attachment_metadata( $attachment_id, $attachment_data );

			// Update the Alt Text.
			update_post_meta( $attachment_id, '_wp_attachment_image_alt', $title );

			// Associate the image to the post.
			return set_post_thumbnail( $post_id, $attachment_id );
		}
	}

	/**
	 * Get the json data.
	 *
	 * @since 0.26.0
	 * @param string $filename Name of the file to import starting from the plugin base directory.
	 * @return array $json Return the json data from the file.
	 */
	public function import_json_data( $filename ) {
		global $co_logs, $co_notices;

		$json       = array();
		$data_file  = CARD_ORACLE_URL . $filename;
		$local_file = CARD_ORACLE_DIR . $filename;
		$co_logs->add( 'Importing File', $data_file, null, 'event' );

		$response      = wp_remote_get( $data_file );
		$response_code = wp_remote_retrieve_response_code( $response );

		if ( 200 !== $response_code ) {
			// Log the failure.
			$error = sprintf( 'Unable to access URL [%s]. Code [%d].', $data_file, $response_code );
			$co_logs->add( 'URL Demo Data', $error, null, 'error' );

			// Failed to get the data file via the URL, try using the filesystem.
			$local_json = file_get_contents( $local_file );  // @codingStandardsIgnoreLine

			if ( false === $local_json ) {
				// Log the failure.
				/* translators: %s is a filename. */
				$error = sprintf( __( 'Unable to access file [%s].', 'card-oracle' ), $local_file );
				$co_logs->add( 'Local Demo Data', $error, null, 'error' );

				/* translators: %1$d is a number, %2$s is a URL, %3$s is a filename. */
				$notice = esc_html( sprintf( __( 'Error Code: %1$d; URL: %2$s; File: %3$s', 'card-oracle' ), $response_code, $data_file, $local_file ) );
				$co_notices->add( 'demo-data-file-missing', $notice, 'error' );
			} else {
				$json = json_decode( $local_json, true );
				if ( null === $json ) {
					co_debug_log( 'class-card-oracle-demo-data.php:import_json_data: JSON decode failed for file: ' . $local_file );
				}
			}
		} else {
			$json = json_decode( $response['body'], true );
			if ( null === $json ) {
				co_debug_log( 'class-card-oracle-demo-data.php:import_json_data: JSON decode failed for URL response: ' . $data_file );
			}
		}

		return $json;
	}

	/**
	 * Insert the json data into the custom post types.
	 *
	 * @since 0.16.0
	 * @param array $data Data to insert in json format.
	 */
	public function insert_data( $data ) {
		global $co_logs, $co_notices;

		$co_logs->add( DEMO_DATA, 'Inserting data.', null, 'event' );

		$percentage   = isset( $data['reading']['reversed'] ) ? $data['reading']['reversed'] : 0;
		$description  = isset( $data['description'] ) ? wp_kses_post( $data['description'] ) : '';
		$reading_args = array(
			'post_title'   => $data['reading']['name'],
			'post_type'    => 'co_readings',
			'post_status'  => 'publish',
			'post_content' => $description,
			'meta_input'   => array(
				CO_REVERSE_PERCENT => $percentage,
			),
		);

		// Insert the Reading.
		$co_logs->add( DEMO_DATA, 'Inserting Reading.', null, 'event' );

		$reading_id = wp_insert_post( $reading_args );

		if ( is_wp_error( $reading_id ) ) {
			co_debug_log( 'class-card-oracle-demo-data.php:insert_data: Failed to insert reading: ' . $reading_id->get_error_message() );
			$co_logs->add( DEMO_DATA, 'Failed to insert reading: ' . $reading_id->get_error_message(), null, 'error' );
			return;
		}

		if ( isset( $data['reading']['image'] ) ) {
			$this->feature_image( $reading_id, $data['reading']['image'] );
		}

		// Store demo reading ID for easier lookup.
		if ( isset( $data['reading']['name'] ) && 'Past, Present, Future (Demo)' === $data['reading']['name'] ) {
			update_option( 'card_oracle_demo_reading_id', $reading_id );
		}

		// Store demo package metadata for tracking.
		if ( isset( $data['reading']['name'] ) ) {
			$package_slug = sanitize_title( $data['reading']['name'] );
			update_post_meta( $reading_id, 'card_oracle_demo_package', $package_slug );
			update_option( "card_oracle_demo_package_{$package_slug}", $reading_id );
		}

		// Insert the Positions. Initialised up front so the later descriptions
		// loop has a valid array even when no positions were inserted.
		$positions = array();
		foreach ( $data['positions'] as $position ) {
			$co_logs->add( DEMO_DATA, 'Inserting Position [' . $position['name'] . '].', null, 'event' );

			$position_id = wp_insert_post(
				array(
					'post_title'  => $position['name'],
					'post_type'   => 'co_positions',
					'post_status' => 'publish',
					'meta_input'  => array(
						CO_READING_ID => $reading_id,
						CO_CARD_ORDER => $position['order'],
					),
				)
			);

			if ( is_wp_error( $position_id ) ) {
				co_debug_log( 'class-card-oracle-demo-data.php:insert_data: Failed to insert position [' . $position['name'] . ']: ' . $position_id->get_error_message() );
				continue;
			}

			$positions[ $position['name'] ] = $position_id;
		}

		// Insert the Cards.
		foreach ( $data['cards'] as $card ) {
			$co_logs->add( DEMO_DATA, 'Inserting Card [' . $card['name'] . '].', null, 'event' );

			$content = isset( $card['description'] ) ? $card['description'] : '';

			$card_id = wp_insert_post(
				array(
					'post_title'   => $card['name'],
					'post_type'    => 'co_cards',
					'post_status'  => 'publish',
					'post_content' => $content,
					'meta_input'   => array(
						CO_READING_ID => $reading_id,
					),
				)
			);

			if ( is_wp_error( $card_id ) ) {
				co_debug_log( 'class-card-oracle-demo-data.php:insert_data: Failed to insert card [' . $card['name'] . ']: ' . $card_id->get_error_message() );
				continue;
			}

			if ( isset( $card['image'] ) ) {
				$this->feature_image( $card_id, $card['image'] );
			}

			// Insert a Description for each Card for each of the Positions.
			foreach ( $positions as $key => $value ) {
				$co_logs->add( DEMO_DATA, 'Inserting Descriptions [' . $card['name'] . ' - ' . $key . '].', null, 'event' );

				$upright  = isset( $card['upright'][0][ $key ] ) ? $card['upright'][0][ $key ] : '';
				$reversed = isset( $card['reverse'][0][ $key ] ) ? $card['reverse'][0][ $key ] : '';

				$desc_id = wp_insert_post(
					array(
						'post_title'   => $card['name'] . ' - ' . $key,
						'post_type'    => 'co_descriptions',
						'post_status'  => 'publish',
						'post_content' => $upright,
						'meta_input'   => array(
							CO_CARD_ID             => $card_id,
							CO_POSITION_ID         => $value,
							CO_REVERSE_DESCRIPTION => $reversed,
						),
					)
				);

				if ( is_wp_error( $desc_id ) ) {
					co_debug_log( 'class-card-oracle-demo-data.php:insert_data: Failed to insert description [' . $card['name'] . ' - ' . $key . ']: ' . $desc_id->get_error_message() );
				}
			}
		}
	}

	/**
	 * Repair existing card image attachments.
	 *
	 * For each Marseille card attachment already in the database, verifies the
	 * uploaded file exists and regenerates attachment metadata. If the file is
	 * missing or corrupted it is re-copied from the plugin assets.
	 *
	 * @since 1.2.1
	 * @param array $images The image manifest from images.json.
	 * @return array Associative array with 'repaired' and 'errors' counts.
	 */
	public function repair_images( $images ) {
		global $co_logs;

		// Ensure WordPress image functions are available (file absent in unit tests).
		if ( file_exists( ABSPATH . 'wp-admin/includes/image.php' ) ) {
			require_once ABSPATH . 'wp-admin/includes/image.php';
		}

		$repaired = 0;
		$errors   = 0;

		foreach ( $images['images'] as $element ) {
			$card_name = $element['name'];
			$title     = 'Marseille - ' . $card_name;
			$existing  = get_page_by_title( $title, OBJECT, 'attachment' );

			if ( ! $existing instanceof WP_Post ) {
				continue;
			}

			$attachment_id = $existing->ID;
			$attached_file = get_attached_file( $attachment_id );
			$source_image  = CARD_ORACLE_DIR . $element['image'];
			$filename      = 'card-oracle-' . basename( $element['image'] );
			$needs_copy    = false;

			if ( ! file_exists( $source_image ) ) {
				$co_logs->add( 'Repair Images', 'Source file missing: ' . $source_image, null, 'error' );
				++$errors;
				continue;
			}

			// Check if the uploaded file is missing or has a different size than the source.
			$source_size = filesize( $source_image );
			if ( ! $attached_file || ! file_exists( $attached_file ) ) {
				$needs_copy = true;
			} elseif ( abs( filesize( $attached_file ) - $source_size ) > 1024 ) {
				// Size differs by more than 1 KB — likely corrupted during previous import.
				$needs_copy = true;
			}

			if ( $needs_copy ) {
				$upload = $this->copy_image_to_uploads( $source_image, $filename );

				if ( ! $upload ) {
					$co_logs->add( 'Repair Images', 'Copy failed for ' . $filename, null, 'error' );
					++$errors;
					continue;
				}

				// Update the attachment to point to the new file.
				update_attached_file( $attachment_id, $upload['file'] );
				$attached_file = $upload['file'];

				// Update MIME type.
				if ( ! empty( $upload['type'] ) ) {
					wp_update_post(
						array(
							'ID'             => $attachment_id,
							'post_mime_type' => $upload['type'],
						)
					);
				}
			}

			// Regenerate metadata (thumbnails, dimensions).
			$metadata = wp_generate_attachment_metadata( $attachment_id, $attached_file );
			wp_update_attachment_metadata( $attachment_id, $metadata );

			// Ensure alt text is set.
			update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( $card_name ) );

			++$repaired;
		}

		// Re-link attachments to card posts whose thumbnail is missing or invalid.
		$relinked = 0;
		foreach ( $images['images'] as $element ) {
			$card_name = $element['name'];
			$title     = 'Marseille - ' . $card_name;

			// Find the attachment by title using WP_Query (get_page_by_title deprecated in WP 6.2).
			$attachment_query = new WP_Query(
				array(
					'post_type'      => 'attachment',
					'post_status'    => 'inherit',
					'title'          => $title,
					'posts_per_page' => 1,
				)
			);

			if ( ! $attachment_query->have_posts() ) {
				continue;
			}
			$attachment_id = $attachment_query->posts[0]->ID;

			// Find all co_cards posts with this name.
			$card_query = new WP_Query(
				array(
					'post_type'      => 'co_cards',
					'post_status'    => 'publish',
					'title'          => $card_name,
					'posts_per_page' => -1,
				)
			);

			foreach ( $card_query->posts as $card ) {
				$current = get_post_thumbnail_id( $card->ID );
				if ( ! $current || ! wp_attachment_is_image( $current ) ) {
					set_post_thumbnail( $card->ID, $attachment_id );
					++$relinked;
				}
			}
		}

		return array(
			'repaired' => $repaired,
			'errors'   => $errors,
			'relinked' => $relinked,
		);
	}

	/**
	 * Insert all the card images.
	 *
	 * @since 1.1.2
	 * @param array $images The array of images.
	 */
	public function insert_images( $images ) {
		global $co_logs;

		// Ensure WordPress image functions are available (file absent in unit tests).
		if ( file_exists( ABSPATH . 'wp-admin/includes/image.php' ) ) {
			require_once ABSPATH . 'wp-admin/includes/image.php';
		}

		foreach ( $images['images'] as $element ) {
			$image_name = $element['image'];

			$filename  = 'card-oracle-' . basename( $image_name );
			$image     = CARD_ORACLE_DIR . $image_name;
			$card_name = $element['name'];
			$title     = 'Marseille - ' . $card_name;

			if ( file_exists( $image ) && ! post_exists( $title ) ) {
				$upload = $this->copy_image_to_uploads( $image, $filename );

				if ( ! $upload ) {
					$co_logs->add( 'Insert Images', 'Copy failed for ' . $filename, null, 'error' );
					continue;
				}

				// Attachment attributes for the file.
				$attachment = array(
					'post_mime_type' => $upload['type'],
					'post_title'     => sanitize_text_field( $title ),
					'post_content'   => sanitize_text_field( $title ) . ' image.',
					'post_status'    => 'inherit',
				);
				// Insert and return attachment id.
				$attachment_id = wp_insert_attachment( $attachment, $upload['file'] );
				// Insert and return attachment metadata.
				$attachment_data = wp_generate_attachment_metadata( $attachment_id, $upload['file'] );

				// Update and return attachment metadata.
				wp_update_attachment_metadata( $attachment_id, $attachment_data );

				// Update the Alt Text.
				update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( $card_name ) );
			}
		}
	}
}
