<?php

declare(strict_types=1);

namespace PpPhp\Modules\Settings\Controllers;

use PpPhp\Core\Config;
use PpPhp\Core\Security;
use PpPhp\Core\Storage\StorageConfig;
use PpPhp\Core\Storage\StorageRegistry;
use PpPhp\Core\Storage\StorageOAuth;

/**
 * Storage Connections — owner + superadmin.
 *
 * Manage the cloud targets a File/Image field can upload to: S3-compatible
 * buckets (Amazon S3, Cloudflare R2, MinIO, Backblaze…) via keys, and Google
 * Drive / OneDrive via a connect-your-account OAuth flow. Secrets + refresh
 * tokens are stored encrypted (StorageConfig → PlatformSettings). Once saved, a
 * connection appears in every field's "Store on" dropdown.
 *
 * Access: Security::IsAdmin() (owner OR superadmin) — cloud credentials are a
 * console-level, platform-wide integration, so a superadmin managing their own
 * platform can configure them, not just the hidden owner tier.
 */
class StorageConnectionsController
{
    public function __construct(private Config $config) {}

    private function gateManage(): void
    {
        Security::RequireAuth();
        if (!Security::IsAdmin()) redirect('/projects');
    }

    private function redirectUri(): string
    {
        return rtrim((string) $this->config->get('base_url'), '/') . '/settings/storage/oauth/callback';
    }

    /** The Storage Connections management page. */
    public function index(): string
    {
        $this->gateManage();
        $connections = array_values(array_filter(StorageConfig::all(), static fn($c) => ($c['id'] ?? '') !== 'local'));
        $flash = $_SESSION['_flash'] ?? null;
        unset($_SESSION['_flash']);
        $redirectUri = $this->redirectUri();
        ob_start();
        include __DIR__ . '/../Views/storage_connections.php';
        return (string) ob_get_clean();
    }

    /** Create or update a connection. */
    public function save(): void
    {
        $this->gateManage();
        if (!Security::CSRF()->Check((string) ($_POST['_token'] ?? ''))) {
            $this->flash('Session expired — please try again.', 'error');
            redirect('/settings/storage');
        }
        $driver = preg_replace('/[^a-z0-9]/', '', strtolower((string) ($_POST['driver'] ?? '')));
        if (!in_array($driver, ['s3', 'gdrive', 'onedrive', 'ftp'], true)) {
            $this->flash('Unknown storage type.', 'error');
            redirect('/settings/storage');
        }
        $conn = $this->connFromPost($_POST);

        // Custom FTP/SFTP: prove the credentials work AND the folder is writable
        // before saving — a connection that can't store files is worse than none.
        if ($driver === 'ftp') {
            if (($conn['publicBase'] ?? '') === '') {
                $this->flash('Enter the Public base URL — it maps to the remote folder so stored files can be served.', 'error');
                redirect('/settings/storage');
            }
            $res = $this->testConn($conn);
            if (empty($res['ok'])) {
                $this->flash('Not saved — connection test failed: ' . ($res['message'] ?? 'unknown error'), 'error');
                redirect('/settings/storage');
            }
        }

        $savedId = StorageConfig::save($conn);
        // For an OAuth provider that isn't connected yet, go straight to consent.
        if (in_array($driver, ['gdrive', 'onedrive'], true) && (string) ($conn['refreshToken'] ?? '') === '' && !empty($_POST['connect'])) {
            redirect('/settings/storage/oauth/start?id=' . urlencode($savedId));
        }
        $this->flash('Connection saved.', 'success');
        redirect('/settings/storage');
    }

    /**
     * Build a connection-config array from posted fields (used by both save() and
     * the pre-save Test). Blank secrets fall back to the stored value on edit, so
     * an owner can re-test/re-save without re-typing the password.
     */
    private function connFromPost(array $in): array
    {
        $driver = preg_replace('/[^a-z0-9]/', '', strtolower((string) ($in['driver'] ?? '')));
        $id = preg_replace('/[^a-zA-Z0-9_-]/', '', (string) ($in['id'] ?? ''));
        $existing = $id !== '' ? StorageConfig::get($id) : [];
        $conn = ['id' => $id, 'driver' => $driver, 'label' => trim((string) ($in['label'] ?? '')) ?: ucfirst($driver)];

        if ($driver === 's3') {
            $conn += [
                'accessKey' => trim((string) ($in['accessKey'] ?? '')),
                'bucket'    => trim((string) ($in['bucket'] ?? '')),
                'region'    => trim((string) ($in['region'] ?? 'us-east-1')) ?: 'us-east-1',
                'endpoint'  => trim((string) ($in['endpoint'] ?? '')),
                'pathStyle' => !empty($in['pathStyle']),
                'public'    => !empty($in['public']),
            ];
            $secret = (string) ($in['secretKey'] ?? '');
            $conn['secretKey'] = $secret !== '' ? $secret : (string) ($existing['secretKey'] ?? '');
        } elseif ($driver === 'ftp') {
            $proto = strtolower((string) ($in['protocol'] ?? 'sftp'));
            $conn += [
                'protocol'   => in_array($proto, ['sftp', 'ftp', 'ftps'], true) ? $proto : 'sftp',
                'host'       => trim((string) ($in['host'] ?? '')),
                'port'       => (int) ($in['port'] ?? 0),
                'user'       => trim((string) ($in['user'] ?? '')),
                'basePath'   => trim((string) ($in['basePath'] ?? '')),
                'publicBase' => trim((string) ($in['publicBase'] ?? '')),
                'passive'    => !isset($in['passive']) ? true : !empty($in['passive']),
            ];
            $pw = (string) ($in['password'] ?? '');
            $conn['password'] = $pw !== '' ? $pw : (string) ($existing['password'] ?? '');
        } else {
            $conn += [
                'clientId' => trim((string) ($in['clientId'] ?? '')),
                'folder'   => trim((string) ($in['folder'] ?? '')),
                'folderId' => trim((string) ($in['folderId'] ?? '')),
                'refreshToken' => (string) ($existing['refreshToken'] ?? ''),
            ];
            $cs = (string) ($in['clientSecret'] ?? '');
            $conn['clientSecret'] = $cs !== '' ? $cs : (string) ($existing['clientSecret'] ?? '');
        }
        return $conn;
    }

    /** Instantiate the right driver for a transient (unsaved) config and test it. */
    private function testConn(array $conn): array
    {
        $type = (string) ($conn['driver'] ?? '');
        if (!StorageRegistry::available($type)) {
            return ['ok' => false, 'message' => 'This storage type is not available on this server.'];
        }
        try {
            $class = '\\PpPhp\\Core\\Storage\\' . [
                'ftp' => 'FtpStorageDriver', 's3' => 'S3Driver',
                'gdrive' => 'GoogleDriveDriver', 'onedrive' => 'OneDriveDriver',
            ][$type] ?? '';
            if ($class === '' || !class_exists($class)) return ['ok' => false, 'message' => 'No test for this type.'];
            $driver = new $class($conn);
            return method_exists($driver, 'test') ? $driver->test() : ['ok' => false, 'message' => 'This driver has no test.'];
        } catch (\Throwable $e) {
            return ['ok' => false, 'message' => $e->getMessage()];
        }
    }

    public function delete(): void
    {
        $this->gateManage();
        if (Security::CSRF()->Check((string) ($_POST['_token'] ?? ''))) {
            StorageConfig::remove(preg_replace('/[^a-zA-Z0-9_-]/', '', (string) ($_POST['id'] ?? '')));
            $this->flash('Connection removed.', 'success');
        }
        redirect('/settings/storage');
    }

    /** Live connectivity/credentials test (JSON). Tests posted (unsaved) fields
     *  when a driver is supplied, so the owner can verify before saving; otherwise
     *  tests the saved connection by id. */
    public function test(): void
    {
        $this->gateManage();
        header('Content-Type: application/json');
        if (!Security::CSRF()->Check((string) ($_POST['_token'] ?? ''))) {
            echo json_encode(['ok' => false, 'message' => 'Session expired — reload and try again.']);
            return;
        }
        // Test-before-save: build a transient driver straight from the form.
        if (($_POST['driver'] ?? '') !== '') {
            echo json_encode($this->testConn($this->connFromPost($_POST)));
            return;
        }
        // Fallback: test an already-saved connection by id.
        $id = preg_replace('/[^a-zA-Z0-9_-]/', '', (string) ($_POST['id'] ?? $_GET['id'] ?? ''));
        try {
            $driver = StorageRegistry::driver($id);
            $res = method_exists($driver, 'test') ? $driver->test() : ['ok' => false, 'message' => 'This driver has no test.'];
        } catch (\Throwable $e) {
            $res = ['ok' => false, 'message' => $e->getMessage()];
        }
        echo json_encode($res);
    }

    /** Step 1 of connecting an OAuth account: redirect to the provider's consent screen. */
    public function oauthStart(): void
    {
        $this->gateManage();
        $id = preg_replace('/[^a-zA-Z0-9_-]/', '', (string) ($_GET['id'] ?? ''));
        $conn = StorageConfig::get($id);
        $provider = (string) ($conn['driver'] ?? '');
        if (!isset(StorageOAuth::PROVIDERS[$provider]) || (string) ($conn['clientId'] ?? '') === '') {
            $this->flash('Enter the Client ID first, then Connect.', 'error');
            redirect('/settings/storage');
        }
        $_SESSION['_storage_oauth'] = ['id' => $id, 'state' => bin2hex(random_bytes(8))];
        $authorizeUrl = StorageOAuth::authorizeUrl($provider, (string) $conn['clientId'], $this->redirectUri(), $_SESSION['_storage_oauth']['state']);
        header('Location: ' . $authorizeUrl);   // external → provider consent screen
        exit;
    }

    /** Step 2: the provider redirects back with a code → exchange it for a refresh token. */
    public function oauthCallback(): void
    {
        $this->gateManage();
        $pending = $_SESSION['_storage_oauth'] ?? null;
        unset($_SESSION['_storage_oauth']);
        $code  = (string) ($_GET['code'] ?? '');
        $state = (string) ($_GET['state'] ?? '');
        if (!$pending || $code === '' || $state !== ($pending['state'] ?? '')) {
            $this->flash('Connection was cancelled or the session expired.', 'error');
            redirect('/settings/storage');
        }
        $conn = StorageConfig::get($pending['id']);
        $provider = (string) ($conn['driver'] ?? '');
        $tokens = StorageOAuth::exchangeCode($provider, (string) ($conn['clientId'] ?? ''), (string) ($conn['clientSecret'] ?? ''), $code, $this->redirectUri());
        $refresh = (string) ($tokens['refresh_token'] ?? '');
        if ($refresh === '') {
            $this->flash('The account did not return a refresh token — re-try and grant offline access.', 'error');
            redirect('/settings/storage');
        }
        $conn['refreshToken'] = $refresh;
        StorageConfig::save($conn);
        $this->flash('Account connected successfully.', 'success');
        redirect('/settings/storage');
    }

    private function flash(string $message, string $type): void
    {
        $_SESSION['_flash'] = ['message' => $message, 'type' => $type];
    }
}
