<?php
/**
 * OneDrive destination.
 *
 * Stores backup parts inside a configurable folder in the user's OneDrive
 * (default: "BackReload" at the root of My Drive). Each backup gets its own
 * `bk_<ext_id>/` subfolder mirroring the layout we use elsewhere so the
 * import-from-destination flow can browse for backups.
 *
 * Implementation delegated to OnedriveClient (thin HTTP wrapper around
 * Microsoft Graph v1.0). This class just maps the Destination interface
 * onto Graph paths.
 *
 * OAuth setup (handled by OauthController):
 *   1. User registers an app in Azure AD → App registrations
 *   2. Pastes Client ID + Secret into the destination form
 *   3. Clicks Save → popup opens to Microsoft for consent
 *   4. We receive tokens via the /oauth/onedrive/callback REST route
 *   5. Tokens persisted into config (encrypted at rest)
 *
 * @package Backreload\Destinations
 */

namespace Backreload\Destinations;

use Backreload\Models\DestinationRecord;

if ( ! defined( 'ABSPATH' ) ) { exit; }

class OnedriveDestination extends AbstractDestination {

	public static function type(): string  { return 'onedrive'; }
	public static function label(): string { return 'OneDrive'; }

	public static function config_schema(): array {
		return [
			[
				'key'      => 'client_id',
				'label'    => 'Client ID (Application ID)',
				'type'     => 'text',
				'required' => true,
				'help'     => 'From Azure AD → App registrations → Overview. See setup help below.',
			],
			[ 'key' => 'client_secret', 'label' => 'Client secret', 'type' => 'password', 'required' => true, 'secret' => true,
			  'help' => 'From Azure AD → App registrations → Certificates & secrets.' ],
			[
				'key'     => 'tenant_id',
				'label'   => 'Tenant',
				'type'    => 'text',
				'default' => 'common',
				'help'    => 'Use "common" for personal + workplace accounts, "consumers" for personal only, "organizations" for workplace only, or your Azure AD tenant ID for a single-tenant app.',
			],
			[
				'key'     => 'folder',
				'label'   => 'Folder name',
				'type'    => 'text',
				'default' => 'BackReload',
				'help'    => 'A folder of this name is created at the root of your OneDrive. Backups land inside, grouped into bk_* subfolders.',
			],
		];
	}

	/**
	 * Step-by-step Azure AD setup guide. Same shape as GoogleDestination.
	 * Catalog passes it through; React renders below the OAuth fields.
	 */
	public static function setup_help(): array {
		$callback = rest_url( 'backreload/v1/oauth/onedrive/callback' );
		return [
			'title'    => 'Setup help: where do I get the Client ID and Client Secret?',
			'provider' => 'OneDrive',
			'steps' => [
				'Sign in to <a href="https://portal.azure.com" target="_blank" rel="noopener">Azure Portal</a> with the account that owns the OneDrive you want to back up to.',
				'Open <strong>Microsoft Entra ID → App registrations → + New registration</strong>.',
				'Name it anything (e.g. "BackReload"). Under <strong>Supported account types</strong> pick <strong>Accounts in any organizational directory and personal Microsoft accounts</strong> for the most permissive setup. Pick <strong>Web</strong> as the redirect URI platform, then paste the URL from the box below.',
				'After registration, copy the <strong>Application (client) ID</strong> from the Overview tab into the Client ID field above.',
				'Open <strong>Certificates &amp; secrets → + New client secret</strong>. Give it a description and pick an expiry (24 months is the longest Microsoft allows). Save it. <strong>Copy the value column immediately</strong> because Azure hides it after you leave the page.',
				'Paste the copied secret into the Client Secret field above.',
				'Open <strong>API permissions → + Add a permission → Microsoft Graph → Delegated permissions</strong>. Add <code>Files.ReadWrite</code>, <code>offline_access</code>, and <code>User.Read</code>. Save.',
				'Save the destination, then click "Sign in with Microsoft" to complete the consent flow.',
			],
			'callouts' => [
				[
					'label' => 'Redirect URI (Web platform)',
					'value' => $callback,
					'help'  => 'Paste exactly. Must match what Azure receives during the OAuth handshake.',
				],
			],
		];
	}

	/**
	 * Ping /me/drive to confirm the token still works.
	 */
	public function test( DestinationRecord $dest ): bool {
		$this->ensure_authorized( $dest );
		$client = new OnedriveClient( $dest );
		return $client->test_connection();
	}

	public function upload( DestinationRecord $dest, string $local_path, string $remote_name ): string {
		$this->ensure_authorized( $dest );
		$client = new OnedriveClient( $dest );
		$root   = $this->root_folder( $dest );
		[ $sub, $file_name ] = $this->split_remote_name( $remote_name );

		// Make sure the destination folder structure exists. OneDrive's Graph
		// API can implicitly create a missing folder when uploading via a
		// path expression, but only the LEAF — intermediate segments must
		// already exist. ensure_folder walks the whole chain.
		$folder_path = $sub ? "{$root}/{$sub}" : $root;
		$client->ensure_folder( $folder_path );

		$client->upload( $local_path, "{$folder_path}/{$file_name}" );
		return $remote_name;
	}

	public function download( DestinationRecord $dest, string $remote_name, string $local_path ): bool {
		$this->ensure_authorized( $dest );
		$client = new OnedriveClient( $dest );
		$root   = $this->root_folder( $dest );
		[ $sub, $file_name ] = $this->split_remote_name( $remote_name );
		$path   = $sub ? "{$root}/{$sub}/{$file_name}" : "{$root}/{$file_name}";
		return $client->download( $path, $local_path );
	}

	public function delete( DestinationRecord $dest, string $remote_name ): bool {
		$this->ensure_authorized( $dest );
		$client = new OnedriveClient( $dest );
		$root   = $this->root_folder( $dest );
		[ $sub, $file_name ] = $this->split_remote_name( $remote_name );
		$path   = $sub ? "{$root}/{$sub}/{$file_name}" : "{$root}/{$file_name}";
		return $client->delete( $path );
	}

	/**
	 * List bk_* subfolders + their contents. Same shape as the Local /
	 * Drive list_backups() responses so the import-from-destination UI
	 * works without per-provider branching.
	 */
	public function list_backups( DestinationRecord $dest ): array {
		$this->ensure_authorized( $dest );
		$client = new OnedriveClient( $dest );
		$root   = $this->root_folder( $dest );

		$top = $client->list_children( $root );
		$out = [];
		foreach ( $top as $entry ) {
			if ( ! $entry['is_folder'] ) {
				continue;
			}
			if ( strpos( (string) $entry['name'], 'bk_' ) !== 0 ) {
				continue;
			}
			$ext_id   = (string) $entry['name'];
			$children = $client->list_children( "{$root}/{$ext_id}" );
			$parts    = [];
			$manifest = null;
			foreach ( $children as $c ) {
				if ( $c['is_folder'] ) {
					continue; // nested folders aren't expected; skip safely
				}
				if ( \Backreload\Core\Manifest::FILENAME === $c['name'] ) {
					$tmp = self::temp_path( 'backreload-manifest' );
					if ( $client->download( "{$root}/{$ext_id}/" . $c['name'], $tmp ) ) {
						$manifest = \Backreload\Core\Manifest::read( $tmp );
					}
					if ( is_file( $tmp ) ) {
						wp_delete_file( $tmp );
					}
					continue;
				}
				$parts[] = [
					'name' => $ext_id . '/' . $c['name'],
					'size' => (int) $c['size'],
				];
			}
			if ( empty( $parts ) && null === $manifest ) {
				continue;
			}
			$out[] = [
				'ext_id'       => $ext_id,
				'has_manifest' => null !== $manifest,
				'manifest'     => $manifest,
				'parts'        => $parts,
			];
		}
		return $out;
	}

	public function usage( DestinationRecord $dest ): array {
		if ( empty( $dest->config['access_token'] ) ) {
			return [ 'used' => 0, 'free' => 0, 'total' => 0 ];
		}
		try {
			$client = new OnedriveClient( $dest );
			return $client->quota();
		} catch ( \Throwable $e ) {
			return [ 'used' => 0, 'free' => 0, 'total' => 0 ];
		}
	}

	private function ensure_authorized( DestinationRecord $dest ): void {
		if ( empty( $dest->config['access_token'] ) ) {
			throw new \RuntimeException( esc_html( 'OneDrive destination has not completed OAuth. Open the destination on the Destinations page and click "Sign in with Microsoft".' ) );
		}
	}

	private function root_folder( DestinationRecord $dest ): string {
		$name = (string) ( $dest->config['folder'] ?? 'BackReload' );
		return '' === $name ? 'BackReload' : $name;
	}

	/**
	 * Split `bk_<ext_id>/plugins-1.zip` into [ 'bk_<ext_id>', 'plugins-1.zip' ].
	 * Returns [ '', $name ] when there's no slash — file lands in root folder.
	 *
	 * @return array{0:string,1:string}
	 */
	private function split_remote_name( string $remote_name ): array {
		$pos = strpos( $remote_name, '/' );
		if ( false === $pos ) {
			return [ '', $remote_name ];
		}
		return [
			substr( $remote_name, 0, $pos ),
			substr( $remote_name, $pos + 1 ),
		];
	}
}
