<?php

namespace MediaReplacement;

use WP_PowerSuite\Core\AbstractModule;

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

class Module extends AbstractModule {

	private const MODULE_ID         = 'media-replacement';
	private const MODULE_VERSION    = '1.0.0';
	private const REPLACED_BY_META    = '_wpps_replaced_by';
	private const REVISED_DATE_META   = '_wpps_revised_date';
	private const IMAGE_TYPES         = [ 'jpeg', 'jpg', 'png', 'gif', 'webp' ];
	private const VIDEO_EXTENSIONS    = [ 'mp4', 'webm', 'ogg', 'mov', 'avi', 'wmv' ];

	/** @var array<int, int> */
	private array $revised_meta_cache = [];

	public function getId(): string {
		return self::MODULE_ID;
	}

	public function getName(): string {
		return __( 'Media Replacement', 'wp-powersuite' );
	}

	public function getVersion(): string {
		return self::MODULE_VERSION;
	}

	public function getDescription(): string {
		return __( 'Replace any media file while keeping the same URL and attachment ID. Update product photos used across multiple pages without touching any of them — just swap the file and every reference updates automatically.', 'wp-powersuite' );
	}

	public function getContext(): string|array {
		return [ 'admin', 'ajax', 'frontend' ];
	}

	public function getDependencies(): array {
		return [];
	}

	private function isLicenseActive(): bool {
		return function_exists( 'wp_powersuite_is_license_valid' ) && wp_powersuite_is_license_valid();
	}

	protected function registerHooks(): void {
		if ( ! $this->isLicenseActive() ) {
			return;
		}

		add_filter( 'wp_get_attachment_url', [ $this, 'addCacheBustToAttachmentUrl' ], 10, 2 );
		add_filter( 'wp_get_attachment_image_src', [ $this, 'addCacheBustToImageSrc' ], 10, 4 );

		if ( ! is_admin() && ! wp_doing_ajax() ) {
			return;
		}

		add_action( 'wp_ajax_wpps_media_replace', [ $this, 'ajaxReplaceMedia' ] );
		add_action( 'wp_ajax_wpps_media_replace_info', [ $this, 'ajaxGetAttachmentInfo' ] );
		add_action( 'add_meta_boxes_attachment', [ $this, 'addReplaceMediaMetaBox' ] );
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueScripts' ] );
		add_action( 'admin_footer-post.php', [ $this, 'maybeRenderReplaceModal' ] );
		add_action( 'load-upload.php', [ $this, 'registerUploadPageHooks' ] );
		add_filter( 'attachment_fields_to_edit', [ $this, 'addReplaceFieldToAttachmentModal' ], 10, 2 );
	}

	public function registerUploadPageHooks(): void {
		if ( ! current_user_can( 'upload_files' ) ) {
			return;
		}

		add_filter( 'media_row_actions', [ $this, 'addReplaceMediaRowAction' ], 10, 2 );
		add_action( 'admin_footer-upload.php', [ $this, 'renderReplaceModal' ] );
	}

	public function addCacheBustToAttachmentUrl( string $url, int $attachment_id ): string {
		$revised = $this->getRevisedTimestamp( $attachment_id );
		return $revised ? add_query_arg( 'v', $revised, $url ) : $url;
	}

	public function addCacheBustToImageSrc( array|false $image, int $attachment_id, string|array $size, bool $icon ): array|false {
		if ( ! is_array( $image ) || empty( $image[0] ) ) {
			return $image;
		}

		$revised = $this->getRevisedTimestamp( $attachment_id );
		if ( $revised ) {
			$image[0] = add_query_arg( 'v', $revised, $image[0] );
		}

		return $image;
	}

	public function addReplaceMediaRowAction( array $actions, \WP_Post $post ): array {
		if ( ! $this->canReplaceAttachment( $post->ID ) ) {
			return $actions;
		}

		$actions['wpps_replace_media'] = sprintf(
			'<a href="#" class="wpps-replace-media-trigger" data-attachment-id="%d">%s</a>',
			(int) $post->ID,
			esc_html__( 'Replace Media', 'wp-powersuite' )
		);

		return $actions;
	}

	public function addReplaceFieldToAttachmentModal( array $form_fields, \WP_Post $post ): array {
		if ( ! $this->canReplaceAttachment( $post->ID ) ) {
			return $form_fields;
		}

		$form_fields['wpps_replace_media'] = [
			'label' => __( 'Replace Media', 'wp-powersuite' ),
			'input' => 'html',
			'html'  => $this->getReplaceTriggerButton( $post->ID, __( 'Upload a new file', 'wp-powersuite' ) ),
		];

		return $form_fields;
	}

	public function addReplaceMediaMetaBox(): void {
		if ( ! current_user_can( 'upload_files' ) ) {
			return;
		}

		add_meta_box(
			'wpps-replace-media',
			__( 'Replace Media', 'wp-powersuite' ),
			[ $this, 'renderReplaceMediaMetaBox' ],
			'attachment',
			'side'
		);
	}

	public function renderReplaceMediaMetaBox( \WP_Post $post ): void {
		if ( ! $this->canReplaceAttachment( $post->ID ) ) {
			echo '<p>' . esc_html__( 'No file found.', 'wp-powersuite' ) . '</p>';
			return;
		}
		?>
		<div class="wpps-replace-media-meta-box">
			<p class="wpps-replace-meta-desc"><?php esc_html_e( 'Upload a new file to replace this one. The attachment ID, URL, and all references stay the same — only the file content changes.', 'wp-powersuite' ); ?></p>
			<p class="wpps-replace-meta-actions">
				<?php echo $this->getReplaceTriggerButton( $post->ID, __( 'Upload a new file', 'wp-powersuite' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			</p>
			<?php $this->renderRevisionInfo( $post->ID ); ?>
		</div>
		<?php
	}

	private function renderRevisionInfo( int $attachment_id ): void {
		$revised     = get_post_meta( $attachment_id, self::REVISED_DATE_META, true );
		$replaced_by = get_post_meta( $attachment_id, self::REPLACED_BY_META, true );
		if ( empty( $revised ) && empty( $replaced_by ) ) {
			return;
		}

		echo '<p class="wpps-replace-revision-info description">';
		if ( $revised ) {
			printf(
				/* translators: %s: date */
				esc_html__( 'Last replaced: %s', 'wp-powersuite' ),
				esc_html( wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), (int) $revised ) )
			);
			echo '<br>';
		}
		if ( $replaced_by ) {
			$user = get_user_by( 'id', (int) $replaced_by );
			if ( $user ) {
				printf(
					/* translators: %s: user display name */
					esc_html__( 'Replaced by: %s', 'wp-powersuite' ),
					esc_html( $user->display_name )
				);
			}
		}
		echo '</p>';
	}

	public function maybeRenderReplaceModal(): void {
		$post_id = isset( $_GET['post'] ) ? (int) $_GET['post'] : 0;
		if ( $post_id > 0 && get_post_type( $post_id ) === 'attachment' ) {
			$this->renderReplaceModal();
		}
	}

	public function renderReplaceModal(): void {
		if ( ! current_user_can( 'upload_files' ) ) {
			return;
		}

		include __DIR__ . '/views/replace-modal.php';
	}

	public function enqueueScripts( string $hook ): void {
		if ( ! current_user_can( 'upload_files' ) ) {
			return;
		}

		global $pagenow;
		$is_upload     = $pagenow === 'upload.php';
		$is_media_edit = $pagenow === 'post.php' && isset( $_GET['post'] ) && get_post_type( (int) $_GET['post'] ) === 'attachment';
		if ( ! $is_upload && ! $is_media_edit ) {
			return;
		}

		$base = WP_POWERSUITE_PLUGIN_URL . 'modules/' . self::MODULE_ID . '/assets/';
		wp_enqueue_style( 'wp-powersuite-media-replacement', $base . 'media-replacement.css', [], self::MODULE_VERSION );
		wp_enqueue_script(
			'wp-powersuite-media-replacement',
			$base . 'media-replacement.js',
			$is_upload ? [ 'jquery', 'media-views', 'media-grid' ] : [ 'jquery' ],
			self::MODULE_VERSION,
			true
		);
		wp_localize_script( 'wp-powersuite-media-replacement', 'wppsMediaReplace', [
			'ajaxUrl' => admin_url( 'admin-ajax.php' ),
			'nonce'   => wp_create_nonce( 'wpps_media_replace' ),
			'strings' => [
				'chooseFile'      => __( 'Choose file', 'wp-powersuite' ),
				'uploadNewFile'   => __( 'Upload new file', 'wp-powersuite' ),
				'replaceMedia'    => __( 'Replace Media', 'wp-powersuite' ),
				'uploading'       => __( 'Uploading...', 'wp-powersuite' ),
				'processing'      => __( 'Processing...', 'wp-powersuite' ),
				'success'         => __( 'Replacement complete. Reloading...', 'wp-powersuite' ),
				'error'           => __( 'An error occurred.', 'wp-powersuite' ),
				'invalidFileType' => __( 'The selected file type is not compatible with the original.', 'wp-powersuite' ),
				'selectFile'      => __( 'Please select a file.', 'wp-powersuite' ),
			],
		] );
	}

	public function ajaxGetAttachmentInfo(): void {
		$err = __( 'Invalid request.', 'wp-powersuite' );
		if ( ! check_ajax_referer( 'wpps_media_replace', 'nonce', false ) ) {
			wp_send_json_error( [ 'message' => $err ] );
		}

		$attachment_id = isset( $_POST['attachment_id'] ) ? (int) $_POST['attachment_id'] : 0;
		if ( ! $attachment_id || get_post_type( $attachment_id ) !== 'attachment' || ! $this->canReplaceAttachment( $attachment_id ) ) {
			wp_send_json_error( [ 'message' => $err ] );
		}

		$file = get_attached_file( $attachment_id, true );
		if ( ! $file || ! is_file( $file ) ) {
			wp_send_json_error( [ 'message' => __( 'File not found.', 'wp-powersuite' ) ] );
		}

		$metadata = wp_get_attachment_metadata( $attachment_id );
		$filename = basename( $file );
		$ext      = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) );
		$mime     = get_post_mime_type( $attachment_id ) ?: '';

		$image_url = wp_attachment_is_image( $attachment_id )
			? ( wp_get_attachment_image_url( $attachment_id, 'thumbnail' ) ?: wp_get_attachment_url( $attachment_id ) )
			: wp_mime_type_icon( $attachment_id );

		wp_send_json_success( [
			'attachment_id' => $attachment_id,
			'filename'      => $filename,
			'ext'           => $ext,
			'mime'          => $mime,
			'size'          => filesize( $file ),
			'width'         => $metadata['width'] ?? null,
			'height'        => $metadata['height'] ?? null,
			'preview_url'   => $image_url,
			'compatible'    => $this->getCompatibleTypes( $ext, $mime ),
		] );
	}

	public function ajaxReplaceMedia(): void {
		$err = __( 'Invalid request.', 'wp-powersuite' );
		if ( ! check_ajax_referer( 'wpps_media_replace', 'nonce', false ) ) {
			wp_send_json_error( [ 'message' => $err ] );
		}

		$attachment_id = isset( $_POST['attachment_id'] ) ? (int) $_POST['attachment_id'] : 0;
		if ( ! $attachment_id || get_post_type( $attachment_id ) !== 'attachment' || ! $this->canReplaceAttachment( $attachment_id ) ) {
			wp_send_json_error( [ 'message' => $err ] );
		}

		require_once ABSPATH . 'wp-admin/includes/file.php';

		$keep_filename = ! empty( $_POST['keep_filename'] );
		$keep_date     = ! empty( $_POST['keep_date'] );
		$keep_backup   = ! empty( $_POST['keep_backup'] );

		if ( empty( $_FILES['file'] ) || (int) $_FILES['file']['error'] !== UPLOAD_ERR_OK ) {
			$msg = __( 'No file uploaded or upload error.', 'wp-powersuite' );
			if ( isset( $_FILES['file']['error'] ) && (int) $_FILES['file']['error'] !== UPLOAD_ERR_OK ) {
				$msg = $this->getUploadErrorMessage( (int) $_FILES['file']['error'] );
			}
			wp_send_json_error( [ 'message' => $msg ] );
		}

		$result = $this->replaceAttachmentFile( $attachment_id, $_FILES['file'], $keep_filename, $keep_date, $keep_backup );
		if ( is_wp_error( $result ) ) {
			wp_send_json_error( [ 'message' => $result->get_error_message() ] );
		}

		wp_send_json_success( $result );
	}

	private function getUploadErrorMessage( int $code ): string {
		$messages = [
			UPLOAD_ERR_INI_SIZE   => __( 'The uploaded file exceeds the upload_max_filesize directive.', 'wp-powersuite' ),
			UPLOAD_ERR_FORM_SIZE  => __( 'The uploaded file exceeds the MAX_FILE_SIZE directive.', 'wp-powersuite' ),
			UPLOAD_ERR_PARTIAL    => __( 'The file was only partially uploaded.', 'wp-powersuite' ),
			UPLOAD_ERR_NO_FILE    => __( 'No file was uploaded.', 'wp-powersuite' ),
			UPLOAD_ERR_NO_TMP_DIR => __( 'Missing temporary folder.', 'wp-powersuite' ),
			UPLOAD_ERR_CANT_WRITE => __( 'Failed to write file to disk.', 'wp-powersuite' ),
			UPLOAD_ERR_EXTENSION  => __( 'A PHP extension stopped the upload.', 'wp-powersuite' ),
		];

		return $messages[ $code ] ?? __( 'Unknown upload error.', 'wp-powersuite' );
	}

	public function replaceAttachmentFile( int $attachment_id, array $file, bool $keep_filename = true, bool $keep_date = true, bool $keep_backup = false ): array|\WP_Error {
		$current_file = get_attached_file( $attachment_id, true );
		if ( ! $current_file || ! is_file( $current_file ) ) {
			return new \WP_Error( 'no_file', __( 'Current file not found.', 'wp-powersuite' ) );
		}

		$current_ext = strtolower( pathinfo( $current_file, PATHINFO_EXTENSION ) );
		$upload_ext  = strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) );
		$orig_mime   = get_post_mime_type( $attachment_id ) ?: '';

		if ( ! $this->isCompatibleReplacement( $current_ext, $orig_mime, $upload_ext, (string) ( $file['type'] ?? '' ) ) ) {
			return new \WP_Error( 'incompatible', __( 'The selected file type is not compatible with the original.', 'wp-powersuite' ) );
		}

		$dir         = dirname( $current_file );
		$target_file = $keep_filename ? $current_file : $dir . DIRECTORY_SEPARATOR . sanitize_file_name( $file['name'] );
		if ( $target_file !== $current_file && file_exists( $target_file ) ) {
			return new \WP_Error( 'exists', __( 'A file with that name already exists.', 'wp-powersuite' ) );
		}

		$_POST['action'] = 'wpps_media_replace';
		$tmp             = wp_handle_upload( $file, [ 'test_form' => false ], false );
		if ( isset( $tmp['error'] ) ) {
			return new \WP_Error( 'upload', $tmp['error'] );
		}

		$tmp_file  = $tmp['file'];
		$tmp_check = wp_check_filetype( $tmp_file );
		$tmp_ext   = isset( $tmp_check['ext'] ) ? strtolower( (string) $tmp_check['ext'] ) : '';
		$tmp_type  = isset( $tmp_check['type'] ) ? (string) $tmp_check['type'] : '';
		if ( $tmp_ext === '' || ! $this->isCompatibleReplacement( $current_ext, $orig_mime, $tmp_ext, $tmp_type ) ) {
			@unlink( $tmp_file );
			return new \WP_Error( 'incompatible', __( 'The selected file type is not compatible with the original.', 'wp-powersuite' ) );
		}

		$upload_dir      = wp_upload_dir();
		$basedir_real    = realpath( $upload_dir['basedir'] );
		$target_dir_real = realpath( dirname( $target_file ) );
		if ( ! $basedir_real || ! $target_dir_real || strncmp( $target_dir_real, $basedir_real, strlen( $basedir_real ) ) !== 0 ) {
			@unlink( $tmp_file );
			return new \WP_Error( 'invalid_path', __( 'Invalid target path for replacement.', 'wp-powersuite' ) );
		}

		if ( $keep_backup ) {
			$backup_result = $this->createBackupAttachment( $attachment_id, $current_file );
			if ( is_wp_error( $backup_result ) ) {
				@unlink( $tmp_file );
				return $backup_result;
			}
		}

		if ( ! @rename( $tmp_file, $target_file ) ) {
			if ( ! @copy( $tmp_file, $target_file ) ) {
				@unlink( $tmp_file );
				return new \WP_Error( 'write_failed', __( 'Failed to write the new file.', 'wp-powersuite' ) );
			}
			@unlink( $tmp_file );
		}

		if ( ! is_file( $target_file ) || filesize( $target_file ) === 0 ) {
			return new \WP_Error( 'write_failed', __( 'Failed to write the new file.', 'wp-powersuite' ) );
		}

		$meta = wp_get_attachment_metadata( $attachment_id );
		if ( is_array( $meta ) && ! empty( $meta['sizes'] ) ) {
			$base_dir = dirname( $current_file );
			foreach ( $meta['sizes'] as $size_data ) {
				if ( ! empty( $size_data['file'] ) ) {
					$thumb = $base_dir . DIRECTORY_SEPARATOR . $size_data['file'];
					if ( is_file( $thumb ) ) {
						@unlink( $thumb );
					}
				}
			}
		}

		if ( $target_file !== $current_file ) {
			if ( is_file( $current_file ) ) {
				@unlink( $current_file );
			}
			$rel = str_replace( $upload_dir['basedir'] . DIRECTORY_SEPARATOR, '', $target_file );
			$rel = str_replace( DIRECTORY_SEPARATOR, '/', $rel );
			update_post_meta( $attachment_id, '_wp_attached_file', $rel );
			wp_update_post( [
				'ID'   => $attachment_id,
				'guid' => $upload_dir['baseurl'] . '/' . $rel,
			] );
		}

		$new_meta = wp_generate_attachment_metadata( $attachment_id, $target_file );
		if ( ! is_wp_error( $new_meta ) ) {
			wp_update_attachment_metadata( $attachment_id, $new_meta );
		}

		$post_update = [
			'ID'                => $attachment_id,
			'post_modified'     => current_time( 'mysql' ),
			'post_modified_gmt' => current_time( 'mysql', 1 ),
		];
		if ( ! $keep_date ) {
			$post_update['post_date']     = current_time( 'mysql' );
			$post_update['post_date_gmt'] = current_time( 'mysql', 1 );
		}
		wp_update_post( $post_update );

		update_post_meta( $attachment_id, self::REVISED_DATE_META, time() );
		update_post_meta( $attachment_id, self::REPLACED_BY_META, get_current_user_id() );
		unset( $this->revised_meta_cache[ $attachment_id ] );

		clean_attachment_cache( $attachment_id );

		return [
			'attachment_id' => $attachment_id,
			'url'           => wp_get_attachment_url( $attachment_id ),
			'filename'      => basename( $target_file ),
		];
	}

	private function createBackupAttachment( int $attachment_id, string $current_file ): int|\WP_Error {
		$path_info = pathinfo( $current_file );
		$dir       = $path_info['dirname'] ?? '';
		$base      = $path_info['filename'] ?? 'file';
		$ext       = $path_info['extension'] ?? '';
		$suffix    = $ext ? '.' . $ext : '';

		$backup_name = $base . '-backup-' . gmdate( 'Ymd-His' ) . $suffix;
		$backup_name = sanitize_file_name( $backup_name );
		$backup_file = $dir . DIRECTORY_SEPARATOR . $backup_name;

		$attempt = 0;
		while ( file_exists( $backup_file ) && $attempt < 10 ) {
			$attempt++;
			$backup_name = $base . '-backup-' . gmdate( 'Ymd-His' ) . '-' . $attempt . $suffix;
			$backup_name = sanitize_file_name( $backup_name );
			$backup_file = $dir . DIRECTORY_SEPARATOR . $backup_name;
		}
		if ( file_exists( $backup_file ) ) {
			return new \WP_Error( 'backup_exists', __( 'Could not create backup: file already exists.', 'wp-powersuite' ) );
		}

		if ( ! @copy( $current_file, $backup_file ) ) {
			return new \WP_Error( 'backup_copy', __( 'Could not copy file for backup.', 'wp-powersuite' ) );
		}

		$post  = get_post( $attachment_id );
		$title = ( $post && $post->post_title ) ? $post->post_title . ' (' . __( 'backup', 'wp-powersuite' ) . ')' : __( 'Backup', 'wp-powersuite' );

		$new_id = wp_insert_attachment( [
			'post_mime_type' => get_post_mime_type( $attachment_id ) ?: '',
			'post_title'     => $title,
			'post_content'   => '',
			'post_status'    => 'inherit',
			'post_parent'    => 0,
		], $backup_file, 0 );

		if ( is_wp_error( $new_id ) ) {
			@unlink( $backup_file );
			return $new_id;
		}

		require_once ABSPATH . 'wp-admin/includes/image.php';
		$backup_meta = wp_generate_attachment_metadata( $new_id, $backup_file );
		if ( ! is_wp_error( $backup_meta ) ) {
			wp_update_attachment_metadata( $new_id, $backup_meta );
		}

		update_post_meta( $new_id, '_wpps_replacement_backup_of', $attachment_id );

		return $new_id;
	}

	private function getCompatibleTypes( string $ext, string $mime ): array {
		$ext = strtolower( $ext );
		if ( in_array( $ext, self::IMAGE_TYPES, true ) ) {
			return [ 'extensions' => self::IMAGE_TYPES, 'label' => __( 'Images (JPEG, PNG, WebP, GIF)', 'wp-powersuite' ) ];
		}
		if ( $ext === 'pdf' || $mime === 'application/pdf' ) {
			return [ 'extensions' => [ 'pdf' ], 'label' => __( 'PDF', 'wp-powersuite' ) ];
		}
		if ( in_array( $ext, self::VIDEO_EXTENSIONS, true ) ) {
			return [ 'extensions' => self::VIDEO_EXTENSIONS, 'label' => __( 'Video (same container)', 'wp-powersuite' ) ];
		}

		return [ 'extensions' => [ $ext ], 'label' => sprintf( __( 'Same format (%s)', 'wp-powersuite' ), strtoupper( $ext ) ) ];
	}

	private function isCompatibleReplacement( string $orig_ext, string $orig_mime, string $new_ext, string $new_mime ): bool {
		if ( in_array( $orig_ext, self::IMAGE_TYPES, true ) ) {
			return in_array( $new_ext, self::IMAGE_TYPES, true );
		}
		if ( $orig_ext === 'pdf' || $orig_mime === 'application/pdf' ) {
			return $new_ext === 'pdf' || $new_mime === 'application/pdf';
		}
		if ( in_array( $orig_ext, self::VIDEO_EXTENSIONS, true ) ) {
			return in_array( $new_ext, self::VIDEO_EXTENSIONS, true );
		}

		return $orig_ext === $new_ext;
	}

	private function canReplaceAttachment( int $attachment_id ): bool {
		if ( ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) {
			return false;
		}

		$file = get_attached_file( $attachment_id, true );
		return $file && is_file( $file );
	}

	private function getReplaceTriggerButton( int $attachment_id, string $label ): string {
		return sprintf(
			'<button type="button" class="button button-primary wpps-replace-media-trigger" data-attachment-id="%d">%s</button>',
			$attachment_id,
			esc_html( $label )
		);
	}

	private function getRevisedTimestamp( int $attachment_id ): int {
		if ( ! isset( $this->revised_meta_cache[ $attachment_id ] ) ) {
			$this->revised_meta_cache[ $attachment_id ] = (int) get_post_meta( $attachment_id, self::REVISED_DATE_META, true );
		}

		return $this->revised_meta_cache[ $attachment_id ];
	}
}
