<?php

declare(strict_types=1);

namespace PpPhp\Core\Storage;

/**
 * StorageConfig — named storage "connections".
 *
 * A connection is a saved target a field can point at: the built-in self-hosted
 * disk ("local"), or (cloud phase) an S3 bucket / Google Drive / OneDrive
 * account. Each connection is an array:
 *   ['id'=>'local','driver'=>'local','label'=>'This server', ...driver keys]
 *
 * Sources, in priority order:
 *   1. The generated app's config/storage.php (returns array<string,connection>).
 *   2. The console's PlatformSettings key 'storage.connections' (when present).
 *   3. A synthesised built-in "local" connection, always available.
 *
 * Secret keys (api secrets, OAuth refresh tokens) are stored encrypted via
 * core/Auth/FieldCrypto and decrypted on read here — never persisted in clear.
 */
final class StorageConfig
{
    /** Connection-config keys treated as secrets (encrypted at rest). */
    private const SECRET_KEYS = ['secret', 'accessKey', 'secretKey', 'refreshToken', 'accessToken', 'clientSecret', 'apiKey', 'password'];

    /** @var array<string,array<string,mixed>>|null */
    private static ?array $cache = null;

    /** All known connections keyed by id (always includes 'local'). */
    public static function all(): array
    {
        if (self::$cache !== null) return self::$cache;

        $conns = [];

        // 1. Generated-app config file.
        $file = self::appConfigFile();
        if ($file !== null && is_file($file)) {
            $data = @include $file;
            if (is_array($data)) {
                foreach ($data as $id => $c) {
                    if (is_array($c)) $conns[(string) ($c['id'] ?? $id)] = self::hydrate((string) ($c['id'] ?? $id), $c);
                }
            }
        }

        // 2. Console PlatformSettings.
        if (class_exists('\PpPhp\Core\PlatformSettings')) {
            $raw = \PpPhp\Core\PlatformSettings::get('storage.connections');
            if (is_string($raw) && $raw !== '') $raw = json_decode($raw, true);
            if (is_array($raw)) {
                foreach ($raw as $id => $c) {
                    if (is_array($c)) $conns[(string) ($c['id'] ?? $id)] = self::hydrate((string) ($c['id'] ?? $id), $c);
                }
            }
        }

        // 3. Built-in local always present (does not override a customised one).
        if (!isset($conns['local'])) {
            $conns['local'] = ['id' => 'local', 'driver' => 'local', 'label' => 'This server (self-hosted)'];
        }

        return self::$cache = $conns;
    }

    /** A single connection by id, falling back to 'local'. */
    public static function get(string $id): array
    {
        $all = self::all();
        return $all[$id] ?? $all['local'] ?? ['id' => 'local', 'driver' => 'local'];
    }

    /** Connection ids + labels for a settings dropdown. @return array<int,array{id:string,label:string,driver:string}> */
    public static function choices(): array
    {
        $out = [];
        foreach (self::all() as $c) {
            $out[] = ['id' => (string) $c['id'], 'label' => (string) ($c['label'] ?? $c['id']), 'driver' => (string) ($c['driver'] ?? 'local')];
        }
        return $out;
    }

    /** Create/update a connection (console-side, persisted to PlatformSettings). Returns the id. */
    public static function save(array $conn): string
    {
        $id = preg_replace('/[^a-zA-Z0-9_-]/', '', (string) ($conn['id'] ?? '')) ?: ('conn' . substr(md5(microtime() . random_bytes(4)), 0, 8));
        $conn['id'] = $id;
        $map = self::rawMap();
        $map[$id] = self::seal($conn);
        self::persist($map);
        self::clearCache();
        return $id;
    }

    /** Delete a connection by id. */
    public static function remove(string $id): void
    {
        $map = self::rawMap();
        if (isset($map[$id])) { unset($map[$id]); self::persist($map); self::clearCache(); }
    }

    /** The raw stored connection map (secrets still encrypted). */
    private static function rawMap(): array
    {
        if (!class_exists('\PpPhp\Core\PlatformSettings')) return [];
        $raw = \PpPhp\Core\PlatformSettings::get('storage.connections');
        return is_array($raw) ? $raw : [];
    }
    private static function persist(array $map): void
    {
        if (class_exists('\PpPhp\Core\PlatformSettings')) {
            \PpPhp\Core\PlatformSettings::set('storage.connections', $map);
        }
    }

    /** Encrypt a connection's secret keys for persistence. */
    public static function seal(array $conn): array
    {
        foreach (self::SECRET_KEYS as $k) {
            if (isset($conn[$k]) && is_string($conn[$k]) && $conn[$k] !== '' && !str_starts_with($conn[$k], 'enc:')) {
                $conn[$k] = 'enc:' . self::encrypt($conn[$k]);
            }
        }
        return $conn;
    }

    public static function clearCache(): void { self::$cache = null; }

    /** Decrypt secret keys on read. */
    private static function hydrate(string $id, array $c): array
    {
        $c['id'] = $id;
        $c['driver'] = (string) ($c['driver'] ?? 'local');
        foreach (self::SECRET_KEYS as $k) {
            if (isset($c[$k]) && is_string($c[$k]) && str_starts_with($c[$k], 'enc:')) {
                $c[$k] = self::decrypt(substr($c[$k], 4));
            }
        }
        return $c;
    }

    private static function appConfigFile(): ?string
    {
        if (defined('BASE_PATH')) return rtrim((string) \BASE_PATH, '/\\') . '/config/storage.php';
        return null;
    }

    private static function encrypt(string $plain): string
    {
        if (class_exists('\PpPhp\Core\Auth\FieldCrypto')) {
            try { return \PpPhp\Core\Auth\FieldCrypto::encrypt($plain); } catch (\Throwable $e) {}
        }
        return base64_encode($plain); // last-resort; only hit if crypto unavailable
    }

    private static function decrypt(string $stored): string
    {
        if (class_exists('\PpPhp\Core\Auth\FieldCrypto')) {
            try { return \PpPhp\Core\Auth\FieldCrypto::decrypt($stored); } catch (\Throwable $e) {}
        }
        $d = base64_decode($stored, true);
        return $d === false ? '' : $d;
    }
}
