<?php

declare( strict_types=1 );

namespace StrykeWP\Modules\Backup;

/**
 * Manages all communication with the Google Drive API v3.
 *
 * Migrated from STRYKE_Google_API. Fixed: download_file() was accidentally
 * placed outside the class in the original — it is now a proper method.
 *
 * @package StrykeWP\Modules\Backup
 */
class GoogleDriveClient {

	/**
	 * Chunk size for resumable uploads (5 MB). Must be a multiple of 256 KiB.
	 */
	private const CHUNK_SIZE = 5 * 1024 * 1024;

	private ?string $access_token      = null;
	private ?string $target_folder_id  = null;
	private string  $target_folder_name = 'STRYKE Backups';

	private string $client_id;
	private string $client_secret;
	private string $refresh_token;

	public function __construct() {
		$creds               = BackupSettings::get_active_credentials();
		$this->client_id     = $creds['client_id'];
		$this->client_secret = $creds['client_secret'];
		$this->refresh_token = $creds['refresh_token'];
	}

	// -----------------------------------------------------------------------
	// Auth
	// -----------------------------------------------------------------------

	/**
	 * Obtains a fresh access token using the stored refresh token.
	 */
	private function refresh_access_token(): bool {
		if ( empty( $this->refresh_token ) ) {
			error_log( 'STRYKE API Error: Refresh Token fehlt.' );
			return false;
		}

		$response = wp_remote_post(
			'https://oauth2.googleapis.com/token',
			array(
				'body' => array(
					'client_id'     => $this->client_id,
					'client_secret' => $this->client_secret,
					'refresh_token' => $this->refresh_token,
					'grant_type'    => 'refresh_token',
				),
			)
		);

		if ( is_wp_error( $response ) ) {
			error_log( 'STRYKE API Error: Token-Erneuerung fehlgeschlagen. ' . $response->get_error_message() );
			return false;
		}

		$data = json_decode( wp_remote_retrieve_body( $response ), true );

		if ( isset( $data['access_token'] ) ) {
			$this->access_token = $data['access_token'];
			return true;
		}

		error_log( 'STRYKE API Error: Konnte Access Token nicht extrahieren. Keys: ' . implode( ', ', array_keys( $data ?? array() ) ) );
		return false;
	}

	// -----------------------------------------------------------------------
	// Folder Management
	// -----------------------------------------------------------------------

	/**
	 * Finds or creates the "STRYKE Backups" target folder in Google Drive.
	 *
	 * @return string|false Folder ID or false on error.
	 */
	public function get_or_create_target_folder(): string|false {
		if ( ! $this->refresh_access_token() ) {
			return false;
		}

		$find_response = wp_remote_get(
			add_query_arg(
				array(
					'q'      => "name='{$this->target_folder_name}' and mimeType='application/vnd.google-apps.folder' and trashed=false",
					'fields' => 'files(id)',
				),
				'https://www.googleapis.com/drive/v3/files'
			),
			array(
				'headers' => array( 'Authorization' => 'Bearer ' . $this->access_token ),
			)
		);

		if ( is_wp_error( $find_response ) ) {
			error_log( 'STRYKE API Error: Ordnersuche fehlgeschlagen. ' . $find_response->get_error_message() );
			return false;
		}

		$find_data = json_decode( wp_remote_retrieve_body( $find_response ), true );

		if ( ! empty( $find_data['files'] ) ) {
			$this->target_folder_id = $find_data['files'][0]['id'];
			return $this->target_folder_id;
		}

		// Folder not found — create it.
		$create_response = wp_remote_post(
			'https://www.googleapis.com/drive/v3/files',
			array(
				'headers' => array(
					'Authorization' => 'Bearer ' . $this->access_token,
					'Content-Type'  => 'application/json',
				),
				'body'    => wp_json_encode(
					array(
						'name'     => $this->target_folder_name,
						'mimeType' => 'application/vnd.google-apps.folder',
					)
				),
			)
		);

		if ( is_wp_error( $create_response ) || 200 !== wp_remote_retrieve_response_code( $create_response ) ) {
			error_log( 'STRYKE API Error: Ordner konnte nicht erstellt werden. HTTP ' . wp_remote_retrieve_response_code( $create_response ) );
			return false;
		}

		$create_data = json_decode( wp_remote_retrieve_body( $create_response ), true );
		if ( isset( $create_data['id'] ) ) {
			$this->target_folder_id = $create_data['id'];
			return $this->target_folder_id;
		}

		return false;
	}

	// -----------------------------------------------------------------------
	// File Operations
	// -----------------------------------------------------------------------

	/**
	 * Uploads a backup ZIP to Google Drive via multipart upload.
	 *
	 * @return string|false Google Drive file ID or false on error.
	 */
	public function upload_file( string $file_path, string $file_name ): string|false {
		if ( ! file_exists( $file_path ) ) {
			error_log( 'STRYKE Upload Error: Datei nicht gefunden: ' . $file_path );
			return false;
		}

		if ( ! $this->refresh_access_token() ) {
			return false;
		}

		if ( is_null( $this->target_folder_id ) ) {
			$folder_id = $this->get_or_create_target_folder();
			if ( false === $folder_id ) {
				error_log( 'STRYKE Upload Error: Zielordner-ID konnte nicht abgerufen/erstellt werden.' );
				return false;
			}
		}

		// Use resumable upload for files larger than CHUNK_SIZE to avoid memory exhaustion.
		$file_size = filesize( $file_path );
		if ( false !== $file_size && $file_size > self::CHUNK_SIZE ) {
			return $this->upload_file_resumable( $file_path, $file_name, $file_size );
		}

		// Small files: keep simple multipart upload.
		$file_content = file_get_contents( $file_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		$boundary     = 'stryke_boundary_' . wp_generate_uuid4();

		$metadata = array(
			'name'     => $file_name,
			'parents'  => array( $this->target_folder_id ),
			'mimeType' => 'application/zip',
		);

		$body  = "--{$boundary}\r\n";
		$body .= "Content-Type: application/json; charset=UTF-8\r\n\r\n";
		$body .= wp_json_encode( $metadata ) . "\r\n";
		$body .= "--{$boundary}\r\n";
		$body .= "Content-Type: application/zip\r\n";
		$body .= "Content-Transfer-Encoding: binary\r\n\r\n";
		$body .= $file_content . "\r\n";
		$body .= "--{$boundary}--";

		$upload_response = wp_remote_post(
			'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart',
			array(
				'headers' => array(
					'Authorization'  => 'Bearer ' . $this->access_token,
					'Content-Type'   => "multipart/related; boundary={$boundary}",
					'Content-Length' => (string) strlen( $body ),
				),
				'body'    => $body,
				'timeout' => 300,
			)
		);

		if ( is_wp_error( $upload_response ) || 200 !== wp_remote_retrieve_response_code( $upload_response ) ) {
			error_log( 'STRYKE Upload Error: Google Drive Upload fehlgeschlagen. HTTP ' . wp_remote_retrieve_response_code( $upload_response ) );
			return false;
		}

		$upload_data = json_decode( wp_remote_retrieve_body( $upload_response ), true );
		return $upload_data['id'] ?? false;
	}

	/**
	 * Uploads a file using Google Drive Resumable Upload API (chunked, memory-safe).
	 *
	 * @param string $file_path  Absolute path to the file.
	 * @param string $file_name  Desired filename on Google Drive.
	 * @param int    $file_size  File size in bytes.
	 * @return string|false      Google Drive file ID or false on error.
	 */
	private function upload_file_resumable( string $file_path, string $file_name, int $file_size ): string|false {
		// Step 1: Initiate resumable upload session.
		$metadata = wp_json_encode( array(
			'name'     => $file_name,
			'parents'  => array( $this->target_folder_id ),
			'mimeType' => 'application/zip',
		) );

		$init_response = wp_remote_post(
			'https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable',
			array(
				'headers' => array(
					'Authorization'           => 'Bearer ' . $this->access_token,
					'Content-Type'            => 'application/json; charset=UTF-8',
					'X-Upload-Content-Type'   => 'application/zip',
					'X-Upload-Content-Length' => (string) $file_size,
				),
				'body'    => $metadata,
				'timeout' => 30,
			)
		);

		if ( is_wp_error( $init_response ) || 200 !== wp_remote_retrieve_response_code( $init_response ) ) {
			error_log( 'STRYKE Resumable Init Error: HTTP ' . wp_remote_retrieve_response_code( $init_response ) );
			return false;
		}

		$upload_uri = wp_remote_retrieve_header( $init_response, 'location' );
		if ( empty( $upload_uri ) ) {
			error_log( 'STRYKE Resumable Error: Keine Upload-URI erhalten.' );
			return false;
		}

		// Step 2: Upload in chunks.
		$handle = fopen( $file_path, 'rb' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
		if ( false === $handle ) {
			error_log( 'STRYKE Resumable Error: Datei konnte nicht gelesen werden.' );
			return false;
		}

		$offset        = 0;
		$last_response = null;

		while ( $offset < $file_size ) {
			$chunk      = fread( $handle, self::CHUNK_SIZE ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fread
			$chunk_size = strlen( $chunk );
			$range_end  = $offset + $chunk_size - 1;

			$last_response = wp_remote_request(
				$upload_uri,
				array(
					'method'  => 'PUT',
					'headers' => array(
						'Content-Length' => (string) $chunk_size,
						'Content-Range'  => "bytes {$offset}-{$range_end}/{$file_size}",
					),
					'body'    => $chunk,
					'timeout' => 120,
				)
			);

			$status = wp_remote_retrieve_response_code( $last_response );

			if ( 308 === $status ) {
				// Chunk acknowledged, continue.
				$offset += $chunk_size;
				continue;
			}

			if ( 200 === $status || 201 === $status ) {
				// Upload complete.
				fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
				$data = json_decode( wp_remote_retrieve_body( $last_response ), true );
				return $data['id'] ?? false;
			}

			// Error.
			error_log( 'STRYKE Chunk Upload Error bei Offset ' . $offset . ': HTTP ' . $status );
			fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
			return false;
		}

		fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose

		// Parse final response if loop completed without explicit 200/201.
		if ( null !== $last_response ) {
			$data = json_decode( wp_remote_retrieve_body( $last_response ), true );
			return $data['id'] ?? false;
		}

		return false;
	}

	/**
	 * Deletes a file from Google Drive.
	 */
	public function delete_file( string $file_id ): bool {
		if ( ! $this->refresh_access_token() ) {
			return false;
		}

		$response = wp_remote_request(
			'https://www.googleapis.com/drive/v3/files/' . rawurlencode( $file_id ),
			array(
				'method'  => 'DELETE',
				'headers' => array( 'Authorization' => 'Bearer ' . $this->access_token ),
			)
		);

		if ( is_wp_error( $response ) || 204 !== wp_remote_retrieve_response_code( $response ) ) {
			error_log( 'STRYKE API Error: Fehler beim Löschen der Datei ' . $file_id );
			return false;
		}

		return true;
	}

	/**
	 * Streams a file from Google Drive directly to the browser.
	 * Outputs file content and exits. Returns false on error.
	 */
	public function download_file( string $file_id, string $filename ): bool {
		if ( ! $this->refresh_access_token() ) {
			return false;
		}

		$temp_file = tempnam( sys_get_temp_dir(), 'stryke_dl' );

		$response = wp_remote_get(
			'https://www.googleapis.com/drive/v3/files/' . rawurlencode( $file_id ) . '?alt=media',
			array(
				'headers'  => array( 'Authorization' => 'Bearer ' . $this->access_token ),
				'timeout'  => 300,
				'stream'   => true,
				'filename' => $temp_file,
			)
		);

		if ( is_wp_error( $response ) ) {
			error_log( 'STRYKE Download Error: ' . $response->get_error_message() );
			@unlink( $temp_file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
			return false;
		}

		if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
			error_log( 'STRYKE Download Error: HTTP ' . wp_remote_retrieve_response_code( $response ) );
			@unlink( $temp_file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
			return false;
		}

		if ( ob_get_level() ) {
			ob_end_clean();
		}

		header( 'Content-Description: File Transfer' );
		header( 'Content-Type: application/zip' );
		header( 'Content-Disposition: attachment; filename="' . sanitize_file_name( basename( $filename ) ) . '"' );
		header( 'Expires: 0' );
		header( 'Cache-Control: must-revalidate' );
		header( 'Pragma: public' );

		readfile( $temp_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_readfile
		@unlink( $temp_file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
		exit;
	}
}
