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

use WPO\WC\PDF_Invoices_Pro\Cloud\Abstracts\Cloud_API;
use WPO_IPS_PRO_Vendor_Google_Client                              as Google_Client;
use WPO\WC\PDF_Invoices_Pro\Vendor\Google\Service\Drive           as Google_Service_Drive;
use WPO\WC\PDF_Invoices_Pro\Vendor\Google\Service\Drive\DriveFile as Google_Service_Drive_DriveFile;

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

if ( ! class_exists( '\\WPO\\WC\\PDF_Invoices_Pro\\Cloud\\Gdrive\\Gdrive_API' ) ) :

/**
 * GDrive API Class
 * 
 * PHP 8 or greater
 * 
 * @class  \WPO\WC\PDF_Invoices_Pro\Cloud\Gdrive\Gdrive_API
 */

class Gdrive_API extends Cloud_API {

	/**
	 * @var string
	 */
	private $app_name;
	
	/**
	 * @var string
	 */
	private $client_id;

	/**
	 * @var bool|string
	 */
	public $slug;

	/**
	 * @var string
	 */
	public $name;
	
	/**
	 * @var string
	 */
	public $authorization_url;
	
	/**
	 * @var string
	 */
	public $refresh_token_url;

	/**
	 * @var bool|string
	 */
	public $access_token;

	/**
	 * @var Google_Client|object|false
	 */
	private $client;

	/**
	 * @var Google_Service_Drive|object|null
	 */
	private $service;

	/**
	 * Construct
	 * 
	 * @return void
	 */
	public function __construct() {
		// Parent constructor
		parent::__construct();

		// Check if we are dealing with this service API
		if ( 'gdrive' !== parent::$service_slug ) {
			return;
		}
		
		// set this service slug, name and token
		$this->slug              = parent::$service_slug;
		$this->name              = parent::$service_name;
		$this->access_token      = parent::$service_access_token;
		$this->authorization_url = parent::$service_authorization_url;
		$this->refresh_token_url = parent::$service_refresh_token_url;

		// set the API related properties
		$this->app_name          = apply_filters( 'wpo_wcpdf_' . $this->slug . '_app_name', 'PDF Invoices & Packing Slips for WooCommerce' );
		$this->client_id         = apply_filters( 'wpo_wcpdf_' . $this->slug . '_client_id', '' );     // TODO: fill with the live client ID

		// Configure gdrive
		$this->client            = $this->configure_client();
		$this->service           = $this->client ? $this->configure_service( $this->client ) : null;

		add_action( 'admin_notices', array( $this, 'auth_success' ) );
		add_action( 'admin_notices', array( $this, 'revoke_necessary' ) );
		add_action( 'wpo_wcpdf_before_settings_page', array( $this, 'api_auth_message' ), 10, 2 );
		add_action( 'wp_ajax_wpo_wcpdf_cloud_refresh_token', array( $this, 'ajax_refresh_token' ) );
		
		$this->display_authorization_success_notice();
	}

	/**
	 * Configures GDrive API client
	 * 
	 * @return Google_Client|false
	 */
	private function configure_client() {
		if ( empty( $this->access_token ) ) {
			$access_token = $this->get_access_token();
			
			if ( empty( $access_token ) ) {
				self::unlink_service_settings();
				return false;
			}
		}
		
		$client = new Google_Client();
		$client->setApplicationName( $this->app_name );
		$client->setClientId( $this->client_id );
		$client->setScopes( Google_Service_Drive::DRIVE_FILE );
		$client->setAccessType( 'offline' );
		$client->setAccessToken( $this->access_token  );

		return $client;
	}

	/**
	 * Configures GDrive API service
	 * 
	 * @param Google_Client $client
	 * 
	 * @return Google_Service_Drive
	 */
	private function configure_service( Google_Client $client ): Google_Service_Drive {
		return new Google_Service_Drive( $client );
	}

	/**
	 * Gets the Google user account informations (name and email)
	 * 
	 * @return string
	 */
	public function get_account_info(): string {
		try {
			$client = $this->configure_client();
			
			if ( ! $client ) {
				throw new \Exception( 'Error initializing service client: get_account_info()' );
			}
			
			$service = $this->configure_service( $client );
			
			if ( ! $service ) {
				throw new \Exception( 'Error initializing service: get_account_info()' );
			}
			
			$user  = $service->about->get( array( 'fields' => 'user' ) )->getUser();
			$name  = $user->getdisplayName();
			$email = $user->getemailAddress();
			
			return "{$name} [{$email}]";
		} catch ( \Exception $e ) {
			self::log( 'error', "fetching {$this->slug} account info failed:" . $e->getMessage() );
			return '';
		}
	}

	/**
	 * GDrive upload process
	 * 
	 * @param string $filepath
	 * @param \WC_Abstract_Order|null $order
	 * @param string|null $document_type
	 * 
	 * @return array
	 */
	public function upload( string $filepath, ?\WC_Abstract_Order $order = null, ?string $document_type = null ): array {	
		if ( empty( $filepath ) ) {
			self::log( 'error', 'empty file path: upload()' );
			return array( 'error' => __( 'File path is empty', 'wpo_wcpdf_pro' ) );
		}
		
		// check if token is expired
		if ( $this->is_token_expired() ) {
			self::log( 'error', "token expired, refreshing for a new token: upload()" );
			$this->refresh_token();
		}
		
		// check if credentials are set
		if ( ! self::are_account_credentials_set() ) {
			self::log( 'error', 'invalid credentials: upload()' );
			return array( 'error' => __( 'Cloud service credentials not set', 'wpo_wcpdf_pro' ) );
		}
		
		try {
			$this->client = $this->configure_client();
			
			if ( ! $this->client ) {
				throw new \Exception( 'Error initializing service client: upload()' );
			}
			
			$this->service = $this->configure_service( $this->client );
			
			if ( ! $this->service ) {
				throw new \Exception( 'Error initializing service: upload()' );
			}
		} catch ( \Exception $e ) {
			$error_response = $e->getMessage();
			$error_message  = "trying to upload to {$this->name}: " . $error_response;
			self::log( 'error', $error_message );
			return array( 'error' => $error_message );
		}

		// get destination folders
		$destination_filepath = $this->get_destination_folder( $filepath, $order, $document_type );
		$destination_folders  = $this->get_folders_from_file_path( $destination_filepath );

		// grab saved gdrive settings
		$service_api_settings = self::get_last_api_settings();

		// create folders if needed
		$service_api_settings = $this->create_folders( $destination_folders, $service_api_settings );

		// create file
		return $this->create_file( $filepath, $service_api_settings );
	}
	
	/**
	 * Gets the destination folder(s) from the file path
	 * 
	 * @param string $filepath
	 * 
	 * @return array
	 */
	public function get_folders_from_file_path( string $filepath ): array {
		if ( empty( $filepath ) ) {
			return array();
		}
		
		// grab general settings
		$general_settings = self::$cloud_storage_settings;

		if ( '/' !== $filepath ) {
			$folders             = substr( $filepath, 1, -1 );
			$folders             = explode( '/', $folders );
			$destination_folders = array();

			// add year/month dirs
			if ( isset( $general_settings['year_month_folders'] ) ) {
				$year_month = array_slice( $folders , -2, 2, true );  
				
				foreach ( $year_month as $key => $date ) {
					if ( strlen( $date ) === 4 && is_numeric( $date ) ) {
						$destination_folders['year']  = $date;
					} elseif ( strlen( $date ) === 2 && is_numeric( $date ) ) {
						$destination_folders['month'] = $date;
					}
					
					unset( $folders[ $key ] );
				}
			}

			// add 'parent_folder' and other 'sub_folders' (if exist)
			if ( empty( $folders ) ) {
				$destination_folders['parent_folder'] = '/';
			} else {
				foreach ( $folders as $key => $value ) {
					if ( $key === 0 ) {
						$destination_folders['parent_folder'] = $value;
					} else {
						$destination_folders['sub_folders'][] = $value;
					}
				}
			}

		} else {
			$destination_folders = array( 'parent_folder' => '/' );
		}

		return $destination_folders;
	}

	/**
	 * Creates all the required folders inside GDrive
	 * 
	 * @param array $folders
	 * @param array $service_api_settings
	 * 
	 * @return array
	 */
	private function create_folders( array $folders, array $service_api_settings ): array {
		if ( empty( $folders ) ) {
			return $service_api_settings;
		}

		// get the parent folder name
		if ( '/' === $folders['parent_folder'] ) {
			$parent_folder = $this->app_name;
			
			if ( isset( parent::$cloud_storage_settings['gdrive_access_type'] ) && 'root_folder' === parent::$cloud_storage_settings['gdrive_access_type'] ) { // if root folder and no subfolder provided, don't create parent folder
				$parent_folder = null;
			}
		} else {
			$parent_folder = str_replace( '/', '', $folders['parent_folder'] );
		}

		// create parent folder if not exist in gdrive
		if ( empty( $service_api_settings['parent_dir'] ) ) {
			// if we have a parent folder to create
			if ( ! empty( $parent_folder ) ) {
				// create the folder on gdrive
				$service_api_settings = $this->create_folder(
					$parent_folder,
					null,
					'parent_dir',
					$service_api_settings
				);
			}
		} else {
			// get the parent folder name
			if ( ! empty( $parent_folder ) ) {
				if ( $parent_folder !== $service_api_settings['parent_dir']['name'] ) {
					// remove the old one
					$service_api_settings = $this->delete_api_setting( 'parent_dir' );
					
					// create a new parent folder on gdrive
					$service_api_settings = $this->create_folder(
						$parent_folder,
						null,
						'parent_dir',
						$service_api_settings
					);
				}
			} elseif ( isset( parent::$cloud_storage_settings['gdrive_access_type'] ) && 'root_folder' === parent::$cloud_storage_settings['gdrive_access_type'] ) {
				// if not parent folder provided and we have one saved, delete it
				$service_api_settings = $this->delete_api_setting( 'parent_dir' );
			}
		}

		// create the sub-folders dirs
		if ( ! empty( $service_api_settings['parent_dir'] ) ) {
			// we have sub-folders to create
			if ( ! empty( $folders['sub_folders'] ) ) {
				// first we compare the folders with the saved ones
				if ( ! empty( $service_api_settings['sub_folders'] ) ) {
					$saved_folder_names = array();
					$missmatch          = false;
					
					foreach ( $service_api_settings['sub_folders'] as $saved_folder ) {
						$saved_folder_names[] = $saved_folder['name'];
					}
					
					foreach ( $folders['sub_folders'] as $folder_name ) {
						if ( ! in_array( $folder_name, $saved_folder_names ) ) {
							$missmatch = true;
						}
					}
					
					// if we have some missmatch we should delete the saved sub-folders and start again
					if ( $missmatch ) {
						$service_api_settings = $this->delete_api_setting( 'sub_folders' );
					}
				}
				
				// then we create the sub-folders
				if ( empty( $service_api_settings['sub_folders'] ) ) {
					foreach ( $folders['sub_folders'] as $key => $folder_name ) {
						if ( $key === 0 ) {
							// create a new subfolder on gdrive
							$service_api_settings = $this->create_folder(
								$folder_name,
								$service_api_settings['parent_dir']['id'],
								'sub_folders',
								$service_api_settings
							);
						} else {
							$last_key = array_key_last( $folders['sub_folders'] ); // get always the last one
							// create a new subfolder on gdrive
							$service_api_settings = $this->create_folder(
								$folder_name,
								$last_key,
								'sub_folders',
								$service_api_settings
							);
						}
					}
				}

			// we don't have folders to create, but if we have already saved ones we should delete them
			} else {
				if ( ! empty( $service_api_settings['sub_folders'] ) ) {
					$service_api_settings = $this->delete_api_setting( 'sub_folders' );
				}
			}
		}

		// create the year/month dirs
		if ( ! empty( parent::$cloud_storage_settings['year_month_folders'] ) ) {
			// year dir
			if ( ! empty( $service_api_settings['parent_dir'] ) && ! empty( $folders['year'] ) ) {
				// if we have already a year dir, check the parent ID/name match
				if ( ! empty( $service_api_settings['year'] ) ) {
					// if we have sub-folders saved
					if ( ! empty( $service_api_settings['sub_folders'] ) ) {
						// check for ID
						$last_key = array_key_last( $service_api_settings['sub_folders'] ); // get always the last one
						
						if ( $service_api_settings['year']['parents'][0] !== $service_api_settings['sub_folders'][ $last_key ]['id'] ) {
							$this->move_folder(
								$service_api_settings['year'],
								$service_api_settings['sub_folders'][ $last_key ],
								'year',
								$service_api_settings
							);
						}
					} else {
						if ( $service_api_settings['year']['parents'][0] !== $service_api_settings['parent_dir']['id'] ) {
							$this->move_folder(
								$service_api_settings['year'],
								$service_api_settings['parent_dir'],
								'year',
								$service_api_settings
							);
						}
					}

					// check for name
					if ( $service_api_settings['year']['name'] !== $folders['year'] ) {
						$service_api_settings = $this->delete_api_setting( 'year' );
					}
				}

				// if we don't have year dir yet lets create it
				if ( empty( $service_api_settings['year'] ) ) {
					// if we have sub-folders
					if ( ! empty( $service_api_settings['sub_folders'] ) ) {
						$last_key = array_key_last( $service_api_settings['sub_folders'] ); // get always the last one
						// create a new subfolder on gdrive
						$service_api_settings = $this->create_folder(
							$folders['year'],
							$service_api_settings['sub_folders'][ $last_key ]['id'],
							'year',
							$service_api_settings
						);
					} else {
						// create a new subfolder on gdrive
						$service_api_settings = $this->create_folder(
							$folders['year'],
							$service_api_settings['parent_dir']['id'],
							'year',
							$service_api_settings
						);
					}
				}

			// if no parent dir
			} else {
				if ( ! empty( $service_api_settings['year'] ) ) {
					// create a new subfolder on gdrive root
					$service_api_settings = $this->create_folder(
						$folders['year'],
						null,
						'year',
						$service_api_settings
					);
				}
			}

			// month dir
			if ( ! empty( $service_api_settings['year'] ) && ! empty( $folders['month'] ) ) {
				// if we have already a month dir, check the parent ID/name match
				if ( ! empty( $service_api_settings['month'] ) ) {
					// if we have year dir saved
					if ( $service_api_settings['month']['parents'][0] !== $service_api_settings['year']['id'] ) {
						$this->move_folder(
							$service_api_settings['month'],
							$service_api_settings['year'],
							'month',
							$service_api_settings
						);
					}

					// check for name
					if ( $service_api_settings['month']['name'] !== $folders['month'] ) {
						$service_api_settings = $this->delete_api_setting( 'month' );
					}
				}

				// if we don't have dir yet lets create it
				if ( empty( $service_api_settings['month'] ) ) {
					// create a new subfolder on gdrive
					$service_api_settings = $this->create_folder(
						$folders['month'],
						$service_api_settings['year']['id'],
						'month',
						$service_api_settings
					);
				}
			}

		} else {
			if ( ! empty( $service_api_settings['year'] ) ) {
				$service_api_settings = $this->delete_api_setting( 'year' );
			}
			
			if ( ! empty( $service_api_settings['month'] ) ) {
				$service_api_settings = $this->delete_api_setting( 'month' );
			}
		}

		return $service_api_settings;
	}

	/**
	 * Creates the PDF document files inside the respective folder in GDrive
	 * 
	 * @param string $filepath
	 * @param array $service_api_settings
	 * 
	 * @return array
	 */
	private function create_file( string $filepath, array $service_api_settings ): array {
		$file_name = basename( $filepath );

		try {
			if ( ! empty( $service_api_settings['month'] ) ) {
				$parent_id = $service_api_settings['month']['id'];
			} elseif ( ! empty( $service_api_settings['parent_dir'] ) ) {
				if ( empty( $service_api_settings['sub_folders'] ) ) {
					$parent_id = $service_api_settings['parent_dir']['id'];
				} else {
					$last_key  = array_key_last( $service_api_settings['sub_folders'] ); // get always the last one
					$parent_id = $service_api_settings['sub_folders'][ $last_key ]['id'];
				}
			}

			$metadata = array( 'name' => $file_name );

			if ( ! empty( $parent_id ) ) {
				$folder         = $this->service->files->get( $parent_id, array( 'fields' => 'trashed' ) ); // check if parent folder is trashed in GDrive
				$parent_trashed = $folder['trashed'] ? true : false;
				
				if ( ! $parent_trashed ) {
					$metadata['parents'] = array( $parent_id );
				} else {
					// remove the trashed parent and existing sub folders to recreate them again on the next upload, so the IDs can match again
					$service_api_settings = $this->delete_api_setting( 'parent_dir' );
					$service_api_settings = $this->delete_api_setting( 'sub_folders' );
					
					// if we not mention a parent ID the file is stored in GDrive root
					$message = __( 'A parent directory in GDrive was trashed, the file was transfered to the GDrive root directory.', 'wpo_wcpdf_pro' );
					self::log( 'error', $message );
				}
			}

			$content       = file_get_contents( $filepath );

			$file_metadata = new Google_Service_Drive_DriveFile( $metadata );
			$file          = $this->service->files->create( $file_metadata, array(
				'data'       => $content,
				'mimeType'   => 'application/pdf',
				'uploadType' => 'multipart',
				'fields'     => 'id, parents, name'
			) );

			self::log( 'info', "successfully uploaded {$file_name} to {$this->name}: create_file()" );

			return array( 'success' => $file );
		} catch ( \Exception $e ) {
			$error_message = "creating {$this->name} file {$file_name}" . ': ' . $e->getMessage();
			self::log( 'error', $error_message );
			return array( 'error' => $error_message );
		}
	}

	/**
	 * Creates a folder in GDrive
	 * 
	 * @param string $folder_name
	 * @param string|int|null $parent_id
	 * @param string|null $setting_name
	 * @param array $service_api_settings
	 * 
	 * @return array
	 */
	private function create_folder( string $folder_name, $parent_id = null, $setting_name = null, array $service_api_settings = array() ): array {
		if ( empty( $service_api_settings ) ) {
			$service_api_settings = self::get_last_api_settings();
		}
		
		try {
			// prepare metadata
			$metadata = array(
				'name'     => $folder_name,
				'mimeType' => 'application/vnd.google-apps.folder'
			);
			
			if ( ! is_null( $parent_id ) ) {
				$metadata['parents'] = array( $parent_id );
			}

			// create the folder on gdrive
			$file_metadata = new Google_Service_Drive_DriveFile( $metadata );
			$file          = $this->service->files->create( $file_metadata, array( // in gdrive folders are threat has files
				'fields' => 'id, parents, name'
			) );

			// update the gdrive api option
			if ( ! empty( $setting_name ) ) {
				$atts = array(
					'id'      => $file->id,
					'name'    => $file->name,
					'parents' => $file->parents, // array of parent folders
				);
				
				if ( 'sub_folders' === $setting_name ) {
					$service_api_settings[ $setting_name ][] = $atts;
				} else {
					$service_api_settings[ $setting_name ] = $atts;
				}
			}
			update_option( parent::$service_api_settings_option, $service_api_settings );
			self::log( 'info', "successfully created the folder {$folder_name} in {$this->name}: create_folder()" );
		} catch ( \Exception $e ) {
			self::log( 'error', "creating {$this->name} folder {$folder_name}: " . $e->getMessage() );
		}
		
		return self::get_last_api_settings();
	}

	/**
	 * Moves a GDrive folder to another parent folder
	 * 
	 * @param array $folder
	 * @param array $parent
	 * @param string|null $setting_name
	 * @param array	$service_api_settings
	 * 
	 * @return array
	 */
	private function move_folder( array $folder, array $parent, $setting_name = null, array $service_api_settings = array() ): array {
		if ( empty( $service_api_settings ) ) {
			$service_api_settings = self::get_last_api_settings();
		}

		try {
			$empty_file_metadata = new Google_Service_Drive_DriveFile();
			// Retrieve the existing parents to remove
			$file                = $this->service->files->get( $folder['id'], array( 'fields' => 'parents' ) );
			$previous_parents    = join( ',', $file->parents );
			// Move the file to the new folder
			$file                = $this->service->files->update( $folder['id'], $empty_file_metadata, array(
				'addParents'    => $parent['id'],
				'removeParents' => $previous_parents,
				'fields'        => 'id, parents, name'
			) );

			// update the gdrive api option
			if ( ! empty( $setting_name ) ) {
				$atts = array(
					'id'      => $file->id,
					'name'    => $file->name,
					'parents' => $file->parents, // array of parent folders
				);
				
				if ( 'sub_folders' === $setting_name ) {
					$service_api_settings[ $setting_name ][] = $atts;
				} else {
					$service_api_settings[ $setting_name ] = $atts;
				}
			}

			update_option( parent::$service_api_settings_option, $service_api_settings );
			self::log( 'info', "successfully moved the folder {$folder['name']} into {$parent['name']}: move_folder()" );
		} catch ( \Exception $e ) {
			self::log( 'error', "moving {$this->name} folder {$folder['name']}: " . $e->getMessage() );
		}
		
		return self::get_last_api_settings();
	}

}

endif; // class_exists

return new Gdrive_API();