<?php
namespace WPO\WC\PDF_Invoices_Pro\Cloud\Abstracts;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

if ( ! class_exists( '\\WPO\\WC\\PDF_Invoices_Pro\\Cloud\\Abstracts\\Cloud_API' ) ) :

abstract class Cloud_API {

	/**
	 * Cloud storage settings
	 * 
	 * @var array
	 */
	public static $cloud_storage_settings;
	
	/**
	 * Base auth URL
	 * 
	 * @var string
	 */
	public $base_auth_url;
	
	/**
	 * Enabled
	 * 
	 * @var bool
	 */
	public $enabled;
	
	/**
	 * Service slug
	 * 
	 * @var string
	 */
	public static $service_slug = '';
	
	/**
	 * Service name
	 * 
	 * @var string
	 */
	public static $service_name = '';
	
	/**
	 * Service logo
	 * 
	 * @var string
	 */
	public static $service_logo = '';
	
	/**
	 * Service authorization URL
	 * 
	 * @var string
	 */
	public static $service_authorization_url;
	
	/**
	 * Service refresh token URL
	 * 
	 * @var string
	 */
	public static $service_refresh_token_url;
	
	/**
	 * Service API settings option
	 * 
	 * @var string
	 */
	public static $service_api_settings_option;
	
	/**
	 * Service API settings
	 * 
	 * @var array
	 */
	public static $service_api_settings;
	
	/**
	 * Service access token
	 * 
	 * @var string
	 */
	public static $service_access_token;

	/**
	 * Construct
	 *
	 * @return void
	 */
	public function __construct() {
		if ( ! class_exists( '\\WPO\\WC\\PDF_Invoices_Pro\\Cloud_Storage' ) ) {
			self::log( 'critical', 'Cloud_Storage class not found' );
		}
		
		$service_enabled = \WPO\WC\PDF_Invoices_Pro\Cloud_Storage::get_service_enabled(); // it's an array
		
		if ( empty( $service_enabled ) ) {
			return;
		}
		
		self::$cloud_storage_settings      = \WPO\WC\PDF_Invoices_Pro\Cloud_Storage::get_latest_settings();
		$this->base_auth_url               = apply_filters( 'wpo_wcpdf_cloud_base_auth_url', 'https://auth.wpovernight.com/auth/' );
		$this->enabled                     = self::is_enabled();
		self::$service_slug                = key( $service_enabled );
		self::$service_name                = reset( $service_enabled )['name'];
		self::$service_logo                = reset( $service_enabled )['logo'];
		self::$service_authorization_url   = $this->base_auth_url . self::$service_slug . '/?action=get_auth_url&return=' . urlencode( admin_url( 'admin.php?page=wpo_wcpdf_options_page&tab=cloud_storage' ) );
		self::$service_refresh_token_url   = $this->base_auth_url . self::$service_slug . '/?action=refresh_token';
		self::$service_api_settings_option = 'wpo_wcpdf_' . self::$service_slug . '_api_settings';
		self::$service_api_settings        = self::get_last_api_settings();
		self::$service_access_token        = $this->get_access_token();
	}

	/**
	 * Checks if the API is enabled
	 *
	 * @return bool
	 */
	public static function is_enabled(): bool {
		if ( ! empty( self::$cloud_storage_settings ) && isset( self::$cloud_storage_settings['enabled'] ) ) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Get Access token from the cloud storage settings
	 * 
	 * @return string|bool string when available, false when not set
	 */
	public function get_access_token() {
		$service_api_settings = self::get_last_api_settings();
		return $service_api_settings['access_token'] ?? false;
	}

	/**
	 * Shows cloud service authorization notice
	 * 
	 * @param string $auth_url
	 * 
	 * @return void
	 */
	public function auth_message( string $auth_url ): void {
		?>
		<div class="notice notice-warning wcpdf-pro-cloud-storage-notice inline">
			<p class="service-logo"><?php echo self::$service_logo; ?><?php echo self::$service_name; ?></p>
			<p>
				<strong>
					<?php
						printf(
							/* translators: cloud service */
							__( 'Authorize %s service!', 'wpo_wcpdf_pro' ),
							self::$service_name
						);
					?>
				</strong>
			</p>
			<p>
				<?php
					printf(
						/* translators: 1. cloud service, 2-3. <a> tags  */
						__( 'Visit %1$s via %2$sthis link%3$s to authorize your account.' , 'wpo_wcpdf_pro' ),
						self::$service_name,
						'<a href="' . $auth_url . '">',
						'</a>'
					);
				?>
			</p>
		</div>
		<?php
	}
	
	/**
	 * Shows no license notice
	 * 
	 * @return void
	 */
	public function no_license_message(): void {
		?>
		<div class="notice notice-warning wcpdf-pro-cloud-storage-notice inline">
			<p class="service-logo"><?php echo self::$service_logo; ?><?php echo self::$service_name; ?></p>
			<p><strong><?php _e( 'Activate your Professional extension license!', 'wpo_wcpdf_pro' ); ?></strong></p>
			<p>
				<?php
					printf(
						/* translators: 1. cloud service, 2. open anchor tag, 3. open anchor tag, 4. close anchor tag */
						__( 'To keep %1$s API limits under control, you are required to activate your Professional extension license to use this service. See how to activate your license %2$shere%4$s, or clear the cache %3$shere%4$s.', 'wpo_wcpdf_pro' ),
						self::$service_name,
						'<a href="https://docs.wpovernight.com/general/how-to-get-the-latest-version/#activating-your-license" target="_blank">',
						'<a href="' . esc_url( admin_url( 'admin.php?page=wpo_wcpdf_options_page&tab=debug&section=tools' ) ) . '">',
						'</a>'
					);
				?>
			</p>
		</div>
		<?php
	}
	
	/**
	 * Gets the destination folder(s)
	 * 
	 * @param string $filepath
	 * @param \WC_Abstract_Order|null $order
	 * @param string|null $document_type
	 * 
	 * @return string
	 */
	public function get_destination_folder( string $filepath, ?\WC_Abstract_Order $order, ?string $document_type ): string {
		$settings = self::$cloud_storage_settings;

		// Possible keys and valid values for access_type
		$access_type_keys = [
			'dropbox' => [ 'root_folder' ],
			'gdrive'  => [ 'custom_folder' ],
		];

		$destination_folder = '/';

		foreach ( $access_type_keys as $service => $valid_values ) {
			$access_key      = "{$service}_access_type";
			$folder_key      = "{$service}_destination_folder";

			if (
				isset( $settings[ $access_key ], $settings[ $folder_key ] ) &&
				in_array( $settings[ $access_key ], $valid_values, true ) &&
				! empty( $settings[ $folder_key ] )
			) {
				$destination_folder = '/' . trim( str_replace( '\\', '/', $settings[ $folder_key ] ), '\/' ) . '/';
				break; // stop on first match
			}
		}

		// append year/month according to setting
		$date               = $this->get_file_date_with_fallback( $filepath );
		$destination_folder = $this->maybe_append_year_month_folders( $destination_folder, $date );

		// filters
		$destination_folder = apply_filters_deprecated( 'wpo_wcpdf_dropbox_destination_folder', array( $destination_folder, $order, $document_type, $filepath, $this ), '2.16.0', 'wpo_wcpdf_cloud_service_destination_folder' );
		$destination_folder = apply_filters( 'wpo_wcpdf_cloud_service_destination_folder', $destination_folder, $order, $document_type, $filepath, $this );

		return $destination_folder;
	}

	/**
	 * Append YYYY/MM/ to a path (for a given date) if setting enabled
	 * 
	 * @param string $path
	 * @param int $date
	 * 
	 * @return string
	 */
	public function maybe_append_year_month_folders( string $path, int $date ): string {
		if ( ! empty( self::$cloud_storage_settings['year_month_folders'] ) ) {
			$path = sprintf( '%s/%s/%s/', untrailingslashit( $path ), date( "Y", $date ), date( "m", $date ) );
		}
		return $path;
	}

	/**
	 * Get the date from a file if we can
	 * 
	 * @param string|null $file
	 * 
	 * @return int
	 */
	public function get_file_date_with_fallback( ?string $file = null ): int {
		if ( ! empty( $file ) ) {
			$date = filemtime( $file );
		}
		
		if ( empty( $date ) ) {
			$date = time();
		}
		
		return $date;
	}

	/**
	 * Write logs enabled in cloud storage settings
	 * 
	 * @param string $level
	 * @param string $message
	 * 
	 * @return void
	 */
	public static function log( string $level, string $message ): void {
		$general_settings   = self::$cloud_storage_settings;
		$cloud_service_slug = isset( $general_settings['cloud_service'] ) ? $general_settings['cloud_service'] : null;
		
		if ( isset( $general_settings['api_log'] ) ) {
			if ( function_exists( 'wc_get_logger' ) ) {
				$wc_logger = wc_get_logger();
				$context   = array( 'source' => 'wpo-wcpdf-' . $cloud_service_slug );
				$wc_logger->log( $level, $message, $context );
			} else {
				$current_date_time = date( 'Y-m-d H:i:s' );
				$message           = $current_date_time.' '.$message."\n";

				file_put_contents( plugin_dir_path(__FILE__) . '/wpo_wcpdf_'.$cloud_service_slug.'_log.txt', $message, FILE_APPEND );
			}
		}
	}

	/**
	 * Validates a JSON string
	 * 
	 * @param string $string
	 * 
	 * @return array|bool
	 */
	public function maybe_json_decode( string $string ) {
		$decoded = json_decode( $string, true );
		
		if ( json_last_error() == JSON_ERROR_NONE ) {
			return $decoded;
		} else {
			return false;
		}
	}

	/**
	 * Insert array of settings after a specific existing setting ID
	 * 
	 * @param array  $settings          The original settings.
	 * @param array  $append_settings   The settings to append.
	 * @param string $setting_id        ID of the setting after which to append
	 * 
	 * @return array
	 */
	public function append_settings_after_setting_id( array $settings, array $append_settings, string $setting_id ): array {
		$settings_ids = array_column( $settings, 'id' );
		
		if ( ! in_array( $setting_id, $settings_ids ) ) {
			return $settings;
		}
		
		$offset = array_search( $setting_id, $settings_ids ) + 1;
		
		return array_merge( array_slice( $settings, 0, $offset, true ), (array) $append_settings, array_slice( $settings, $offset, null, true ) );
	}
	
	/**
	 * Gets the user account informations (name and email)
	 * 
	 * @return string
	 */
	public function get_account_info(): string {
		return '';
	}
	
	/**
	 * Sets the account data in service settings
	 * 
	 * @param array $data
	 * 
	 * @return void
	 */
	public function set_account_data( array $data ): void {
		$this->access_token   = ! empty( $data['access_token'] ) ? $data['access_token'] : $this->access_token;
		$service_api_settings = self::get_last_api_settings();
		$account_info         = $this->get_account_info();

		if ( ! empty( $account_info ) ) {
			$service_api_settings['account_info'] = $account_info;
		} else {
			unset( $service_api_settings['account_info'] );
		}
		
		$service_api_settings = array_merge( $service_api_settings, $data );

		update_option( self::$service_api_settings_option, $service_api_settings );
	}
	
	/**
	 * Gets the last API settings
	 * 
	 * @return array
	 */
	public static function get_last_api_settings(): array {
		return (array) get_option( self::$service_api_settings_option, array() );
	}

	/**
	 * Delete API setting
	 * 
	 * @param string $setting_name
	 * 
	 * @return array
	 */
	public function delete_api_setting( string $setting_name ): array {
		$service_api_settings = self::get_last_api_settings();
		unset( $service_api_settings[ $setting_name ] );
		update_option( self::$service_api_settings_option, $service_api_settings );
		
		return $service_api_settings;
	}
	
	/**
	 * Displays the authorization notice for the service
	 * 
	 * @param string $active_tab
	 * @param string $active_section
	 * 
	 * @return void
	 */
	public function api_auth_message( string $active_tab, string $active_section ): void {
		if ( 'cloud_storage' === $active_tab && empty( $this->access_token ) && $this->enabled ) {
			if ( 'valid' === WPO_WCPDF_Pro()->functions->get_pro_license_status() ) {
				$this->auth_message( $this->authorization_url );
			} else {
				$this->no_license_message();
			}
		}
	}
	
	/**
	 * Handles the authorization success
	 * 
	 * @return void
	 */
	public function auth_success(): void {
		if (
			isset( $_GET['page'] ) && 'wpo_wcpdf_options_page' === $_GET['page'] &&
			isset( $_GET['tab'] ) && 'cloud_storage' === $_GET['tab'] &&
			isset( $_GET['action'] ) && 'auth_success' === $_GET['action'] &&
			! empty( $_GET['access_token'] )
		) {
			$service_api_settings = self::get_last_api_settings();
			
			$data = array(
				'access_token'  => trim( $_GET['access_token'] ),
				'refresh_token' => ! empty( $service_api_settings['refresh_token'] ) && empty( $_GET['refresh_token'] ) ? $service_api_settings['refresh_token'] : $_GET['refresh_token'],
				'expires_in'    => isset( $_GET['expires_in'] ) ? absint( $_GET['expires_in'] ) : '',
			);
			
			try {
				// set account data
				$this->set_account_data( $data );
				
				if ( 'dropbox' === $this->slug ) {
					delete_option( 'wpo_wcpdf_using_legacy_dropbox_api_token' ); // was using legacy Dropbox API token
				}

				self::log( 'info', "{$this->slug} account data successfully saved: auth_success()" );
				
				// Redirect to settings page
				wp_safe_redirect( esc_url_raw( admin_url( 'admin.php?page=wpo_wcpdf_options_page&tab=cloud_storage&auth=success' ) ) );
				exit;
				
			} catch ( \Throwable $e ) {
				self::log(
					'error',
					sprintf(
						'%s failed to save account data: %s in %s:%d',
						$this->slug,
						$e->getMessage(),
						$e->getFile(),
						$e->getLine()
					)
				);
			}
		}
	}
	
	/**
	 * Displays the authorization success notice
	 * 
	 * @return void
	 */
	public function display_authorization_success_notice(): void {
		if ( isset( $_GET['auth'] ) && 'success' === $_GET['auth'] ) {
			echo '<div class="notice notice-success"><p>' . sprintf(
				/* translators: %s cloud service name */
				__( '%s connection established!', 'wpo_wcpdf_pro' ),
				self::$service_name
			) . '</p></div>';
		}
	}
	
	/**
	 * Refreshes the access token from AJAX
	 * 
	 * @return void
	 */
	public function ajax_refresh_token(): void {
		check_ajax_referer( 'generate_wpo_wcpdf', '_wpnonce' );
		
		$request      = stripslashes_deep( $_REQUEST );
		$redirect     = isset( $request['redirect'] ) && wc_string_to_bool( $request['redirect'] );
		$redirect_url = esc_url_raw( admin_url( 'admin.php?page=wpo_wcpdf_options_page&tab=cloud_storage' ) );
		
		if (
			! isset( $request['service'] ) ||
			$this->slug !== $request['service'] ||
			! isset( $request['action'] ) ||
			'wpo_wcpdf_cloud_refresh_token' !== $request['action']
		) {
			self::log( 'error', 'invalid request: ajax_refresh_token()' );
			
			if ( $redirect ) {
				wp_redirect( $redirect_url );
				exit;
			} else {
				return;
			}
		}
		
		$this->refresh_token( $redirect );
	}
	
	/**
	 * Refreshes the access token
	 * 
	 * @param bool $redirect
	 * 
	 * @return void
	 */
	public function refresh_token( bool $redirect = false ): void {
		$service_api_settings = self::get_last_api_settings();
		$redirect_url         = esc_url_raw( admin_url( 'admin.php?page=wpo_wcpdf_options_page&tab=cloud_storage' ) );
		
		if ( empty( $service_api_settings ) || ! isset( $service_api_settings['refresh_token'] ) ) {
			self::log( 'error', 'no refresh token found: refresh_token()' );
			
			if ( $redirect ) {
				wp_redirect( $redirect_url );
				exit;
			} else {
				return;
			}
		}
		
		$refresh_token_url = $this->refresh_token_url . '&token=' . $service_api_settings['refresh_token'];
		$response          = wp_remote_get( $refresh_token_url );
		
		if ( is_wp_error( $response ) ) {
			self::log( 'error', $response->get_error_message() );
			
			if ( $redirect ) {
				wp_redirect( $redirect_url );
				exit;
			} else {
				return;
			}
		}
		
		$body  = wp_remote_retrieve_body( $response );
		$data  = json_decode( $body, true );
		$error = false;
		
		if ( isset( $data['status'] ) && 'error' === $data['status'] ) {
			$error = true;
			
		} elseif ( isset( $data['refresh'] ) && wc_string_to_bool( $data['refresh'] ) ) {
			unset( $data['refresh'] );
			
			if ( isset( $data['access_token'] ) && $data['refresh_token'] && isset( $data['expires_in'] ) ) {
				$this->set_account_data( $data );
				
				if ( 'dropbox' === $this->slug ) {
					delete_option( 'wpo_wcpdf_using_legacy_dropbox_api_token' ); // was using legacy Dropbox API token
				}
				
				self::log( 'info', 'token successfully refreshed: ' . $data['access_token'] );
			} else {
				$error = true;
			}
		}
		
		if ( $error ) {
			self::log( 'error', 'error refreshing token: ' . $body );
		}
		
		if ( $redirect ) {
			wp_redirect( $redirect_url );
			exit;
		} else {
			return;
		}
	}
	
	/**
	 * Check if token is expired
	 * 
	 * @return bool
	 */
	public function is_token_expired(): bool {
		$service_api_settings = self::get_last_api_settings();
		$expires_in           = isset( $service_api_settings['expires_in'] ) ? absint( $service_api_settings['expires_in'] ) : '';
		$now                  = time();
		
		if ( '' === $expires_in ) {
			return false;
		}
		
		if ( $expires_in < $now ) {
			return true;
		}
		
		return false;
	}
	
	/**
	 * Check account credentials
	 * 
	 * @return bool
	 */
	public static function are_account_credentials_set(): bool {
		$service_api_settings = self::get_last_api_settings();
		
		if ( empty( $service_api_settings ) ) {
			return false;
		}
		
		if ( self::is_using_dropbox_legacy_api_token() ) {
			return true;
		}
		
		if ( ! isset( $service_api_settings['access_token'] ) || ! isset( $service_api_settings['refresh_token'] ) || ! isset( $service_api_settings['expires_in'] ) ) {
			return false;
		}
		
		return true;
	}
	
	/**
	 * Revoke necessary notice
	 * 
	 * @return void
	 */
	public function revoke_necessary(): void {
		if ( self::is_using_dropbox_legacy_api_token() ) {
			return;
		}
		
		$service_api_settings = self::get_last_api_settings();
		
		if ( $this->enabled && isset( $service_api_settings['access_token'] ) && ( empty( $service_api_settings['refresh_token'] ) || empty( $service_api_settings['expires_in'] ) ) ) {
			?>
			<div class="notice notice-error wcpdf-pro-cloud-storage-notice">
				<p class="service-logo"><?php echo self::$service_logo; ?><?php echo self::$service_name; ?></p>
				<p><strong><?php echo WPO_WCPDF_Pro()->plugin_name; ?></strong></p>
				<p>
					<?php
						switch ( self::$service_slug ) {
							case 'dropbox':
								$url = 'https://docs.wpovernight.com/woocommerce-pdf-invoices-packing-slips/authorizing-dropbox/#revoke-and-re-authorize';
								break;
							case 'gdrive':
								$url = 'https://docs.wpovernight.com/woocommerce-pdf-invoices-packing-slips/authorizing-google-drive/#google-drive-refresh-token-issue';
								break;
							default:
								$url = '';
								break;
						}
						printf(
							/* translators: 1. open anchor tag, 2. close anchor tag */
							__( 'Your access token has been retrieved, but the refresh token or the token expiration details are missing. To fix this, please revoke the current permissions for this app following the steps outlined in our %1$srevoke and reauthorize documentation%2$s. After revoking, reauthorize the application to ensure you receive all necessary tokens for full functionality.', 'wpo_wcpdf_pro' ),
							'<a href="' . esc_url( $url ) . '" target="_blank">',
							'</a>'
						);
					?>
				</p>
			</div>
			<?php
		}
	}
	
	/**
	 * Check if still using legacy Dropbox API token
	 * 
	 * @return bool
	 */
	public static function is_using_dropbox_legacy_api_token(): bool {
		$service_api_settings   = self::get_last_api_settings();
		$using_legacy_api_token = wc_string_to_bool( get_option( 'wpo_wcpdf_using_legacy_dropbox_api_token', 'no' ) );
		
		if ( ! empty( $service_api_settings['access_token'] ) && 'dropbox' === self::$service_slug && $using_legacy_api_token ) {
			return true;
		}
		
		return false;
	}
	
	/**
	 * Unlink service settings
	 *
	 * @param bool $delete_refresh_token
	 * @return void
	 */
	public static function unlink_service_settings( bool $delete_refresh_token = false ): void {
		$service_api_settings = get_option( 'wpo_wcpdf_' . self::$service_slug . '_api_settings' );

		if ( ! $delete_refresh_token && isset( $service_api_settings['refresh_token'] ) ) {
			$refresh_token = $service_api_settings['refresh_token'];
		}

		$service_api_settings                  = array();
		$service_api_settings['refresh_token'] = $refresh_token ?? '';

		update_option( 'wpo_wcpdf_' . self::$service_slug . '_api_settings', $service_api_settings );
	}

}

endif; // class_exists
