<?php

namespace App\Models\Central;

use App\Services\Storage\StorageConfig;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;

/**
 * The platform's single storage configuration (one Cloudflare R2 bucket shared
 * by every tenant), stored as one encrypted payload in a singleton row.
 *
 * Central data — never tenant-scoped. Read through {@see StorageConfig}, which
 * layers this row over the `.env` values and is the only thing that should
 * build a disk from it.
 *
 * NOTE ON THE CAST: `config` is deliberately NOT an `encrypted:array` cast.
 * That cast is what turned an APP_KEY change into a hard failure — Eloquent's
 * dirty check (`originalIsEquivalent()`) decrypts the stored value on every
 * save, so once a row could not be decrypted it could not be overwritten
 * either, and the error surfaced as "The MAC is invalid." on unrelated screens
 * like a product image upload. Encryption here is explicit instead:
 * {@see decodedConfig()} catches a decrypt failure and reports "unreadable"
 * rather than throwing, and {@see writeConfig()} writes through the query
 * builder so a broken payload can always be replaced.
 */
class StorageSetting extends Model
{
    protected $table = 'storage_settings';

    protected $guarded = [];

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'quota_bytes' => 'integer',
        ];
    }

    /**
     * The singleton row, or null when the platform has never saved one.
     */
    public static function current(): ?self
    {
        return static::query()->orderBy('id')->first();
    }

    /**
     * The decrypted credentials.
     *
     * Returns null both when nothing is stored and when the payload cannot be
     * decrypted with the current APP_KEY. The caller ({@see StorageConfig})
     * treats both the same way — fall back to `.env` — so a rotated or
     * mismatched key degrades to the environment's credentials instead of
     * taking every upload down.
     *
     * @return array<string, mixed>|null
     */
    public function decodedConfig(): ?array
    {
        $raw = $this->attributes['config'] ?? null;

        if (blank($raw)) {
            return null;
        }

        try {
            $decoded = Crypt::decrypt($raw);
        } catch (DecryptException) {
            return null;
        }

        return is_array($decoded) ? $decoded : null;
    }

    /**
     * Whether a payload exists but this APP_KEY cannot read it — the exact
     * state that used to throw. Surfaced in the Storage settings UI so the
     * operator is told to re-enter the credentials rather than left wondering
     * why their saved bucket is being ignored.
     */
    public function hasUnreadableConfig(): bool
    {
        return filled($this->attributes['config'] ?? null) && $this->decodedConfig() === null;
    }

    /**
     * Persist credentials, encrypting them under the current APP_KEY.
     *
     * Writes via the query builder on purpose: an Eloquent save would run the
     * dirty check against the OLD value, which is unreadable in precisely the
     * situation this method exists to repair.
     *
     * @param  array<string, mixed>|null  $config
     */
    public static function writeConfig(?array $config, ?int $quotaBytes): self
    {
        $payload = $config === null ? null : Crypt::encrypt($config);
        $row = static::current();

        if ($row) {
            DB::table('storage_settings')
                ->where('id', $row->id)
                ->update([
                    'config' => $payload,
                    'quota_bytes' => $quotaBytes,
                    'updated_at' => now(),
                ]);

            app(StorageConfig::class)->flush();

            return static::query()->find($row->id);
        }

        $id = DB::table('storage_settings')->insertGetId([
            'config' => $payload,
            'quota_bytes' => $quotaBytes,
            'created_at' => now(),
            'updated_at' => now(),
        ]);

        // This is the ONE write path for the configuration, and it deliberately
        // bypasses Eloquent (see above), so model events cannot carry the
        // invalidation — flush StorageConfig's per-request memo here instead.
        app(StorageConfig::class)->flush();

        return static::query()->find($id);
    }
}
