<?php

/**
 * Class WRIO_WebP_Api processing images from processing queue, sends them to API and saves locally.
 *
 * @author Alexander Teshabaev <sasha.tesh@gmail.com>
 */
class WRIO_WebP_Api {

	/**
	 * @var string API url.
	 */
	//private $_api_url = 'http://142.93.91.206/';
	private $_api_url = 'https://dashboard.robinoptimizer.com/';

	/**
	 * @var int|null Attachment ID.
	 */
	private $_models = null;

	/**
	 * @var null|int UNIX epoch when last request was processed.
	 */
	private $_last_request_tick = null;


	/**
	 * WRIO_WebP_Api constructor.
	 *
	 * @param RIO_Process_Queue[] $model Item to be converted to WebP.
	 */
	public function __construct( $model ) {
		$this->_models = $model;
	}

	/**
	 * Process image queue based on provided attachment ID.
	 *
	 * When attachment has multiple thumbnails, all of them would be converted one after another.
	 * Notice: when there are no items queried for provided data, false would be returned.
	 *
	 * @param bool $quota decr quota?.
	 *
	 * @return bool true on success execution, false on failure or missing item in queue.
	 */
	public function process_image_queue( $quota = false ) {
		$thumb_count = count( $this->_models ) - 1;

		foreach ( $this->_models as $model ) {
			/**
			 * @var RIOP_WebP_Extra_Data $extra_data
			 */
			$extra_data = $model->get_extra_data();

			if ( $extra_data === null ) {
				continue;
			}

			$response = $this->request( $model, $quota );

			if ( $this->can_save( $response ) && $this->save_file( $response, $model ) ) {
				$extra_data->set_thumbnails_count( $thumb_count );
				$model->set_extra_data( $extra_data );

				$this->update( $model );
			}
		}

		return true;
	}

	/**
	 * Request API
	 *
	 * @param RIO_Process_Queue $model Queue model.
	 * @param bool $quota decr quota?.
	 *
	 * @return array|bool|WP_Error
	 */
	public function request( $model, $quota = false ) {

		if ( $this->_last_request_tick === null ) {
			$this->_last_request_tick = time();
		} else {
			if ( is_int( $this->_last_request_tick ) && ( time() - $this->_last_request_tick ) < 1 ) {
				// Need to have some rest before calling REST :D to comply with API request limit
				sleep( 2 );
			}

			$this->_last_request_tick = time();
		}

		$optimization_server = WRIO_Plugin::app()->getPopulateOption( 'image_optimization_server' );
		if ( $optimization_server !== 'server_5' ) {
			WRIO_Plugin::app()->logger->warning( 'To use webp compression you need to switch to using a premium server' );

			return false;
		}

		if ( ! wrio_is_license_activate() ) {
			WRIO_Plugin::app()->logger->error( "Unable to get license to make proper request to the API" );

			return false;
		}

		$transient_string = md5( WRIO_Plugin::app()->getPrefix() . '_processing_image' . $model->get_item_hash() );

		$transient_value = get_transient( $transient_string );

		if ( is_numeric( $transient_value ) && (int) $transient_value === 1 ) {
			WRIO_Plugin::app()->logger->info( sprintf( 'Skipping to wp_remote_get() as transient "%s" already exist. Usually it means that no request was returned yet', $transient_string ) );

			return false;
		}

		set_transient( $transient_string, 1 );

		$url = $this->_api_url . 'v1/image/convert?';

		$query_data = [ 'format' => 'webp' ];
		if ( $quota ) {
			$query_data ['type'] = 'webp';
		}
		$url .= http_build_query( $query_data );

		/**
		 * @var RIOP_WebP_Extra_Data $extra_data
		 */
		$extra_data = $model->get_extra_data();

		$multipartBoundary = '--------------------------' . microtime( true );

		$file_contents = file_get_contents( $extra_data->get_source_path() );

		$body = "--" . $multipartBoundary . "\r\n" . "Content-Disposition: form-data; name=\"file\"; filename=\"" . basename( $extra_data->get_source_path() ) . "\"\r\n" . "Content-Type: " . $model->get_original_mime_type() . "\r\n\r\n" . $file_contents . "\r\n";

		$body .= "--" . $multipartBoundary . "--\r\n";

		$headers = [
			// should be base64 encoded, otherwise API would fail authentication
			'Authorization' => 'Bearer ' . base64_encode( wrio_get_license_key() ),
			'PluginId'      => wrio_get_freemius_plugin_id(),
			'Content-Type'  => 'multipart/form-data; boundary=' . $multipartBoundary,
		];

		$response = wp_remote_post( $url, [
			'timeout' => 60,
			'headers' => $headers,
			'body'    => $body,
		] );

		delete_transient( $transient_string );

		return $response;
	}

	/**
	 * Process response from API.
	 *
	 * @param array|WP_Error|false $response
	 *
	 * @return bool True means response image was successfully saved, false on failure.
	 */
	public function can_save( $response ) {
		\WRIO_Plugin::app()->logger->info( 'WebP convertation: Checks to save a webp by response.' );
		//\WRIO_Plugin::app()->logger->debug( var_export( $response, true ) );

		if ( is_wp_error( $response ) ) {
			WRIO_Plugin::app()->logger->error( sprintf( 'Error response from API. Code: %s, error: %s', $response->get_error_code(), $response->get_error_message() ) );

			return false;
		}

		if ( false === $response ) {
			WRIO_Plugin::app()->logger->error( 'Unknown response returned from API or it was not requested, failing to process response' );

			return false;
		}

		$content_disposition = wp_remote_retrieve_header( $response, 'content-disposition' );

		if ( 0 === strpos( $content_disposition, 'attachment;' ) ) {

			$body = wp_remote_retrieve_body( $response );

			if ( empty( $body ) ) {
				WRIO_Plugin::app()->logger->error( 'Response returned content-disposition header as "attachment;", but empty body returned, failing to proceed' );

				return false;
			}

			\WRIO_Plugin::app()->logger->info( 'WebP convertation: Image can be saved. ' );

			return true;
		}

		$response_text = wp_remote_retrieve_body( $response );

		if ( ! empty( $response_text ) ) {
			$response_json = json_decode( $response_text );

			if ( ! empty( $response_json ) ) {
				if ( isset( $response_json->error ) && ! empty( $response_json->error ) ) {
					WRIO_Plugin::app()->logger->error( sprintf( 'Unable to convert attachment as API returned error: "%s"', $response_json->error ) );
				}

				if ( isset( $response_json->status ) && 401 === (int) $response_json->status ) {
					WRIO_Plugin::app()->logger->error( sprintf( 'Error response from API. Code: %s, error: %s', $response_json->message, $response_json->code ) );
				}
			}
		}

		return false;
	}

	/**
	 * Save file from response.
	 *
	 * It is assumed that it was checked by can_save() method.
	 *
	 * @param array|WP_Error|false $response
	 * @param RIO_Process_Queue $queue_model
	 *
	 * @return bool
	 * @see can_save() for further information.
	 *
	 */
	public function save_file( $response, $queue_model ) {

		try {
			$save_path = static::get_save_path( $queue_model );
		} catch ( \Exception $exception ) {
			WRIO_Plugin::app()->logger->error( sprintf( 'Unable to process response failed to get save path: "%s"', $exception->getMessage() ) );

			return false;
		}

		\WRIO_Plugin::app()->logger->info( sprintf( 'WebP convertation: Try to save webp image in %s.', $save_path ) );

		$body = wp_remote_retrieve_body( $response );

		$file_saved = @file_put_contents( $save_path, $body );

		if ( ! $file_saved ) {
			/**
			 * @var $http_response WP_HTTP_Requests_Response
			 */
			$http_response = $response['http_response'];
			WRIO_Plugin::app()->logger->error( sprintf( 'Failed to save file "%s" under %s with file_put_contents()', $save_path, $http_response->get_response_object()->url ) );

			return false;
		}

		\WRIO_Plugin::app()->logger->info( 'WebP convertation: Image saved successfully!' );

		return true;
	}

	/**
	 * Update processing item data to finish its cycle.
	 *
	 * @param RIO_Process_Queue $queue_model Queue model to be update.
	 *
	 * @return bool
	 */
	public function update( $queue_model ) {

		try {
			$save_path = static::get_save_path( $queue_model );
		} catch ( \Exception $exception ) {
			WRIO_Plugin::app()->logger->error( sprintf( 'Unable to update queue model #%s as of exception: %s', $queue_model->get_id(), $exception->getMessage() ) );

			return false;
		}

		$queue_model->result_status = RIO_Process_Queue::STATUS_SUCCESS;
		$queue_model->final_size    = filesize( $save_path );

		$image_statistics = WRIO_Image_Statistic::get_instance();
		wp_suspend_cache_addition( true ); // останавливаем кеширование
		$image_statistics->addToField( 'webp_optimized_size', $queue_model->final_size );
		$image_statistics->save();
		wp_suspend_cache_addition(); // возобновляем кеширование

		/**
		 * @var RIOP_WebP_Extra_Data $updated_extra_data
		 */
		$updated_extra_data = $queue_model->get_extra_data();
		$updated_extra_data->set_converted_src( $this->get_save_url( $queue_model ) );
		$updated_extra_data->set_converted_path( $save_path );

		$queue_model->extra_data = $updated_extra_data;

		/**
		 * Хук срабатывает после успешной конвертации в WebP
		 *
		 * @param RIO_Process_Queue $queue_model
		 *
		 * @since 1.2.0
		 *
		 */
		do_action( 'wbcr/rio/webp_success', $queue_model );

		return $queue_model->save();
	}

	/**
	 * Get complete save url.
	 *
	 * @param RIO_Process_Queue $queue_model Instance of queue item.
	 *
	 * @return string
	 */
	public function get_save_url( $queue_model ) {
		/**
		 * @var $extra_data RIOP_WebP_Extra_Data
		 */
		$extra_data = $queue_model->get_extra_data();

		if ( empty( $extra_data ) ) {
			WRIO_Plugin::app()->logger->error( sprintf( 'Unable to get extra data for queue item #%s', $queue_model->get_id() ) );

			return null;
		}

		$origin_file_name = wp_basename( $extra_data->get_source_src() );
		$webp_file_name   = trim( wp_basename( $extra_data->get_source_path() ) ) . '.webp';

		return str_replace( $origin_file_name, $webp_file_name, $extra_data->get_source_src() );
	}

	/**
	 * Get absolute save path.
	 *
	 * @param \RIO_Process_Queue $queue_model
	 *
	 * @return bool
	 * @throws Exception on failure to create missing directory
	 */
	public static function get_save_path( $queue_model ) {
		/**
		 * @var $extra_data RIOP_WebP_Extra_Data
		 */
		$extra_data = $queue_model->get_extra_data();

		if ( empty( $extra_data ) ) {
			WRIO_Plugin::app()->logger->error( sprintf( 'Unable to get extra data for queue item #%s', $queue_model->get_id() ) );

			return null;
		}

		$path = dirname( $extra_data->get_source_path() );

		// Create DIR when does not exist
		if ( ! file_exists( $path ) ) {
			$message = sprintf( 'Failed to create directory %s with mode %s recursively', $path, 0755 );
			WRIO_Plugin::app()->logger->error( $message );
			throw new \Exception( $message );
		}

		return trailingslashit( $path ) . trim( wp_basename( $extra_data->get_source_path() ) ) . '.webp';
	}
}
