<?php

declare(strict_types=1);

namespace PpPhp\Core\Storage;

/**
 * GoogleDriveDriver — store File/Image uploads in a connected Google Drive.
 *
 * OAuth2 (drive.file scope) with a stored refresh token (see StorageOAuth). The
 * ref's `key` is the Drive file id. Uploads go via the Drive v3 multipart
 * upload; the file is granted "anyone with the link – reader" so the generated
 * app can display it via a stable Drive URL (note: link-accessible, not truly
 * private — proxy through the app if you need auth-gated Drive files).
 *
 * Connection config: clientId, clientSecret, refreshToken (from the connect
 * flow), optional folderId (a Drive folder to upload into; blank = My Drive
 * root). Google Drive is id-based, not path-based, so the per-field sub-folder
 * is not used here.
 */
final class GoogleDriveDriver implements StorageDriver
{
    private array $conn;
    private string $folderId;
    private const API = 'https://www.googleapis.com/drive/v3';
    private const UPLOAD = 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,name,size';

    public function __construct(array $config = [])
    {
        $this->conn = $config;
        $this->folderId = (string) ($config['folderId'] ?? '');
    }

    public static function id(): string { return 'gdrive'; }

    private function token(): string { return StorageOAuth::accessToken('gdrive', $this->conn); }

    public function put(string $tmpPath, array $meta): array
    {
        $token = $this->token();
        if ($token === '') throw new \RuntimeException('Google Drive is not connected.');
        $name = (string) ($meta['name'] ?? basename($tmpPath));
        $mime = (string) ($meta['mime'] ?? 'application/octet-stream') ?: 'application/octet-stream';
        $body = @file_get_contents($tmpPath);
        if ($body === false) throw new \RuntimeException('Could not read the upload.');

        $metadata = ['name' => $name];
        if ($this->folderId !== '') $metadata['parents'] = [$this->folderId];
        $boundary = 'pp' . bin2hex(random_bytes(8));
        $multipart = "--$boundary\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n"
            . json_encode($metadata) . "\r\n"
            . "--$boundary\r\nContent-Type: $mime\r\n\r\n" . $body . "\r\n--$boundary--";

        [$status, $resp] = $this->http('POST', self::UPLOAD, $token, $multipart, "multipart/related; boundary=$boundary");
        $json = json_decode($resp, true);
        if ($status < 200 || $status >= 300 || empty($json['id'])) {
            throw new \RuntimeException('Google Drive upload failed (HTTP ' . $status . ').');
        }
        $id = (string) $json['id'];
        // Grant "anyone with the link" read so the app can display it.
        $this->http('POST', self::API . "/files/$id/permissions",
            $token, json_encode(['role' => 'reader', 'type' => 'anyone']), 'application/json');

        return StorageRef::normalise(['v' => StorageRef::VERSION, 'driver' => 'gdrive', 'key' => $id,
            'name' => $name, 'size' => (int) ($meta['size'] ?? strlen($body)), 'mime' => $mime]
            + (isset($meta['w']) ? ['w' => (int) $meta['w'], 'h' => (int) ($meta['h'] ?? 0)] : []));
    }

    public function url(array $ref, array $opts = []): string
    {
        if (($ref['url'] ?? '') !== '') return (string) $ref['url'];
        $id = (string) ($ref['key'] ?? '');
        if ($id === '') return '';
        // Stable link-shared URL (works for images inline; downloads for others).
        return 'https://drive.google.com/uc?export=view&id=' . rawurlencode($id);
    }

    public function openStream(array $ref)
    {
        $token = $this->token();
        $id = (string) ($ref['key'] ?? '');
        if ($token === '' || $id === '') return null;
        [$status, $body] = $this->http('GET', self::API . "/files/$id?alt=media", $token, null);
        if ($status < 200 || $status >= 300) return null;
        $fh = fopen('php://temp', 'r+b'); fwrite($fh, (string) $body); rewind($fh); return $fh;
    }

    public function delete(array $ref): bool
    {
        $token = $this->token();
        $id = (string) ($ref['key'] ?? '');
        if ($token === '' || $id === '') return false;
        [$status] = $this->http('DELETE', self::API . "/files/$id", $token, null);
        return ($status >= 200 && $status < 300) || $status === 404;
    }

    public function exists(array $ref): bool
    {
        $token = $this->token();
        $id = (string) ($ref['key'] ?? '');
        if ($token === '' || $id === '') return false;
        [$status] = $this->http('GET', self::API . "/files/$id?fields=id", $token, null);
        return $status >= 200 && $status < 300;
    }

    /** Verify the account is connected (a refresh token that mints an access token). @return array{ok:bool,message:string} */
    public function test(): array
    {
        if ((string) ($this->conn['clientId'] ?? '') === '') return ['ok' => false, 'message' => 'Client ID is required.'];
        if ((string) ($this->conn['refreshToken'] ?? '') === '') return ['ok' => false, 'message' => 'Not connected yet — click Connect to authorize the account.'];
        return $this->token() !== '' ? ['ok' => true, 'message' => 'Connected to Google Drive.'] : ['ok' => false, 'message' => 'Could not refresh the access token — reconnect the account.'];
    }

    public function capabilities(): array
    {
        return ['directUpload' => false, 'publicUrl' => true, 'signedUrl' => false, 'chunked' => false, 'maxBytes' => 0];
    }

    /**
     * Replace the contents of an existing Drive file in place (keeps the same id,
     * so any link-shared URL stays valid). Returns true on success. Used by the
     * update-host backup so re-publishing does not create duplicate files.
     */
    public function updateFile(string $fileId, string $localPath, string $mime = ''): bool
    {
        $token = $this->token();
        if ($token === '' || $fileId === '') return false;
        $body = @file_get_contents($localPath);
        if ($body === false) return false;
        $mime = $mime !== '' ? $mime : 'application/octet-stream';
        $url = 'https://www.googleapis.com/upload/drive/v3/files/' . rawurlencode($fileId) . '?uploadType=media';
        [$status] = $this->http('PATCH', $url, $token, $body, $mime);
        return $status >= 200 && $status < 300;
    }

    /** @return array{0:int,1:string} */
    private function http(string $method, string $url, string $token, ?string $body, string $contentType = ''): array
    {
        if (!function_exists('curl_init')) return [0, ''];
        $headers = ['Authorization: Bearer ' . $token];
        if ($contentType !== '') $headers[] = 'Content-Type: ' . $contentType;
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => $headers, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 120,
        ]);
        if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        $resp = curl_exec($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        return [$status, is_string($resp) ? $resp : ''];
    }
}
