<?php

namespace ImageOptimization\Modules\Optimization\Components;

use ImageOptimization\Classes\Async_Operation\{
	Async_Operation,
	Async_Operation_Hook,
	Async_Operation_Queue,
};

use ImageOptimization\Classes\Image\{
	Image_Meta,
	Image_Status
};

use ImageOptimization\Modules\Optimization\{
	Classes\Exceptions\Bulk_Token_Expired_Error,
	Classes\Optimize_Image,
	Classes\Bulk_Optimization\Bulk_Optimization_Queue,
	Classes\Bulk_Optimization\Bulk_Optimization_Queue_Status,
	Classes\Bulk_Optimization\Bulk_Optimization_Queue_Type,
	Classes\Bulk_Optimization\Bulk_Optimization_Token_Manager,
};

use ImageOptimization\Classes\Logger;
use ImageOptimization\Classes\Utils;
use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error;
use Throwable;

// @codeCoverageIgnoreStart
if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}
// @codeCoverageIgnoreEnd

class Bulk_Optimization {
	/** @async */
	public function optimize_bulk() {
		$queue = new Bulk_Optimization_Queue( Bulk_Optimization_Queue_Type::OPTIMIZATION );

		if ( ! $queue->exists() ) {
			Logger::debug( 'Bulk optimization queue did not found' );

			return;
		}

		if ( ! $queue->has_more_images() ) {
			Logger::debug( 'No more pending images, deleting queue' );
			$queue->delete();

			return;
		}

		if ( $queue->should_refresh_token() ) {
			Logger::debug( 'Refreshing bulk token' );

			try {
				$token_data = Bulk_Optimization_Token_Manager::obtain_token(
					$queue->get_stats()[ Bulk_Optimization_Queue_Status::PENDING ],
					$queue->get_max_batch_size()
				);

				$queue
					->set_bulk_token(
						$token_data['token'],
						time() + HOUR_IN_SECONDS,
						$token_data['batch_size']
					)
					->save();
			} catch ( Throwable $t ) {
				Logger::info( 'Failed to obtain a bulk token: ' . $t->getMessage() );

				foreach ( $queue->get_images_by_status( Bulk_Optimization_Queue_Status::PENDING ) as $image ) {
					( new Image_Meta( $image['id'] ) )
						->set_status( Image_Status::NOT_OPTIMIZED )
						->save();
				}

				$queue->delete();

				return;
			}
		}

		$image_id = $queue->get_next_image();

		Logger::debug( 'Processing image: ' . $image_id );

		if ( ! $image_id ) {
			Logger::debug( 'No image to process, deleting queue' );
			$queue->delete();

			return;
		}

		$queue
			->set_current_image_id( $image_id )
			->save();

		try {
			$oi = new Optimize_Image(
				$image_id,
				'bulk',
				$queue->get_bulk_token()
			);

			$oi->optimize();

			$queue
				->mark_image_completed( $image_id )
				->increment_optimized_counter()
				->save();

		} catch ( Bulk_Token_Expired_Error $btee ) {
			$queue->mark_image_failed( $image_id )->save();

			Logger::info( "Token expired while optimizing image {$image_id}" );
		} catch ( Quota_Exceeded_Error $qee ) {
			$queue->mark_image_failed( $image_id )->save();

			Logger::info( "Quota exceeded while optimizing image {$image_id}" );
		} catch ( Throwable $e ) {
			$queue->mark_image_failed( $image_id )->save();

			Logger::info( "Failed to optimize image {$image_id}: " . $e->getMessage() );
		}

		$queue
			->set_current_image_id( null )
			->save();

		try {
			if ( $queue->has_more_images() ) {
				Async_Operation::create(
					Async_Operation_Hook::OPTIMIZE_BULK,
					[ 'operation_id' => $queue->get_operation_id() ],
					Async_Operation_Queue::OPTIMIZE
				);

				Logger::debug( 'Async operation created' );
			} else {
				Logger::debug( 'All images were optimized, deleting queue' );

				$queue->delete();
			}
		} catch ( Throwable $t ) {
			Logger::error( "Failed to create next async operation or delete queue: " . $t->getMessage() );
		}
	}

	/** @async */
	public function reoptimize_bulk() {
		$queue = new Bulk_Optimization_Queue( Bulk_Optimization_Queue_Type::REOPTIMIZATION );

		if ( ! $queue->exists() ) {
			return;
		}

		if ( ! $queue->has_more_images() ) {
			$queue->delete();

			return;
		}

		if ( $queue->should_refresh_token() ) {
			try {
				$token_data = Bulk_Optimization_Token_Manager::obtain_token(
					$queue->get_stats()[ Bulk_Optimization_Queue_Status::PENDING ],
					$queue->get_max_batch_size()
				);

				$queue
					->set_bulk_token(
						$token_data['token'],
						time() + HOUR_IN_SECONDS,
						$token_data['batch_size']
					)
					->save();
			} catch ( Throwable $t ) {
				Logger::info( 'Failed to obtain bulk token: ' . $t->getMessage() );

				foreach ( $queue->get_images_by_status( Bulk_Optimization_Queue_Status::PENDING ) as $image ) {
					( new Image_Meta( $image['id'] ) )
						->set_status( Image_Status::OPTIMIZATION_FAILED )
						->save();
				}

				$queue->delete();

				return;
			}
		}

		$image_id = $queue->get_next_image();

		if ( ! $image_id ) {
			$queue
				->set_status( Bulk_Optimization_Queue_Status::COMPLETED )
				->save();

			return;
		}

		$queue
			->set_current_image_id( $image_id )
			->save();

		try {
			$oi = new Optimize_Image(
				$image_id,
				'bulk-reoptimize',
				$queue->get_bulk_token(),
				true
			);

			$oi->optimize();

			$queue
				->mark_image_completed( $image_id )
				->increment_optimized_counter()
				->save();

		} catch ( Bulk_Token_Expired_Error $btee ) {
			$queue->mark_image_failed( $image_id )->save();

			Logger::info( "Token expired while reoptimizing image {$image_id}" );
		} catch ( Quota_Exceeded_Error $qee ) {
			$queue->mark_image_failed( $image_id )->save();

			foreach ( $queue->get_images_by_status( Bulk_Optimization_Queue_Status::PENDING ) as $image ) {
				( new Image_Meta( $image['id'] ) )
					->set_status( Image_Status::OPTIMIZATION_FAILED )
					->save();
			}

			$queue->delete();

			Logger::info( "Quota exceeded while reoptimizing image {$image_id}" );

			return;
		} catch ( Throwable $e ) {
			$queue->mark_image_failed( $image_id )->save();

			Logger::info( "Failed to reoptimize image {$image_id}: " . $e->getMessage() );
		}

		$queue
			->set_current_image_id( null )
			->save();

		try {
			if ( $queue->has_more_images() ) {
				Async_Operation::create(
					Async_Operation_Hook::REOPTIMIZE_BULK,
					[ 'operation_id' => $queue->get_operation_id() ],
					Async_Operation_Queue::OPTIMIZE
				);
			} else {
				$queue->delete();
			}
		} catch ( Throwable $t ) {
			Logger::error( "Failed to create next async operation or delete queue: " . $t->getMessage() );
		}
	}

	/**
	 * Renders the bulk optimization notice
	 *
	 * @return void
	 */
	public function render_bulk_optimization_notice() {
		$queue = new Bulk_Optimization_Queue( Bulk_Optimization_Queue_Type::OPTIMIZATION );
		$is_in_progress = $queue->exists();
		?>
		<div class="notice notice-info notice image-optimizer__notice image-optimizer__notice--info image-optimizer__notice--bulk-tip"
				style="display: <?php echo $is_in_progress ? 'block' : 'none'; ?>">
			<p>
				<b>
					<?php esc_html_e(
						'Heads up!',
						'image-optimization'
					); ?>
				</b>

				<span>
					<?php esc_html_e(
						'Bulk optimizing may take a lot of processing and server time, depending on the number of images. Your site will still work smoothly until the processing is all done, without any downtime.',
						'image-optimization'
					); ?>
				</span>
			</p>
		</div>
		<?php
	}

	public function __construct() {
		add_action( Async_Operation_Hook::OPTIMIZE_BULK, [ $this, 'optimize_bulk' ] );
		add_action( Async_Operation_Hook::REOPTIMIZE_BULK, [ $this, 'reoptimize_bulk' ] );

		add_action( 'current_screen', function () {
			if ( Utils::is_bulk_optimization_page() ) {
				add_filter( 'admin_footer_text', [ $this, 'render_bulk_optimization_notice' ] );
			}
		} );
	}
}
